diff --git a/.github/workflows/tests_with_context_path.yml b/.github/workflows/tests_with_context_path.yml index 8a3fa5090..50317f610 100644 --- a/.github/workflows/tests_with_context_path.yml +++ b/.github/workflows/tests_with_context_path.yml @@ -84,6 +84,14 @@ jobs: name: questdb-log path: tmp/dbroot/log/* + - name: Upload Cypress Screenshots + if: success() || failure() + uses: actions/upload-artifact@v4 + with: + name: cypress-screenshots + path: packages/browser-tests/cypress/screenshots + if-no-files-found: ignore + - name: Stop QuestDB if: success() || failure() run: ./tmp/questdb-*-rt-linux-x86-64/bin/questdb.sh stop diff --git a/.pnp.cjs b/.pnp.cjs index d4e601d01..e95aad45b 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -6615,6 +6615,7 @@ const RAW_RUNTIME_STATE = ["@questdb/sql-grammar", "npm:1.2.4"],\ ["@radix-ui/react-context-menu", "virtual:b7c775051d99785ec73273707ec685f06b84c737b39cc023ebc60bda25254288f27e86643fb15b7a00bd8a6d76cc119d4628443d858e76eb62af2718d7eb18cf#npm:2.1.5"],\ ["@radix-ui/react-dialog", "virtual:b7c775051d99785ec73273707ec685f06b84c737b39cc023ebc60bda25254288f27e86643fb15b7a00bd8a6d76cc119d4628443d858e76eb62af2718d7eb18cf#npm:1.0.5"],\ + ["@radix-ui/react-dropdown-menu", "virtual:b7c775051d99785ec73273707ec685f06b84c737b39cc023ebc60bda25254288f27e86643fb15b7a00bd8a6d76cc119d4628443d858e76eb62af2718d7eb18cf#npm:2.1.15"],\ ["@styled-icons/bootstrap", "virtual:b7c775051d99785ec73273707ec685f06b84c737b39cc023ebc60bda25254288f27e86643fb15b7a00bd8a6d76cc119d4628443d858e76eb62af2718d7eb18cf#npm:10.47.0"],\ ["@styled-icons/boxicons-logos", "virtual:b7c775051d99785ec73273707ec685f06b84c737b39cc023ebc60bda25254288f27e86643fb15b7a00bd8a6d76cc119d4628443d858e76eb62af2718d7eb18cf#npm:10.47.0"],\ ["@styled-icons/boxicons-regular", "virtual:b7c775051d99785ec73273707ec685f06b84c737b39cc023ebc60bda25254288f27e86643fb15b7a00bd8a6d76cc119d4628443d858e76eb62af2718d7eb18cf#npm:10.47.0"],\ @@ -6767,6 +6768,13 @@ const RAW_RUNTIME_STATE = ["@babel/runtime", "npm:7.23.1"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:1.1.2", {\ + "packageLocation": "./.yarn/cache/@radix-ui-primitive-npm-1.1.2-6c4aac54e5-6cb2ac097f.zip/node_modules/@radix-ui/primitive/",\ + "packageDependencies": [\ + ["@radix-ui/primitive", "npm:1.1.2"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@radix-ui/react-alert-dialog", [\ @@ -6817,6 +6825,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.7", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-arrow-npm-1.1.7-f534aad787-6cdf74f060.zip/node_modules/@radix-ui/react-arrow/",\ + "packageDependencies": [\ + ["@radix-ui/react-arrow", "npm:1.1.7"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:1fdc94c5df04c255c0b5de85157cf84a4ba430a2f014b72559f72becfd0741fd1fe73e536ed02c8ee5b0b60c458bd024623974e87957215a245e478dae0528bf#npm:1.0.3", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-arrow-virtual-c96d95d0bb/0/cache/@radix-ui-react-arrow-npm-1.0.3-d57b8cf08f-8cca086f0d.zip/node_modules/@radix-ui/react-arrow/",\ "packageDependencies": [\ @@ -6836,6 +6851,24 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["virtual:47a13e63d321cfce00f9bd3153f9ef0ff7b7abdf04b3fcaaf0ea3255ea587812c08b9b4e7ea1085f6e58dd55c26111a5f611bb770f540a49a4604edb212c8372#npm:1.1.7", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-arrow-virtual-5c2b474339/0/cache/@radix-ui-react-arrow-npm-1.1.7-f534aad787-6cdf74f060.zip/node_modules/@radix-ui/react-arrow/",\ + "packageDependencies": [\ + ["@radix-ui/react-arrow", "virtual:47a13e63d321cfce00f9bd3153f9ef0ff7b7abdf04b3fcaaf0ea3255ea587812c08b9b4e7ea1085f6e58dd55c26111a5f611bb770f540a49a4604edb212c8372#npm:1.1.7"],\ + ["@radix-ui/react-primitive", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:2.1.3"],\ + ["@types/react", "npm:17.0.2"],\ + ["@types/react-dom", "npm:16.9.8"],\ + ["react", "npm:17.0.2"],\ + ["react-dom", "virtual:455cc1315669a6e622038e6093381f8d95ab8bb473af09abaaf7e72fce2d6a9ff2602fe53215abe1948a8259d689b4411a17531ea4acc5522a659c642ee7696d#npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react",\ + "react-dom",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:b1393fa62437828464cae528959a7d7b857ebe5a4e6e9b38ac176c0822c8dac6882c71ed9e1630c2fa130c7abc56da61fd4b275ba33de5b860a4d0ef2c5587cd#npm:1.0.3", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-arrow-virtual-f59da43b30/0/cache/@radix-ui-react-arrow-npm-1.0.3-d57b8cf08f-8cca086f0d.zip/node_modules/@radix-ui/react-arrow/",\ "packageDependencies": [\ @@ -6879,6 +6912,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.7", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-collection-npm-1.1.7-ae6da53399-cd53e2a2be.zip/node_modules/@radix-ui/react-collection/",\ + "packageDependencies": [\ + ["@radix-ui/react-collection", "npm:1.1.7"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:8473f355c418a88b1335f02ca575aaf21e571210b49dea3695d702f03556bb3f7fd6d03390454f8cb9aab89c810628052c7c1f94d9b6866d479d5b8526a6e7a6#npm:1.0.3", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-collection-virtual-33fa155d4e/0/cache/@radix-ui-react-collection-npm-1.0.3-e63f97f38b-2ac740ab74.zip/node_modules/@radix-ui/react-collection/",\ "packageDependencies": [\ @@ -6922,6 +6962,27 @@ const RAW_RUNTIME_STATE = "react"\ ],\ "linkType": "HARD"\ + }],\ + ["virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.7", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-collection-virtual-bd77046a97/0/cache/@radix-ui-react-collection-npm-1.1.7-ae6da53399-cd53e2a2be.zip/node_modules/@radix-ui/react-collection/",\ + "packageDependencies": [\ + ["@radix-ui/react-collection", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.7"],\ + ["@radix-ui/react-compose-refs", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@radix-ui/react-context", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@radix-ui/react-primitive", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:2.1.3"],\ + ["@radix-ui/react-slot", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.2.3"],\ + ["@types/react", "npm:17.0.2"],\ + ["@types/react-dom", "npm:16.9.8"],\ + ["react", "npm:17.0.2"],\ + ["react-dom", "virtual:455cc1315669a6e622038e6093381f8d95ab8bb473af09abaaf7e72fce2d6a9ff2602fe53215abe1948a8259d689b4411a17531ea4acc5522a659c642ee7696d#npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react",\ + "react-dom",\ + "react"\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@radix-ui/react-compose-refs", [\ @@ -6939,6 +7000,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.2", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-compose-refs-npm-1.1.2-f0371f8267-9a91f02130.zip/node_modules/@radix-ui/react-compose-refs/",\ + "packageDependencies": [\ + ["@radix-ui/react-compose-refs", "npm:1.1.2"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:47c4e77569e041bff79bd1ddad1bc7b570c90d41054db55f98d0e7c9ed6833246ad39cdb11895b53770ab2e980507584b01ba43d98bda3f9d03cff7bfa2cd915#npm:1.0.1", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-compose-refs-virtual-fad8c81850/0/cache/@radix-ui-react-compose-refs-npm-1.0.1-02d1046f7d-2b9a613b6d.zip/node_modules/@radix-ui/react-compose-refs/",\ "packageDependencies": [\ @@ -6953,6 +7021,19 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-compose-refs-virtual-6b23e6e980/0/cache/@radix-ui-react-compose-refs-npm-1.1.2-f0371f8267-9a91f02130.zip/node_modules/@radix-ui/react-compose-refs/",\ + "packageDependencies": [\ + ["@radix-ui/react-compose-refs", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:e936aa93baa7c0261f756485b60a5523ed8da82252c9b4f6776e22e2a1c666edb6d92d6ef611105c2710902580c86be2f8a03d8d471374f481ae43be0e3a451c#npm:0.1.0", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-compose-refs-virtual-d0c19e085a/0/cache/@radix-ui-react-compose-refs-npm-0.1.0-a16c93a4d0-d1455577b2.zip/node_modules/@radix-ui/react-compose-refs/",\ "packageDependencies": [\ @@ -6983,6 +7064,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.2", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-context-npm-1.1.2-8b506f5df0-156088367d.zip/node_modules/@radix-ui/react-context/",\ + "packageDependencies": [\ + ["@radix-ui/react-context", "npm:1.1.2"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:47c4e77569e041bff79bd1ddad1bc7b570c90d41054db55f98d0e7c9ed6833246ad39cdb11895b53770ab2e980507584b01ba43d98bda3f9d03cff7bfa2cd915#npm:1.0.1", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-context-virtual-5b182e82c6/0/cache/@radix-ui-react-context-npm-1.0.1-c6d8414c9a-a02187a3ba.zip/node_modules/@radix-ui/react-context/",\ "packageDependencies": [\ @@ -6997,6 +7085,19 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-context-virtual-15920fce1c/0/cache/@radix-ui-react-context-npm-1.1.2-8b506f5df0-156088367d.zip/node_modules/@radix-ui/react-context/",\ + "packageDependencies": [\ + ["@radix-ui/react-context", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:e936aa93baa7c0261f756485b60a5523ed8da82252c9b4f6776e22e2a1c666edb6d92d6ef611105c2710902580c86be2f8a03d8d471374f481ae43be0e3a451c#npm:0.1.1", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-context-virtual-e43f7ee797/0/cache/@radix-ui-react-context-npm-0.1.1-f6f528ee12-6960ed93f3.zip/node_modules/@radix-ui/react-context/",\ "packageDependencies": [\ @@ -7126,6 +7227,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.1", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-direction-npm-1.1.1-43894c0d7e-8cc330285f.zip/node_modules/@radix-ui/react-direction/",\ + "packageDependencies": [\ + ["@radix-ui/react-direction", "npm:1.1.1"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:8473f355c418a88b1335f02ca575aaf21e571210b49dea3695d702f03556bb3f7fd6d03390454f8cb9aab89c810628052c7c1f94d9b6866d479d5b8526a6e7a6#npm:1.0.1", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-direction-virtual-b290058895/0/cache/@radix-ui-react-direction-npm-1.0.1-ab286e4395-5336a8b0d4.zip/node_modules/@radix-ui/react-direction/",\ "packageDependencies": [\ @@ -7139,6 +7247,19 @@ const RAW_RUNTIME_STATE = "react"\ ],\ "linkType": "HARD"\ + }],\ + ["virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.1", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-direction-virtual-4fbe4b68d2/0/cache/@radix-ui-react-direction-npm-1.1.1-43894c0d7e-8cc330285f.zip/node_modules/@radix-ui/react-direction/",\ + "packageDependencies": [\ + ["@radix-ui/react-direction", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@radix-ui/react-dismissable-layer", [\ @@ -7156,6 +7277,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.10", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-dismissable-layer-npm-1.1.10-9f18499422-e08733ee34.zip/node_modules/@radix-ui/react-dismissable-layer/",\ + "packageDependencies": [\ + ["@radix-ui/react-dismissable-layer", "npm:1.1.10"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:8f2eb0e9c6d6b27526e3c7af04e7acfcf679c55d25bea142e8983f1f5c757e02446b5cab6b3ceea5b123d229960fee61df0e4c03a8726979ac88cf4e90829f6c#npm:1.0.5", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-dismissable-layer-virtual-fe6da596d9/0/cache/@radix-ui-react-dismissable-layer-npm-1.0.5-fbc4b71169-f1626d69bb.zip/node_modules/@radix-ui/react-dismissable-layer/",\ "packageDependencies": [\ @@ -7202,6 +7330,28 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.10", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-dismissable-layer-virtual-c01b40e32f/0/cache/@radix-ui-react-dismissable-layer-npm-1.1.10-9f18499422-e08733ee34.zip/node_modules/@radix-ui/react-dismissable-layer/",\ + "packageDependencies": [\ + ["@radix-ui/react-dismissable-layer", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.10"],\ + ["@radix-ui/primitive", "npm:1.1.2"],\ + ["@radix-ui/react-compose-refs", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@radix-ui/react-primitive", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:2.1.3"],\ + ["@radix-ui/react-use-callback-ref", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.1"],\ + ["@radix-ui/react-use-escape-keydown", "virtual:c01b40e32fad46239374527e4909c9faae5939b7cf0fbc9612a6f16fb080ed08d06caf35f0527a829ee862877495480fedb4a73751bff5d0f6633731f0bbb318#npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["@types/react-dom", "npm:16.9.8"],\ + ["react", "npm:17.0.2"],\ + ["react-dom", "virtual:455cc1315669a6e622038e6093381f8d95ab8bb473af09abaaf7e72fce2d6a9ff2602fe53215abe1948a8259d689b4411a17531ea4acc5522a659c642ee7696d#npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react",\ + "react-dom",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:e936aa93baa7c0261f756485b60a5523ed8da82252c9b4f6776e22e2a1c666edb6d92d6ef611105c2710902580c86be2f8a03d8d471374f481ae43be0e3a451c#npm:0.1.5", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-dismissable-layer-virtual-51ef7727df/0/cache/@radix-ui-react-dismissable-layer-npm-0.1.5-c0006f242f-ad75eb6323.zip/node_modules/@radix-ui/react-dismissable-layer/",\ "packageDependencies": [\ @@ -7231,6 +7381,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:2.1.15", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-dropdown-menu-npm-2.1.15-e66d5c78e9-1300aa871d.zip/node_modules/@radix-ui/react-dropdown-menu/",\ + "packageDependencies": [\ + ["@radix-ui/react-dropdown-menu", "npm:2.1.15"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:455cc1315669a6e622038e6093381f8d95ab8bb473af09abaaf7e72fce2d6a9ff2602fe53215abe1948a8259d689b4411a17531ea4acc5522a659c642ee7696d#npm:2.0.6", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-dropdown-menu-virtual-4d2555f638/0/cache/@radix-ui-react-dropdown-menu-npm-2.0.6-a8eb9dbbc7-efa0728a25.zip/node_modules/@radix-ui/react-dropdown-menu/",\ "packageDependencies": [\ @@ -7255,6 +7412,30 @@ const RAW_RUNTIME_STATE = "react"\ ],\ "linkType": "HARD"\ + }],\ + ["virtual:b7c775051d99785ec73273707ec685f06b84c737b39cc023ebc60bda25254288f27e86643fb15b7a00bd8a6d76cc119d4628443d858e76eb62af2718d7eb18cf#npm:2.1.15", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-dropdown-menu-virtual-7a7ce5bfc3/0/cache/@radix-ui-react-dropdown-menu-npm-2.1.15-e66d5c78e9-1300aa871d.zip/node_modules/@radix-ui/react-dropdown-menu/",\ + "packageDependencies": [\ + ["@radix-ui/react-dropdown-menu", "virtual:b7c775051d99785ec73273707ec685f06b84c737b39cc023ebc60bda25254288f27e86643fb15b7a00bd8a6d76cc119d4628443d858e76eb62af2718d7eb18cf#npm:2.1.15"],\ + ["@radix-ui/primitive", "npm:1.1.2"],\ + ["@radix-ui/react-compose-refs", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@radix-ui/react-context", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@radix-ui/react-id", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.1"],\ + ["@radix-ui/react-menu", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:2.1.15"],\ + ["@radix-ui/react-primitive", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:2.1.3"],\ + ["@radix-ui/react-use-controllable-state", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.2.2"],\ + ["@types/react", "npm:17.0.2"],\ + ["@types/react-dom", "npm:16.9.8"],\ + ["react", "npm:17.0.2"],\ + ["react-dom", "virtual:455cc1315669a6e622038e6093381f8d95ab8bb473af09abaaf7e72fce2d6a9ff2602fe53215abe1948a8259d689b4411a17531ea4acc5522a659c642ee7696d#npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react",\ + "react-dom",\ + "react"\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@radix-ui/react-focus-guards", [\ @@ -7272,6 +7453,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.2", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-focus-guards-npm-1.1.2-f716f8ce5a-618658e2b9.zip/node_modules/@radix-ui/react-focus-guards/",\ + "packageDependencies": [\ + ["@radix-ui/react-focus-guards", "npm:1.1.2"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:8f2eb0e9c6d6b27526e3c7af04e7acfcf679c55d25bea142e8983f1f5c757e02446b5cab6b3ceea5b123d229960fee61df0e4c03a8726979ac88cf4e90829f6c#npm:1.0.1", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-focus-guards-virtual-5935b7dc86/0/cache/@radix-ui-react-focus-guards-npm-1.0.1-415ba52867-1f8ca8f83b.zip/node_modules/@radix-ui/react-focus-guards/",\ "packageDependencies": [\ @@ -7286,6 +7474,19 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.2", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-focus-guards-virtual-fddfcb0f85/0/cache/@radix-ui-react-focus-guards-npm-1.1.2-f716f8ce5a-618658e2b9.zip/node_modules/@radix-ui/react-focus-guards/",\ + "packageDependencies": [\ + ["@radix-ui/react-focus-guards", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.2"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:e936aa93baa7c0261f756485b60a5523ed8da82252c9b4f6776e22e2a1c666edb6d92d6ef611105c2710902580c86be2f8a03d8d471374f481ae43be0e3a451c#npm:0.1.0", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-focus-guards-virtual-e0f63ad6f8/0/cache/@radix-ui-react-focus-guards-npm-0.1.0-7ff2b5df5f-d199462ecf.zip/node_modules/@radix-ui/react-focus-guards/",\ "packageDependencies": [\ @@ -7316,6 +7517,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.7", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-focus-scope-npm-1.1.7-3d2d275c11-2a7cd00e39.zip/node_modules/@radix-ui/react-focus-scope/",\ + "packageDependencies": [\ + ["@radix-ui/react-focus-scope", "npm:1.1.7"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:8f2eb0e9c6d6b27526e3c7af04e7acfcf679c55d25bea142e8983f1f5c757e02446b5cab6b3ceea5b123d229960fee61df0e4c03a8726979ac88cf4e90829f6c#npm:1.0.4", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-focus-scope-virtual-e8d48e35c0/0/cache/@radix-ui-react-focus-scope-npm-1.0.4-7b881d2f7e-3590e74c6b.zip/node_modules/@radix-ui/react-focus-scope/",\ "packageDependencies": [\ @@ -7358,6 +7566,26 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.7", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-focus-scope-virtual-08506882aa/0/cache/@radix-ui-react-focus-scope-npm-1.1.7-3d2d275c11-2a7cd00e39.zip/node_modules/@radix-ui/react-focus-scope/",\ + "packageDependencies": [\ + ["@radix-ui/react-focus-scope", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.7"],\ + ["@radix-ui/react-compose-refs", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@radix-ui/react-primitive", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:2.1.3"],\ + ["@radix-ui/react-use-callback-ref", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["@types/react-dom", "npm:16.9.8"],\ + ["react", "npm:17.0.2"],\ + ["react-dom", "virtual:455cc1315669a6e622038e6093381f8d95ab8bb473af09abaaf7e72fce2d6a9ff2602fe53215abe1948a8259d689b4411a17531ea4acc5522a659c642ee7696d#npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react",\ + "react-dom",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:e936aa93baa7c0261f756485b60a5523ed8da82252c9b4f6776e22e2a1c666edb6d92d6ef611105c2710902580c86be2f8a03d8d471374f481ae43be0e3a451c#npm:0.1.4", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-focus-scope-virtual-08154dfe62/0/cache/@radix-ui-react-focus-scope-npm-0.1.4-c7848ccda0-8753ec9aa0.zip/node_modules/@radix-ui/react-focus-scope/",\ "packageDependencies": [\ @@ -7391,6 +7619,27 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.1", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-id-npm-1.1.1-d2c71a3e42-8d68e20077.zip/node_modules/@radix-ui/react-id/",\ + "packageDependencies": [\ + ["@radix-ui/react-id", "npm:1.1.1"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.1", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-id-virtual-150a052be0/0/cache/@radix-ui-react-id-npm-1.1.1-d2c71a3e42-8d68e20077.zip/node_modules/@radix-ui/react-id/",\ + "packageDependencies": [\ + ["@radix-ui/react-id", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.1"],\ + ["@radix-ui/react-use-layout-effect", "virtual:150a052be01d70b4313ae2f453584d91f26d6d2247563f119af7d630a81d01637f0bfb65a997d8e72445a92edb95eac10b4b591400b3fd952e26ff957a5bc703#npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:8f2eb0e9c6d6b27526e3c7af04e7acfcf679c55d25bea142e8983f1f5c757e02446b5cab6b3ceea5b123d229960fee61df0e4c03a8726979ac88cf4e90829f6c#npm:1.0.1", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-id-virtual-b7641780f0/0/cache/@radix-ui-react-id-npm-1.0.1-d2f01e7fd5-446a453d79.zip/node_modules/@radix-ui/react-id/",\ "packageDependencies": [\ @@ -7430,6 +7679,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:2.1.15", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-menu-npm-2.1.15-05aa64453c-7876c0892b.zip/node_modules/@radix-ui/react-menu/",\ + "packageDependencies": [\ + ["@radix-ui/react-menu", "npm:2.1.15"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:0c4481e19042493101599ce30d3873bb80bb98f3789571d33512f2c3220b77c2d6263e6d07f08e9b023252414d2986a04654073cdf9d8f2f0461f7ccaa4c990d#npm:2.0.6", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-menu-virtual-b0619b0636/0/cache/@radix-ui-react-menu-npm-2.0.6-d36ea58acb-8e8c41a46f.zip/node_modules/@radix-ui/react-menu/",\ "packageDependencies": [\ @@ -7501,6 +7757,41 @@ const RAW_RUNTIME_STATE = "react"\ ],\ "linkType": "HARD"\ + }],\ + ["virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:2.1.15", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-menu-virtual-df6d22e638/0/cache/@radix-ui-react-menu-npm-2.1.15-05aa64453c-7876c0892b.zip/node_modules/@radix-ui/react-menu/",\ + "packageDependencies": [\ + ["@radix-ui/react-menu", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:2.1.15"],\ + ["@radix-ui/primitive", "npm:1.1.2"],\ + ["@radix-ui/react-collection", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.7"],\ + ["@radix-ui/react-compose-refs", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@radix-ui/react-context", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@radix-ui/react-direction", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.1"],\ + ["@radix-ui/react-dismissable-layer", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.10"],\ + ["@radix-ui/react-focus-guards", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.2"],\ + ["@radix-ui/react-focus-scope", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.7"],\ + ["@radix-ui/react-id", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.1"],\ + ["@radix-ui/react-popper", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.2.7"],\ + ["@radix-ui/react-portal", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.9"],\ + ["@radix-ui/react-presence", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.4"],\ + ["@radix-ui/react-primitive", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:2.1.3"],\ + ["@radix-ui/react-roving-focus", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.10"],\ + ["@radix-ui/react-slot", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.2.3"],\ + ["@radix-ui/react-use-callback-ref", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["@types/react-dom", "npm:16.9.8"],\ + ["aria-hidden", "npm:1.2.6"],\ + ["react", "npm:17.0.2"],\ + ["react-dom", "virtual:455cc1315669a6e622038e6093381f8d95ab8bb473af09abaaf7e72fce2d6a9ff2602fe53215abe1948a8259d689b4411a17531ea4acc5522a659c642ee7696d#npm:17.0.2"],\ + ["react-remove-scroll", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:2.7.1"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react",\ + "react-dom",\ + "react"\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@radix-ui/react-popover", [\ @@ -7559,6 +7850,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.2.7", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-popper-npm-1.2.7-00fc4d5ec7-1d1bcb679f.zip/node_modules/@radix-ui/react-popper/",\ + "packageDependencies": [\ + ["@radix-ui/react-popper", "npm:1.2.7"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:8473f355c418a88b1335f02ca575aaf21e571210b49dea3695d702f03556bb3f7fd6d03390454f8cb9aab89c810628052c7c1f94d9b6866d479d5b8526a6e7a6#npm:1.1.3", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-popper-virtual-b1393fa624/0/cache/@radix-ui-react-popper-npm-1.1.3-d7804f4c70-1f70ca09b6.zip/node_modules/@radix-ui/react-popper/",\ "packageDependencies": [\ @@ -7615,6 +7913,33 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.2.7", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-popper-virtual-47a13e63d3/0/cache/@radix-ui-react-popper-npm-1.2.7-00fc4d5ec7-1d1bcb679f.zip/node_modules/@radix-ui/react-popper/",\ + "packageDependencies": [\ + ["@radix-ui/react-popper", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.2.7"],\ + ["@floating-ui/react-dom", "virtual:1fdc94c5df04c255c0b5de85157cf84a4ba430a2f014b72559f72becfd0741fd1fe73e536ed02c8ee5b0b60c458bd024623974e87957215a245e478dae0528bf#npm:2.0.2"],\ + ["@radix-ui/react-arrow", "virtual:47a13e63d321cfce00f9bd3153f9ef0ff7b7abdf04b3fcaaf0ea3255ea587812c08b9b4e7ea1085f6e58dd55c26111a5f611bb770f540a49a4604edb212c8372#npm:1.1.7"],\ + ["@radix-ui/react-compose-refs", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@radix-ui/react-context", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@radix-ui/react-primitive", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:2.1.3"],\ + ["@radix-ui/react-use-callback-ref", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.1"],\ + ["@radix-ui/react-use-layout-effect", "virtual:150a052be01d70b4313ae2f453584d91f26d6d2247563f119af7d630a81d01637f0bfb65a997d8e72445a92edb95eac10b4b591400b3fd952e26ff957a5bc703#npm:1.1.1"],\ + ["@radix-ui/react-use-rect", "virtual:47a13e63d321cfce00f9bd3153f9ef0ff7b7abdf04b3fcaaf0ea3255ea587812c08b9b4e7ea1085f6e58dd55c26111a5f611bb770f540a49a4604edb212c8372#npm:1.1.1"],\ + ["@radix-ui/react-use-size", "virtual:47a13e63d321cfce00f9bd3153f9ef0ff7b7abdf04b3fcaaf0ea3255ea587812c08b9b4e7ea1085f6e58dd55c26111a5f611bb770f540a49a4604edb212c8372#npm:1.1.1"],\ + ["@radix-ui/rect", "npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["@types/react-dom", "npm:16.9.8"],\ + ["react", "npm:17.0.2"],\ + ["react-dom", "virtual:455cc1315669a6e622038e6093381f8d95ab8bb473af09abaaf7e72fce2d6a9ff2602fe53215abe1948a8259d689b4411a17531ea4acc5522a659c642ee7696d#npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react",\ + "react-dom",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:e936aa93baa7c0261f756485b60a5523ed8da82252c9b4f6776e22e2a1c666edb6d92d6ef611105c2710902580c86be2f8a03d8d471374f481ae43be0e3a451c#npm:0.1.4", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-popper-virtual-e7c8af8e3c/0/cache/@radix-ui-react-popper-npm-0.1.4-babf34c41f-c591e04bc4.zip/node_modules/@radix-ui/react-popper/",\ "packageDependencies": [\ @@ -7653,6 +7978,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.9", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-portal-npm-1.1.9-8d4bfbd782-bd6be39bf0.zip/node_modules/@radix-ui/react-portal/",\ + "packageDependencies": [\ + ["@radix-ui/react-portal", "npm:1.1.9"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:8f2eb0e9c6d6b27526e3c7af04e7acfcf679c55d25bea142e8983f1f5c757e02446b5cab6b3ceea5b123d229960fee61df0e4c03a8726979ac88cf4e90829f6c#npm:1.0.4", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-portal-virtual-6801f5063b/0/cache/@radix-ui-react-portal-npm-1.0.4-e4c93f6e90-c4cf35e2f2.zip/node_modules/@radix-ui/react-portal/",\ "packageDependencies": [\ @@ -7691,6 +8023,25 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.9", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-portal-virtual-c5c73f9c30/0/cache/@radix-ui-react-portal-npm-1.1.9-8d4bfbd782-bd6be39bf0.zip/node_modules/@radix-ui/react-portal/",\ + "packageDependencies": [\ + ["@radix-ui/react-portal", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.9"],\ + ["@radix-ui/react-primitive", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:2.1.3"],\ + ["@radix-ui/react-use-layout-effect", "virtual:150a052be01d70b4313ae2f453584d91f26d6d2247563f119af7d630a81d01637f0bfb65a997d8e72445a92edb95eac10b4b591400b3fd952e26ff957a5bc703#npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["@types/react-dom", "npm:16.9.8"],\ + ["react", "npm:17.0.2"],\ + ["react-dom", "virtual:455cc1315669a6e622038e6093381f8d95ab8bb473af09abaaf7e72fce2d6a9ff2602fe53215abe1948a8259d689b4411a17531ea4acc5522a659c642ee7696d#npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react",\ + "react-dom",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:e936aa93baa7c0261f756485b60a5523ed8da82252c9b4f6776e22e2a1c666edb6d92d6ef611105c2710902580c86be2f8a03d8d471374f481ae43be0e3a451c#npm:0.1.4", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-portal-virtual-7d9d095a37/0/cache/@radix-ui-react-portal-npm-0.1.4-5d15e94604-d841636dd1.zip/node_modules/@radix-ui/react-portal/",\ "packageDependencies": [\ @@ -7727,6 +8078,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.4", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-presence-npm-1.1.4-8b5ae8121c-ba01f385f6.zip/node_modules/@radix-ui/react-presence/",\ + "packageDependencies": [\ + ["@radix-ui/react-presence", "npm:1.1.4"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:8f2eb0e9c6d6b27526e3c7af04e7acfcf679c55d25bea142e8983f1f5c757e02446b5cab6b3ceea5b123d229960fee61df0e4c03a8726979ac88cf4e90829f6c#npm:1.0.1", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-presence-virtual-abc0744513/0/cache/@radix-ui-react-presence-npm-1.0.1-2057bd46b4-406f0b5a54.zip/node_modules/@radix-ui/react-presence/",\ "packageDependencies": [\ @@ -7767,6 +8125,25 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.4", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-presence-virtual-aa425ac9e0/0/cache/@radix-ui-react-presence-npm-1.1.4-8b5ae8121c-ba01f385f6.zip/node_modules/@radix-ui/react-presence/",\ + "packageDependencies": [\ + ["@radix-ui/react-presence", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.4"],\ + ["@radix-ui/react-compose-refs", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@radix-ui/react-use-layout-effect", "virtual:150a052be01d70b4313ae2f453584d91f26d6d2247563f119af7d630a81d01637f0bfb65a997d8e72445a92edb95eac10b4b591400b3fd952e26ff957a5bc703#npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["@types/react-dom", "npm:16.9.8"],\ + ["react", "npm:17.0.2"],\ + ["react-dom", "virtual:455cc1315669a6e622038e6093381f8d95ab8bb473af09abaaf7e72fce2d6a9ff2602fe53215abe1948a8259d689b4411a17531ea4acc5522a659c642ee7696d#npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react",\ + "react-dom",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:e936aa93baa7c0261f756485b60a5523ed8da82252c9b4f6776e22e2a1c666edb6d92d6ef611105c2710902580c86be2f8a03d8d471374f481ae43be0e3a451c#npm:0.1.2", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-presence-virtual-0c08805d39/0/cache/@radix-ui-react-presence-npm-0.1.2-52b34dc77a-7faad92b1c.zip/node_modules/@radix-ui/react-presence/",\ "packageDependencies": [\ @@ -7799,6 +8176,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:2.1.3", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-primitive-npm-2.1.3-6080896851-1dbbf932a3.zip/node_modules/@radix-ui/react-primitive/",\ + "packageDependencies": [\ + ["@radix-ui/react-primitive", "npm:2.1.3"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:0c4481e19042493101599ce30d3873bb80bb98f3789571d33512f2c3220b77c2d6263e6d07f08e9b023252414d2986a04654073cdf9d8f2f0461f7ccaa4c990d#npm:1.0.3", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-primitive-virtual-c4893accda/0/cache/@radix-ui-react-primitive-npm-1.0.3-1983a5adc0-bedb934ac0.zip/node_modules/@radix-ui/react-primitive/",\ "packageDependencies": [\ @@ -7837,6 +8221,24 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:2.1.3", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-primitive-virtual-27ce556eb0/0/cache/@radix-ui-react-primitive-npm-2.1.3-6080896851-1dbbf932a3.zip/node_modules/@radix-ui/react-primitive/",\ + "packageDependencies": [\ + ["@radix-ui/react-primitive", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:2.1.3"],\ + ["@radix-ui/react-slot", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.2.3"],\ + ["@types/react", "npm:17.0.2"],\ + ["@types/react-dom", "npm:16.9.8"],\ + ["react", "npm:17.0.2"],\ + ["react-dom", "virtual:455cc1315669a6e622038e6093381f8d95ab8bb473af09abaaf7e72fce2d6a9ff2602fe53215abe1948a8259d689b4411a17531ea4acc5522a659c642ee7696d#npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react",\ + "react-dom",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:e936aa93baa7c0261f756485b60a5523ed8da82252c9b4f6776e22e2a1c666edb6d92d6ef611105c2710902580c86be2f8a03d8d471374f481ae43be0e3a451c#npm:0.1.4", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-primitive-virtual-900a6b33bc/0/cache/@radix-ui-react-primitive-npm-0.1.4-115fd5c8b4-5341ce08ce.zip/node_modules/@radix-ui/react-primitive/",\ "packageDependencies": [\ @@ -7861,6 +8263,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.10", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-roving-focus-npm-1.1.10-052edc0141-65241d84cb.zip/node_modules/@radix-ui/react-roving-focus/",\ + "packageDependencies": [\ + ["@radix-ui/react-roving-focus", "npm:1.1.10"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:8473f355c418a88b1335f02ca575aaf21e571210b49dea3695d702f03556bb3f7fd6d03390454f8cb9aab89c810628052c7c1f94d9b6866d479d5b8526a6e7a6#npm:1.0.4", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-roving-focus-virtual-5225f07346/0/cache/@radix-ui-react-roving-focus-npm-1.0.4-7106f3083a-a23ffb1e3e.zip/node_modules/@radix-ui/react-roving-focus/",\ "packageDependencies": [\ @@ -7914,6 +8323,32 @@ const RAW_RUNTIME_STATE = "react"\ ],\ "linkType": "HARD"\ + }],\ + ["virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.10", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-roving-focus-virtual-752b851c2f/0/cache/@radix-ui-react-roving-focus-npm-1.1.10-052edc0141-65241d84cb.zip/node_modules/@radix-ui/react-roving-focus/",\ + "packageDependencies": [\ + ["@radix-ui/react-roving-focus", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.10"],\ + ["@radix-ui/primitive", "npm:1.1.2"],\ + ["@radix-ui/react-collection", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.7"],\ + ["@radix-ui/react-compose-refs", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@radix-ui/react-context", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@radix-ui/react-direction", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.1"],\ + ["@radix-ui/react-id", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.1"],\ + ["@radix-ui/react-primitive", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:2.1.3"],\ + ["@radix-ui/react-use-callback-ref", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.1"],\ + ["@radix-ui/react-use-controllable-state", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.2.2"],\ + ["@types/react", "npm:17.0.2"],\ + ["@types/react-dom", "npm:16.9.8"],\ + ["react", "npm:17.0.2"],\ + ["react-dom", "virtual:455cc1315669a6e622038e6093381f8d95ab8bb473af09abaaf7e72fce2d6a9ff2602fe53215abe1948a8259d689b4411a17531ea4acc5522a659c642ee7696d#npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react-dom",\ + "@types/react",\ + "react-dom",\ + "react"\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@radix-ui/react-slot", [\ @@ -7931,6 +8366,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.2.3", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-slot-npm-1.2.3-6e45e6d89b-fe484c2741.zip/node_modules/@radix-ui/react-slot/",\ + "packageDependencies": [\ + ["@radix-ui/react-slot", "npm:1.2.3"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:47c4e77569e041bff79bd1ddad1bc7b570c90d41054db55f98d0e7c9ed6833246ad39cdb11895b53770ab2e980507584b01ba43d98bda3f9d03cff7bfa2cd915#npm:1.0.2", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-slot-virtual-029dd4b687/0/cache/@radix-ui-react-slot-npm-1.0.2-b9078e9a0b-734866561e.zip/node_modules/@radix-ui/react-slot/",\ "packageDependencies": [\ @@ -7960,6 +8402,20 @@ const RAW_RUNTIME_STATE = "react"\ ],\ "linkType": "HARD"\ + }],\ + ["virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.2.3", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-slot-virtual-4185852c4e/0/cache/@radix-ui-react-slot-npm-1.2.3-6e45e6d89b-fe484c2741.zip/node_modules/@radix-ui/react-slot/",\ + "packageDependencies": [\ + ["@radix-ui/react-slot", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.2.3"],\ + ["@radix-ui/react-compose-refs", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.1.2"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@radix-ui/react-switch", [\ @@ -8035,6 +8491,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.1", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-use-callback-ref-npm-1.1.1-d0f2aaabce-cde8c40f1d.zip/node_modules/@radix-ui/react-use-callback-ref/",\ + "packageDependencies": [\ + ["@radix-ui/react-use-callback-ref", "npm:1.1.1"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:51ef7727df9f6f8db0e0c7c74658ca729889d7d4bada58c7749df90ef4096c4ffbe103a300acb5679a6ff4d0696859396adda03497d490e1a955e83021fa72d5#npm:0.1.0", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-callback-ref-virtual-8c245f279e/0/cache/@radix-ui-react-use-callback-ref-npm-0.1.0-838ec38d13-5356971123.zip/node_modules/@radix-ui/react-use-callback-ref/",\ "packageDependencies": [\ @@ -8049,6 +8512,19 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.1", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-callback-ref-virtual-5aec7160eb/0/cache/@radix-ui-react-use-callback-ref-npm-1.1.1-d0f2aaabce-cde8c40f1d.zip/node_modules/@radix-ui/react-use-callback-ref/",\ + "packageDependencies": [\ + ["@radix-ui/react-use-callback-ref", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:fe6da596d945ceea9528cd5eb8918986b9de845171b3375d10bacc663af081ca1009fc74b8184697f3e63c3f5b8c8c495632d70b6dfcb88b21086590ace16602#npm:1.0.1", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-callback-ref-virtual-0e743add79/0/cache/@radix-ui-react-use-callback-ref-npm-1.0.1-e521cb00a3-b9fd39911c.zip/node_modules/@radix-ui/react-use-callback-ref/",\ "packageDependencies": [\ @@ -8079,6 +8555,28 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.2.2", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-use-controllable-state-npm-1.2.2-ddb427f3a3-a100bff3dd.zip/node_modules/@radix-ui/react-use-controllable-state/",\ + "packageDependencies": [\ + ["@radix-ui/react-use-controllable-state", "npm:1.2.2"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.2.2", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-controllable-state-virtual-01ea390c77/0/cache/@radix-ui-react-use-controllable-state-npm-1.2.2-ddb427f3a3-a100bff3dd.zip/node_modules/@radix-ui/react-use-controllable-state/",\ + "packageDependencies": [\ + ["@radix-ui/react-use-controllable-state", "virtual:7a7ce5bfc33133d4ec7e1ed34608c1f637e4c8b760e8d14d08d145c3fe161f31b53292a37b901bcebb4ffd7308092123e170a8ddc397bf5d1344b5618f6923b4#npm:1.2.2"],\ + ["@radix-ui/react-use-effect-event", "virtual:01ea390c773a9cadeb7a20f7602fb66462399e385b01222ab32feabf0e57fea252a16547015570f2fe0da2ace3e4457d9f10dc7755f821fcb12438ecbf0eecc8#npm:0.0.2"],\ + ["@radix-ui/react-use-layout-effect", "virtual:150a052be01d70b4313ae2f453584d91f26d6d2247563f119af7d630a81d01637f0bfb65a997d8e72445a92edb95eac10b4b591400b3fd952e26ff957a5bc703#npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:8f2eb0e9c6d6b27526e3c7af04e7acfcf679c55d25bea142e8983f1f5c757e02446b5cab6b3ceea5b123d229960fee61df0e4c03a8726979ac88cf4e90829f6c#npm:1.0.1", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-controllable-state-virtual-b175ec689a/0/cache/@radix-ui-react-use-controllable-state-npm-1.0.1-cbe6fcf1d7-dee2be1937.zip/node_modules/@radix-ui/react-use-controllable-state/",\ "packageDependencies": [\ @@ -8110,6 +8608,29 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@radix-ui/react-use-effect-event", [\ + ["npm:0.0.2", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-use-effect-event-npm-0.0.2-578346f568-5a1950a30a.zip/node_modules/@radix-ui/react-use-effect-event/",\ + "packageDependencies": [\ + ["@radix-ui/react-use-effect-event", "npm:0.0.2"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:01ea390c773a9cadeb7a20f7602fb66462399e385b01222ab32feabf0e57fea252a16547015570f2fe0da2ace3e4457d9f10dc7755f821fcb12438ecbf0eecc8#npm:0.0.2", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-effect-event-virtual-ca55b89957/0/cache/@radix-ui-react-use-effect-event-npm-0.0.2-578346f568-5a1950a30a.zip/node_modules/@radix-ui/react-use-effect-event/",\ + "packageDependencies": [\ + ["@radix-ui/react-use-effect-event", "virtual:01ea390c773a9cadeb7a20f7602fb66462399e385b01222ab32feabf0e57fea252a16547015570f2fe0da2ace3e4457d9f10dc7755f821fcb12438ecbf0eecc8#npm:0.0.2"],\ + ["@radix-ui/react-use-layout-effect", "virtual:150a052be01d70b4313ae2f453584d91f26d6d2247563f119af7d630a81d01637f0bfb65a997d8e72445a92edb95eac10b4b591400b3fd952e26ff957a5bc703#npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@radix-ui/react-use-escape-keydown", [\ ["npm:0.1.0", {\ "packageLocation": "./.yarn/cache/@radix-ui-react-use-escape-keydown-npm-0.1.0-af4910b082-b92769ecf4.zip/node_modules/@radix-ui/react-use-escape-keydown/",\ @@ -8125,6 +8646,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.1", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-use-escape-keydown-npm-1.1.1-dea48a407a-0eb0756c2c.zip/node_modules/@radix-ui/react-use-escape-keydown/",\ + "packageDependencies": [\ + ["@radix-ui/react-use-escape-keydown", "npm:1.1.1"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:51ef7727df9f6f8db0e0c7c74658ca729889d7d4bada58c7749df90ef4096c4ffbe103a300acb5679a6ff4d0696859396adda03497d490e1a955e83021fa72d5#npm:0.1.0", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-escape-keydown-virtual-80fde765fc/0/cache/@radix-ui-react-use-escape-keydown-npm-0.1.0-af4910b082-b92769ecf4.zip/node_modules/@radix-ui/react-use-escape-keydown/",\ "packageDependencies": [\ @@ -8140,6 +8668,20 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["virtual:c01b40e32fad46239374527e4909c9faae5939b7cf0fbc9612a6f16fb080ed08d06caf35f0527a829ee862877495480fedb4a73751bff5d0f6633731f0bbb318#npm:1.1.1", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-escape-keydown-virtual-87aa3ea62e/0/cache/@radix-ui-react-use-escape-keydown-npm-1.1.1-dea48a407a-0eb0756c2c.zip/node_modules/@radix-ui/react-use-escape-keydown/",\ + "packageDependencies": [\ + ["@radix-ui/react-use-escape-keydown", "virtual:c01b40e32fad46239374527e4909c9faae5939b7cf0fbc9612a6f16fb080ed08d06caf35f0527a829ee862877495480fedb4a73751bff5d0f6633731f0bbb318#npm:1.1.1"],\ + ["@radix-ui/react-use-callback-ref", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:fe6da596d945ceea9528cd5eb8918986b9de845171b3375d10bacc663af081ca1009fc74b8184697f3e63c3f5b8c8c495632d70b6dfcb88b21086590ace16602#npm:1.0.3", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-escape-keydown-virtual-556b73db11/0/cache/@radix-ui-react-use-escape-keydown-npm-1.0.3-2455d95aa3-c6ed0d9ce7.zip/node_modules/@radix-ui/react-use-escape-keydown/",\ "packageDependencies": [\ @@ -8171,6 +8713,26 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.1", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-use-layout-effect-npm-1.1.1-e3c7fd61a2-bad2ba4f20.zip/node_modules/@radix-ui/react-use-layout-effect/",\ + "packageDependencies": [\ + ["@radix-ui/react-use-layout-effect", "npm:1.1.1"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:150a052be01d70b4313ae2f453584d91f26d6d2247563f119af7d630a81d01637f0bfb65a997d8e72445a92edb95eac10b4b591400b3fd952e26ff957a5bc703#npm:1.1.1", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-layout-effect-virtual-24fec4489e/0/cache/@radix-ui-react-use-layout-effect-npm-1.1.1-e3c7fd61a2-bad2ba4f20.zip/node_modules/@radix-ui/react-use-layout-effect/",\ + "packageDependencies": [\ + ["@radix-ui/react-use-layout-effect", "virtual:150a052be01d70b4313ae2f453584d91f26d6d2247563f119af7d630a81d01637f0bfb65a997d8e72445a92edb95eac10b4b591400b3fd952e26ff957a5bc703#npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:4969176a3eac3f3aa744ae9f1799e3834fa8243dc3229b28aeb1135d90d72b1f9b3619c00a947187a273a7b60f81e6067c59d571fa7e0d5476500496cff84f07#npm:0.1.0", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-layout-effect-virtual-27867fbc91/0/cache/@radix-ui-react-use-layout-effect-npm-0.1.0-d80d7efedb-d8be1f9770.zip/node_modules/@radix-ui/react-use-layout-effect/",\ "packageDependencies": [\ @@ -8238,6 +8800,27 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.1", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-use-rect-npm-1.1.1-a65c790d9f-116461bebc.zip/node_modules/@radix-ui/react-use-rect/",\ + "packageDependencies": [\ + ["@radix-ui/react-use-rect", "npm:1.1.1"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:47a13e63d321cfce00f9bd3153f9ef0ff7b7abdf04b3fcaaf0ea3255ea587812c08b9b4e7ea1085f6e58dd55c26111a5f611bb770f540a49a4604edb212c8372#npm:1.1.1", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-rect-virtual-e13a39a592/0/cache/@radix-ui-react-use-rect-npm-1.1.1-a65c790d9f-116461bebc.zip/node_modules/@radix-ui/react-use-rect/",\ + "packageDependencies": [\ + ["@radix-ui/react-use-rect", "virtual:47a13e63d321cfce00f9bd3153f9ef0ff7b7abdf04b3fcaaf0ea3255ea587812c08b9b4e7ea1085f6e58dd55c26111a5f611bb770f540a49a4604edb212c8372#npm:1.1.1"],\ + ["@radix-ui/rect", "npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:b1393fa62437828464cae528959a7d7b857ebe5a4e6e9b38ac176c0822c8dac6882c71ed9e1630c2fa130c7abc56da61fd4b275ba33de5b860a4d0ef2c5587cd#npm:1.0.1", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-rect-virtual-ea916e6b63/0/cache/@radix-ui-react-use-rect-npm-1.0.1-ea3f7a385f-433f07e61e.zip/node_modules/@radix-ui/react-use-rect/",\ "packageDependencies": [\ @@ -8284,6 +8867,27 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.1", {\ + "packageLocation": "./.yarn/cache/@radix-ui-react-use-size-npm-1.1.1-c1e9d2fef8-64e61f65fe.zip/node_modules/@radix-ui/react-use-size/",\ + "packageDependencies": [\ + ["@radix-ui/react-use-size", "npm:1.1.1"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:47a13e63d321cfce00f9bd3153f9ef0ff7b7abdf04b3fcaaf0ea3255ea587812c08b9b4e7ea1085f6e58dd55c26111a5f611bb770f540a49a4604edb212c8372#npm:1.1.1", {\ + "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-size-virtual-ac8236a442/0/cache/@radix-ui-react-use-size-npm-1.1.1-c1e9d2fef8-64e61f65fe.zip/node_modules/@radix-ui/react-use-size/",\ + "packageDependencies": [\ + ["@radix-ui/react-use-size", "virtual:47a13e63d321cfce00f9bd3153f9ef0ff7b7abdf04b3fcaaf0ea3255ea587812c08b9b4e7ea1085f6e58dd55c26111a5f611bb770f540a49a4604edb212c8372#npm:1.1.1"],\ + ["@radix-ui/react-use-layout-effect", "virtual:150a052be01d70b4313ae2f453584d91f26d6d2247563f119af7d630a81d01637f0bfb65a997d8e72445a92edb95eac10b4b591400b3fd952e26ff957a5bc703#npm:1.1.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:b1393fa62437828464cae528959a7d7b857ebe5a4e6e9b38ac176c0822c8dac6882c71ed9e1630c2fa130c7abc56da61fd4b275ba33de5b860a4d0ef2c5587cd#npm:1.0.1", {\ "packageLocation": "./.yarn/__virtual__/@radix-ui-react-use-size-virtual-857340c345/0/cache/@radix-ui-react-use-size-npm-1.0.1-97c8358b35-6cc150ad1e.zip/node_modules/@radix-ui/react-use-size/",\ "packageDependencies": [\ @@ -8330,6 +8934,13 @@ const RAW_RUNTIME_STATE = ["@babel/runtime", "npm:7.23.1"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:1.1.1", {\ + "packageLocation": "./.yarn/cache/@radix-ui-rect-npm-1.1.1-8c5111b10d-b6c5eb7876.zip/node_modules/@radix-ui/rect/",\ + "packageDependencies": [\ + ["@radix-ui/rect", "npm:1.1.1"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@sideway/address", [\ @@ -13109,6 +13720,14 @@ const RAW_RUNTIME_STATE = ["tslib", "npm:2.6.2"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:1.2.6", {\ + "packageLocation": "./.yarn/cache/aria-hidden-npm-1.2.6-46a480bac7-1914e5a362.zip/node_modules/aria-hidden/",\ + "packageDependencies": [\ + ["aria-hidden", "npm:1.2.6"],\ + ["tslib", "npm:2.6.2"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["aria-query", [\ @@ -26169,6 +26788,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:2.7.1", {\ + "packageLocation": "./.yarn/cache/react-remove-scroll-npm-2.7.1-f317483ad9-5e571ba35b.zip/node_modules/react-remove-scroll/",\ + "packageDependencies": [\ + ["react-remove-scroll", "npm:2.7.1"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:8f2eb0e9c6d6b27526e3c7af04e7acfcf679c55d25bea142e8983f1f5c757e02446b5cab6b3ceea5b123d229960fee61df0e4c03a8726979ac88cf4e90829f6c#npm:2.5.5", {\ "packageLocation": "./.yarn/__virtual__/react-remove-scroll-virtual-2bbb419bcc/0/cache/react-remove-scroll-npm-2.5.5-87479a3637-f0646ac384.zip/node_modules/react-remove-scroll/",\ "packageDependencies": [\ @@ -26187,6 +26813,24 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "HARD"\ }],\ + ["virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:2.7.1", {\ + "packageLocation": "./.yarn/__virtual__/react-remove-scroll-virtual-ea40e58d54/0/cache/react-remove-scroll-npm-2.7.1-f317483ad9-5e571ba35b.zip/node_modules/react-remove-scroll/",\ + "packageDependencies": [\ + ["react-remove-scroll", "virtual:df6d22e6387c03f96d4cb8bdcb0b99f73839fd2a140afff640f447176ec8f0605f69cb775d8f9b55878bf5fcfaeea744cde87a338e8515315001c7bee75b4eeb#npm:2.7.1"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"],\ + ["react-remove-scroll-bar", "virtual:ea40e58d54d27b0a45b54ad579e666a4f36c206869d33d28f3dbe6c66397a18ef79ccaf891c2ca0e9187e4d5346a16cb6a62e7b3470dd99cc1cb5be1037a415b#npm:2.3.8"],\ + ["react-style-singleton", "virtual:ea40e58d54d27b0a45b54ad579e666a4f36c206869d33d28f3dbe6c66397a18ef79ccaf891c2ca0e9187e4d5346a16cb6a62e7b3470dd99cc1cb5be1037a415b#npm:2.2.3"],\ + ["tslib", "npm:2.6.2"],\ + ["use-callback-ref", "virtual:ea40e58d54d27b0a45b54ad579e666a4f36c206869d33d28f3dbe6c66397a18ef79ccaf891c2ca0e9187e4d5346a16cb6a62e7b3470dd99cc1cb5be1037a415b#npm:1.3.3"],\ + ["use-sidecar", "virtual:ea40e58d54d27b0a45b54ad579e666a4f36c206869d33d28f3dbe6c66397a18ef79ccaf891c2ca0e9187e4d5346a16cb6a62e7b3470dd99cc1cb5be1037a415b#npm:1.1.3"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ + }],\ ["virtual:e936aa93baa7c0261f756485b60a5523ed8da82252c9b4f6776e22e2a1c666edb6d92d6ef611105c2710902580c86be2f8a03d8d471374f481ae43be0e3a451c#npm:2.6.0", {\ "packageLocation": "./.yarn/__virtual__/react-remove-scroll-virtual-bc37596c74/0/cache/react-remove-scroll-npm-2.6.0-8b2203a174-9fac79e1c2.zip/node_modules/react-remove-scroll/",\ "packageDependencies": [\ @@ -26221,6 +26865,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:2.3.8", {\ + "packageLocation": "./.yarn/cache/react-remove-scroll-bar-npm-2.3.8-21a578f734-6c0f8cff98.zip/node_modules/react-remove-scroll-bar/",\ + "packageDependencies": [\ + ["react-remove-scroll-bar", "npm:2.3.8"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:2bbb419bcc79b25ce83ef1786234afad01c22b00e7984c57b0888d8c981ae152e1b9d75b70b910840d580c740ad55a2aba297ab95acc25e64e9dde321269dacd#npm:2.3.4", {\ "packageLocation": "./.yarn/__virtual__/react-remove-scroll-bar-virtual-d9dc3f445a/0/cache/react-remove-scroll-bar-npm-2.3.4-7d25bbed45-ac028b3ed1.zip/node_modules/react-remove-scroll-bar/",\ "packageDependencies": [\ @@ -26250,6 +26901,21 @@ const RAW_RUNTIME_STATE = "react"\ ],\ "linkType": "HARD"\ + }],\ + ["virtual:ea40e58d54d27b0a45b54ad579e666a4f36c206869d33d28f3dbe6c66397a18ef79ccaf891c2ca0e9187e4d5346a16cb6a62e7b3470dd99cc1cb5be1037a415b#npm:2.3.8", {\ + "packageLocation": "./.yarn/__virtual__/react-remove-scroll-bar-virtual-f953ab0bcc/0/cache/react-remove-scroll-bar-npm-2.3.8-21a578f734-6c0f8cff98.zip/node_modules/react-remove-scroll-bar/",\ + "packageDependencies": [\ + ["react-remove-scroll-bar", "virtual:ea40e58d54d27b0a45b54ad579e666a4f36c206869d33d28f3dbe6c66397a18ef79ccaf891c2ca0e9187e4d5346a16cb6a62e7b3470dd99cc1cb5be1037a415b#npm:2.3.8"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"],\ + ["react-style-singleton", "virtual:ea40e58d54d27b0a45b54ad579e666a4f36c206869d33d28f3dbe6c66397a18ef79ccaf891c2ca0e9187e4d5346a16cb6a62e7b3470dd99cc1cb5be1037a415b#npm:2.2.3"],\ + ["tslib", "npm:2.6.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["react-style-singleton", [\ @@ -26260,6 +26926,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:2.2.3", {\ + "packageLocation": "./.yarn/cache/react-style-singleton-npm-2.2.3-18f32c05f7-62498094ff.zip/node_modules/react-style-singleton/",\ + "packageDependencies": [\ + ["react-style-singleton", "npm:2.2.3"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:2bbb419bcc79b25ce83ef1786234afad01c22b00e7984c57b0888d8c981ae152e1b9d75b70b910840d580c740ad55a2aba297ab95acc25e64e9dde321269dacd#npm:2.2.1", {\ "packageLocation": "./.yarn/__virtual__/react-style-singleton-virtual-9a00bf89d3/0/cache/react-style-singleton-npm-2.2.1-e45b97b153-80c58fd6aa.zip/node_modules/react-style-singleton/",\ "packageDependencies": [\ @@ -26275,6 +26948,21 @@ const RAW_RUNTIME_STATE = "react"\ ],\ "linkType": "HARD"\ + }],\ + ["virtual:ea40e58d54d27b0a45b54ad579e666a4f36c206869d33d28f3dbe6c66397a18ef79ccaf891c2ca0e9187e4d5346a16cb6a62e7b3470dd99cc1cb5be1037a415b#npm:2.2.3", {\ + "packageLocation": "./.yarn/__virtual__/react-style-singleton-virtual-972f0e568a/0/cache/react-style-singleton-npm-2.2.3-18f32c05f7-62498094ff.zip/node_modules/react-style-singleton/",\ + "packageDependencies": [\ + ["react-style-singleton", "virtual:ea40e58d54d27b0a45b54ad579e666a4f36c206869d33d28f3dbe6c66397a18ef79ccaf891c2ca0e9187e4d5346a16cb6a62e7b3470dd99cc1cb5be1037a415b#npm:2.2.3"],\ + ["@types/react", "npm:17.0.2"],\ + ["get-nonce", "npm:1.0.1"],\ + ["react", "npm:17.0.2"],\ + ["tslib", "npm:2.6.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["react-toastify", [\ @@ -30387,6 +31075,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.3.3", {\ + "packageLocation": "./.yarn/cache/use-callback-ref-npm-1.3.3-e40f41fcdb-adf06a7b6a.zip/node_modules/use-callback-ref/",\ + "packageDependencies": [\ + ["use-callback-ref", "npm:1.3.3"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:2bbb419bcc79b25ce83ef1786234afad01c22b00e7984c57b0888d8c981ae152e1b9d75b70b910840d580c740ad55a2aba297ab95acc25e64e9dde321269dacd#npm:1.3.0", {\ "packageLocation": "./.yarn/__virtual__/use-callback-ref-virtual-7a363d02fe/0/cache/use-callback-ref-npm-1.3.0-6c0773783f-f9f1b217db.zip/node_modules/use-callback-ref/",\ "packageDependencies": [\ @@ -30400,6 +31095,20 @@ const RAW_RUNTIME_STATE = "react"\ ],\ "linkType": "HARD"\ + }],\ + ["virtual:ea40e58d54d27b0a45b54ad579e666a4f36c206869d33d28f3dbe6c66397a18ef79ccaf891c2ca0e9187e4d5346a16cb6a62e7b3470dd99cc1cb5be1037a415b#npm:1.3.3", {\ + "packageLocation": "./.yarn/__virtual__/use-callback-ref-virtual-478bc53b89/0/cache/use-callback-ref-npm-1.3.3-e40f41fcdb-adf06a7b6a.zip/node_modules/use-callback-ref/",\ + "packageDependencies": [\ + ["use-callback-ref", "virtual:ea40e58d54d27b0a45b54ad579e666a4f36c206869d33d28f3dbe6c66397a18ef79ccaf891c2ca0e9187e4d5346a16cb6a62e7b3470dd99cc1cb5be1037a415b#npm:1.3.3"],\ + ["@types/react", "npm:17.0.2"],\ + ["react", "npm:17.0.2"],\ + ["tslib", "npm:2.6.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["use-resize-observer", [\ @@ -30437,6 +31146,13 @@ const RAW_RUNTIME_STATE = ],\ "linkType": "SOFT"\ }],\ + ["npm:1.1.3", {\ + "packageLocation": "./.yarn/cache/use-sidecar-npm-1.1.3-f8e5c3c185-2fec05eb85.zip/node_modules/use-sidecar/",\ + "packageDependencies": [\ + ["use-sidecar", "npm:1.1.3"]\ + ],\ + "linkType": "SOFT"\ + }],\ ["virtual:2bbb419bcc79b25ce83ef1786234afad01c22b00e7984c57b0888d8c981ae152e1b9d75b70b910840d580c740ad55a2aba297ab95acc25e64e9dde321269dacd#npm:1.1.2", {\ "packageLocation": "./.yarn/__virtual__/use-sidecar-virtual-f9a54c6b14/0/cache/use-sidecar-npm-1.1.2-dfc322e94a-ec99e31aef.zip/node_modules/use-sidecar/",\ "packageDependencies": [\ @@ -30451,6 +31167,21 @@ const RAW_RUNTIME_STATE = "react"\ ],\ "linkType": "HARD"\ + }],\ + ["virtual:ea40e58d54d27b0a45b54ad579e666a4f36c206869d33d28f3dbe6c66397a18ef79ccaf891c2ca0e9187e4d5346a16cb6a62e7b3470dd99cc1cb5be1037a415b#npm:1.1.3", {\ + "packageLocation": "./.yarn/__virtual__/use-sidecar-virtual-350a95c18d/0/cache/use-sidecar-npm-1.1.3-f8e5c3c185-2fec05eb85.zip/node_modules/use-sidecar/",\ + "packageDependencies": [\ + ["use-sidecar", "virtual:ea40e58d54d27b0a45b54ad579e666a4f36c206869d33d28f3dbe6c66397a18ef79ccaf891c2ca0e9187e4d5346a16cb6a62e7b3470dd99cc1cb5be1037a415b#npm:1.1.3"],\ + ["@types/react", "npm:17.0.2"],\ + ["detect-node-es", "npm:1.1.0"],\ + ["react", "npm:17.0.2"],\ + ["tslib", "npm:2.6.2"]\ + ],\ + "packagePeers": [\ + "@types/react",\ + "react"\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["util-deprecate", [\ diff --git a/.yarn/cache/@radix-ui-primitive-npm-1.1.2-6c4aac54e5-6cb2ac097f.zip b/.yarn/cache/@radix-ui-primitive-npm-1.1.2-6c4aac54e5-6cb2ac097f.zip new file mode 100644 index 000000000..8fecaad24 Binary files /dev/null and b/.yarn/cache/@radix-ui-primitive-npm-1.1.2-6c4aac54e5-6cb2ac097f.zip differ diff --git a/.yarn/cache/@radix-ui-react-arrow-npm-1.1.7-f534aad787-6cdf74f060.zip b/.yarn/cache/@radix-ui-react-arrow-npm-1.1.7-f534aad787-6cdf74f060.zip new file mode 100644 index 000000000..5068cf6a8 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-arrow-npm-1.1.7-f534aad787-6cdf74f060.zip differ diff --git a/.yarn/cache/@radix-ui-react-collection-npm-1.1.7-ae6da53399-cd53e2a2be.zip b/.yarn/cache/@radix-ui-react-collection-npm-1.1.7-ae6da53399-cd53e2a2be.zip new file mode 100644 index 000000000..865f62480 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-collection-npm-1.1.7-ae6da53399-cd53e2a2be.zip differ diff --git a/.yarn/cache/@radix-ui-react-compose-refs-npm-1.1.2-f0371f8267-9a91f02130.zip b/.yarn/cache/@radix-ui-react-compose-refs-npm-1.1.2-f0371f8267-9a91f02130.zip new file mode 100644 index 000000000..d1b0f1068 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-compose-refs-npm-1.1.2-f0371f8267-9a91f02130.zip differ diff --git a/.yarn/cache/@radix-ui-react-context-npm-1.1.2-8b506f5df0-156088367d.zip b/.yarn/cache/@radix-ui-react-context-npm-1.1.2-8b506f5df0-156088367d.zip new file mode 100644 index 000000000..dc6aaf1d5 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-context-npm-1.1.2-8b506f5df0-156088367d.zip differ diff --git a/.yarn/cache/@radix-ui-react-direction-npm-1.1.1-43894c0d7e-8cc330285f.zip b/.yarn/cache/@radix-ui-react-direction-npm-1.1.1-43894c0d7e-8cc330285f.zip new file mode 100644 index 000000000..15436feb4 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-direction-npm-1.1.1-43894c0d7e-8cc330285f.zip differ diff --git a/.yarn/cache/@radix-ui-react-dismissable-layer-npm-1.1.10-9f18499422-e08733ee34.zip b/.yarn/cache/@radix-ui-react-dismissable-layer-npm-1.1.10-9f18499422-e08733ee34.zip new file mode 100644 index 000000000..85b96c9ae Binary files /dev/null and b/.yarn/cache/@radix-ui-react-dismissable-layer-npm-1.1.10-9f18499422-e08733ee34.zip differ diff --git a/.yarn/cache/@radix-ui-react-dropdown-menu-npm-2.1.15-e66d5c78e9-1300aa871d.zip b/.yarn/cache/@radix-ui-react-dropdown-menu-npm-2.1.15-e66d5c78e9-1300aa871d.zip new file mode 100644 index 000000000..95856a337 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-dropdown-menu-npm-2.1.15-e66d5c78e9-1300aa871d.zip differ diff --git a/.yarn/cache/@radix-ui-react-focus-guards-npm-1.1.2-f716f8ce5a-618658e2b9.zip b/.yarn/cache/@radix-ui-react-focus-guards-npm-1.1.2-f716f8ce5a-618658e2b9.zip new file mode 100644 index 000000000..4a65dc94e Binary files /dev/null and b/.yarn/cache/@radix-ui-react-focus-guards-npm-1.1.2-f716f8ce5a-618658e2b9.zip differ diff --git a/.yarn/cache/@radix-ui-react-focus-scope-npm-1.1.7-3d2d275c11-2a7cd00e39.zip b/.yarn/cache/@radix-ui-react-focus-scope-npm-1.1.7-3d2d275c11-2a7cd00e39.zip new file mode 100644 index 000000000..882083dbc Binary files /dev/null and b/.yarn/cache/@radix-ui-react-focus-scope-npm-1.1.7-3d2d275c11-2a7cd00e39.zip differ diff --git a/.yarn/cache/@radix-ui-react-id-npm-1.1.1-d2c71a3e42-8d68e20077.zip b/.yarn/cache/@radix-ui-react-id-npm-1.1.1-d2c71a3e42-8d68e20077.zip new file mode 100644 index 000000000..28a0d14fe Binary files /dev/null and b/.yarn/cache/@radix-ui-react-id-npm-1.1.1-d2c71a3e42-8d68e20077.zip differ diff --git a/.yarn/cache/@radix-ui-react-menu-npm-2.1.15-05aa64453c-7876c0892b.zip b/.yarn/cache/@radix-ui-react-menu-npm-2.1.15-05aa64453c-7876c0892b.zip new file mode 100644 index 000000000..0fd53225c Binary files /dev/null and b/.yarn/cache/@radix-ui-react-menu-npm-2.1.15-05aa64453c-7876c0892b.zip differ diff --git a/.yarn/cache/@radix-ui-react-popper-npm-1.2.7-00fc4d5ec7-1d1bcb679f.zip b/.yarn/cache/@radix-ui-react-popper-npm-1.2.7-00fc4d5ec7-1d1bcb679f.zip new file mode 100644 index 000000000..86c7995b5 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-popper-npm-1.2.7-00fc4d5ec7-1d1bcb679f.zip differ diff --git a/.yarn/cache/@radix-ui-react-portal-npm-1.1.9-8d4bfbd782-bd6be39bf0.zip b/.yarn/cache/@radix-ui-react-portal-npm-1.1.9-8d4bfbd782-bd6be39bf0.zip new file mode 100644 index 000000000..427e18540 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-portal-npm-1.1.9-8d4bfbd782-bd6be39bf0.zip differ diff --git a/.yarn/cache/@radix-ui-react-presence-npm-1.1.4-8b5ae8121c-ba01f385f6.zip b/.yarn/cache/@radix-ui-react-presence-npm-1.1.4-8b5ae8121c-ba01f385f6.zip new file mode 100644 index 000000000..db64bc9b5 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-presence-npm-1.1.4-8b5ae8121c-ba01f385f6.zip differ diff --git a/.yarn/cache/@radix-ui-react-primitive-npm-2.1.3-6080896851-1dbbf932a3.zip b/.yarn/cache/@radix-ui-react-primitive-npm-2.1.3-6080896851-1dbbf932a3.zip new file mode 100644 index 000000000..09d2108bc Binary files /dev/null and b/.yarn/cache/@radix-ui-react-primitive-npm-2.1.3-6080896851-1dbbf932a3.zip differ diff --git a/.yarn/cache/@radix-ui-react-roving-focus-npm-1.1.10-052edc0141-65241d84cb.zip b/.yarn/cache/@radix-ui-react-roving-focus-npm-1.1.10-052edc0141-65241d84cb.zip new file mode 100644 index 000000000..2988c727c Binary files /dev/null and b/.yarn/cache/@radix-ui-react-roving-focus-npm-1.1.10-052edc0141-65241d84cb.zip differ diff --git a/.yarn/cache/@radix-ui-react-slot-npm-1.2.3-6e45e6d89b-fe484c2741.zip b/.yarn/cache/@radix-ui-react-slot-npm-1.2.3-6e45e6d89b-fe484c2741.zip new file mode 100644 index 000000000..5ac3d5625 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-slot-npm-1.2.3-6e45e6d89b-fe484c2741.zip differ diff --git a/.yarn/cache/@radix-ui-react-use-callback-ref-npm-1.1.1-d0f2aaabce-cde8c40f1d.zip b/.yarn/cache/@radix-ui-react-use-callback-ref-npm-1.1.1-d0f2aaabce-cde8c40f1d.zip new file mode 100644 index 000000000..cff4645a7 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-use-callback-ref-npm-1.1.1-d0f2aaabce-cde8c40f1d.zip differ diff --git a/.yarn/cache/@radix-ui-react-use-controllable-state-npm-1.2.2-ddb427f3a3-a100bff3dd.zip b/.yarn/cache/@radix-ui-react-use-controllable-state-npm-1.2.2-ddb427f3a3-a100bff3dd.zip new file mode 100644 index 000000000..497bfcac5 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-use-controllable-state-npm-1.2.2-ddb427f3a3-a100bff3dd.zip differ diff --git a/.yarn/cache/@radix-ui-react-use-effect-event-npm-0.0.2-578346f568-5a1950a30a.zip b/.yarn/cache/@radix-ui-react-use-effect-event-npm-0.0.2-578346f568-5a1950a30a.zip new file mode 100644 index 000000000..5005bced1 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-use-effect-event-npm-0.0.2-578346f568-5a1950a30a.zip differ diff --git a/.yarn/cache/@radix-ui-react-use-escape-keydown-npm-1.1.1-dea48a407a-0eb0756c2c.zip b/.yarn/cache/@radix-ui-react-use-escape-keydown-npm-1.1.1-dea48a407a-0eb0756c2c.zip new file mode 100644 index 000000000..233af9fee Binary files /dev/null and b/.yarn/cache/@radix-ui-react-use-escape-keydown-npm-1.1.1-dea48a407a-0eb0756c2c.zip differ diff --git a/.yarn/cache/@radix-ui-react-use-layout-effect-npm-1.1.1-e3c7fd61a2-bad2ba4f20.zip b/.yarn/cache/@radix-ui-react-use-layout-effect-npm-1.1.1-e3c7fd61a2-bad2ba4f20.zip new file mode 100644 index 000000000..30fa2bce5 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-use-layout-effect-npm-1.1.1-e3c7fd61a2-bad2ba4f20.zip differ diff --git a/.yarn/cache/@radix-ui-react-use-rect-npm-1.1.1-a65c790d9f-116461bebc.zip b/.yarn/cache/@radix-ui-react-use-rect-npm-1.1.1-a65c790d9f-116461bebc.zip new file mode 100644 index 000000000..6ba27bd72 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-use-rect-npm-1.1.1-a65c790d9f-116461bebc.zip differ diff --git a/.yarn/cache/@radix-ui-react-use-size-npm-1.1.1-c1e9d2fef8-64e61f65fe.zip b/.yarn/cache/@radix-ui-react-use-size-npm-1.1.1-c1e9d2fef8-64e61f65fe.zip new file mode 100644 index 000000000..eb15ff302 Binary files /dev/null and b/.yarn/cache/@radix-ui-react-use-size-npm-1.1.1-c1e9d2fef8-64e61f65fe.zip differ diff --git a/.yarn/cache/@radix-ui-rect-npm-1.1.1-8c5111b10d-b6c5eb7876.zip b/.yarn/cache/@radix-ui-rect-npm-1.1.1-8c5111b10d-b6c5eb7876.zip new file mode 100644 index 000000000..dda005828 Binary files /dev/null and b/.yarn/cache/@radix-ui-rect-npm-1.1.1-8c5111b10d-b6c5eb7876.zip differ diff --git a/.yarn/cache/aria-hidden-npm-1.2.6-46a480bac7-1914e5a362.zip b/.yarn/cache/aria-hidden-npm-1.2.6-46a480bac7-1914e5a362.zip new file mode 100644 index 000000000..f20c3cd40 Binary files /dev/null and b/.yarn/cache/aria-hidden-npm-1.2.6-46a480bac7-1914e5a362.zip differ diff --git a/.yarn/cache/react-remove-scroll-bar-npm-2.3.8-21a578f734-6c0f8cff98.zip b/.yarn/cache/react-remove-scroll-bar-npm-2.3.8-21a578f734-6c0f8cff98.zip new file mode 100644 index 000000000..ef0f48558 Binary files /dev/null and b/.yarn/cache/react-remove-scroll-bar-npm-2.3.8-21a578f734-6c0f8cff98.zip differ diff --git a/.yarn/cache/react-remove-scroll-npm-2.7.1-f317483ad9-5e571ba35b.zip b/.yarn/cache/react-remove-scroll-npm-2.7.1-f317483ad9-5e571ba35b.zip new file mode 100644 index 000000000..67e490014 Binary files /dev/null and b/.yarn/cache/react-remove-scroll-npm-2.7.1-f317483ad9-5e571ba35b.zip differ diff --git a/.yarn/cache/react-style-singleton-npm-2.2.3-18f32c05f7-62498094ff.zip b/.yarn/cache/react-style-singleton-npm-2.2.3-18f32c05f7-62498094ff.zip new file mode 100644 index 000000000..c78c6d62e Binary files /dev/null and b/.yarn/cache/react-style-singleton-npm-2.2.3-18f32c05f7-62498094ff.zip differ diff --git a/.yarn/cache/use-callback-ref-npm-1.3.3-e40f41fcdb-adf06a7b6a.zip b/.yarn/cache/use-callback-ref-npm-1.3.3-e40f41fcdb-adf06a7b6a.zip new file mode 100644 index 000000000..8eabce1c8 Binary files /dev/null and b/.yarn/cache/use-callback-ref-npm-1.3.3-e40f41fcdb-adf06a7b6a.zip differ diff --git a/.yarn/cache/use-sidecar-npm-1.1.3-f8e5c3c185-2fec05eb85.zip b/.yarn/cache/use-sidecar-npm-1.1.3-f8e5c3c185-2fec05eb85.zip new file mode 100644 index 000000000..49e0999ca Binary files /dev/null and b/.yarn/cache/use-sidecar-npm-1.1.3-f8e5c3c185-2fec05eb85.zip differ diff --git a/packages/browser-tests/cypress/commands.js b/packages/browser-tests/cypress/commands.js index 26cc53a03..bab84e83d 100644 --- a/packages/browser-tests/cypress/commands.js +++ b/packages/browser-tests/cypress/commands.js @@ -86,13 +86,15 @@ beforeEach(() => { Cypress.Commands.add("clearSimulatedWarnings", () => { cy.typeQuery("select simulate_warnings('', '');"); - cy.clickRun(); + cy.clickRunIconInLine(1); }); Cypress.Commands.add("getByDataHook", (name) => cy.get(`[data-hook="${name}"]`) ); +Cypress.Commands.add("getByRole", (name) => cy.get(`[role="${name}"]`)); + Cypress.Commands.add("getGrid", () => cy.get(".qg-viewport .qg-canvas").should("be.visible") ); @@ -103,9 +105,9 @@ Cypress.Commands.add("getGridRow", (n) => cy.get(".qg-r").filter(":visible").eq(n) ); -Cypress.Commands.add("getColumnName", (n) => { - cy.get(".qg-header-name").filter(":visible").eq(n); -}) +Cypress.Commands.add("getColumnName", (n) => + cy.get(".qg-header-name").eq(n).invoke("text") +); Cypress.Commands.add("getGridCol", (n) => cy.get(".qg-c").filter(":visible").eq(n) @@ -137,14 +139,22 @@ Cypress.Commands.add("runLineWithResponse", (response) => { }); Cypress.Commands.add("clickLine", (n) => { - cy.get(".monaco-editor .view-line") - .eq(n - 1) - .click(); + cy.window().then((win) => { + const monacoEditor = win.monaco.editor.getEditors()[0]; + monacoEditor.revealLine(n); + monacoEditor.setPosition({ + lineNumber: n, + column: monacoEditor.getModel().getLineMaxColumn(n), + }); + }); + cy.get(".active-line-number").should("contain", n); }); -Cypress.Commands.add("clickRun", () => { - cy.intercept("/exec*").as("exec"); - return cy.get("button").contains("Run").click().wait("@exec"); +Cypress.Commands.add("scrollToLine", (n) => { + cy.window().then((win) => { + const monacoEditor = win.monaco.editor.getEditors()[0]; + monacoEditor.revealLine(n); + }); }); Cypress.Commands.add("clearEditor", () => { @@ -169,9 +179,7 @@ Cypress.Commands.add("getMountedEditor", () => cy.get(".monaco-scrollable-element") ); -Cypress.Commands.add("getEditor", () => { - cy.get(".monaco-editor.vs-dark"); -}); +Cypress.Commands.add("getEditor", () => cy.get(".monaco-editor.vs-dark")); Cypress.Commands.add("getEditorContent", () => cy @@ -198,6 +206,49 @@ Cypress.Commands.add("getCursorQueryDecoration", () => Cypress.Commands.add("getCursorQueryGlyph", () => cy.get(".cursorQueryGlyph")); +Cypress.Commands.add("getRunIconInLine", (lineNumber) => { + cy.getCursorQueryGlyph().should("be.visible"); + const selector = `.cursorQueryGlyph-line-${lineNumber}`; + cy.get("body").then(() => { + let element = null; + + if (Cypress.$(selector).length > 0) { + element = cy.get(selector).first(); + } + + if (!element) { + throw new Error(`No run icon found for line ${lineNumber}.`); + } + return element; + }); +}); + +Cypress.Commands.add("openRunDropdownInLine", (lineNumber) => { + cy.getRunIconInLine(lineNumber).rightclick(); +}); + +Cypress.Commands.add("clickRunIconInLine", (lineNumber) => { + cy.getRunIconInLine(lineNumber).click(); +}); + +Cypress.Commands.add("clickDropdownRunQuery", () => { + cy.intercept("/exec*").as("exec"); + return cy.getByDataHook("dropdown-item-run-query").click().wait("@exec"); +}); + +Cypress.Commands.add("clickDropdownGetQueryPlan", () => { + cy.intercept("/exec*").as("exec"); + return cy.getByDataHook("dropdown-item-get-query-plan").click().wait("@exec"); +}); + +Cypress.Commands.add("clickRunQuery", () => { + cy.intercept("/exec*").as("exec"); + cy.getByDataHook("button-run-query") + .should("not.be.disabled") + .click() + .wait("@exec"); +}); + const numberRangeRegexp = (n, width = 3) => { const [min, max] = [n - width, n + width]; const numbers = Array.from( @@ -226,8 +277,29 @@ Cypress.Commands.add("F9", () => { Cypress.Commands.add("getSelectedLines", () => cy.get(".selected-text")); +Cypress.Commands.add("selectRange", (startPos, endPos) => { + cy.window().then((win) => { + const monacoEditor = win.monaco.editor.getEditors()[0]; + monacoEditor.setSelection({ + startLineNumber: startPos.lineNumber, + startColumn: startPos.column, + endLineNumber: endPos.lineNumber, + endColumn: endPos.column, + }); + }); +}); + Cypress.Commands.add("getVisibleLines", () => cy.get(".view-lines")); +Cypress.Commands.add("expandNotifications", () => + cy.get('[data-hook="expand-notifications"]').click() +); + +Cypress.Commands.add("collapseNotifications", () => { + cy.get('[data-hook="collapse-notifications"]').click(); + cy.get('[data-hook="notifications-collapsed"]').should("be.visible"); +}); + Cypress.Commands.add("getCollapsedNotifications", () => cy.get('[data-hook="notifications-collapsed"]') ); @@ -334,7 +406,9 @@ Cypress.Commands.add("logout", () => { Cypress.Commands.add("executeSQL", (sql) => { cy.clearEditor(); cy.typeQuery(sql); - cy.clickRun(); + cy.intercept("/exec*").as("exec"); + cy.clickRunIconInLine(1); + cy.wait("@exec"); }); Cypress.Commands.add("refreshSchema", () => { diff --git a/packages/browser-tests/cypress/integration/console/editor.spec.js b/packages/browser-tests/cypress/integration/console/editor.spec.js index 11bc0b801..c72243125 100644 --- a/packages/browser-tests/cypress/integration/console/editor.spec.js +++ b/packages/browser-tests/cypress/integration/console/editor.spec.js @@ -3,6 +3,8 @@ const contextPath = process.env.QDB_HTTP_CONTEXT_WEB_CONSOLE || ""; const baseUrl = `http://localhost:9999${contextPath}`; +const { ctrlOrCmd } = require("../../utils"); + const getTabDragHandleByTitle = (title) => `.chrome-tab[data-tab-title="${title}"] .chrome-tab-drag-handle`; @@ -15,8 +17,7 @@ describe("run query", () => { it("should correctly run query in the first line", () => { cy.typeQuery("select 1;\n\nselect 2;"); - cy.clickLine(1); - cy.clickRun(); + cy.clickRunIconInLine(1); cy.getGridRow(0).should("contain", "1"); }); @@ -27,8 +28,7 @@ describe("run query", () => { cy.typeQuery(" select count(*) from longseq;select 1;"); // go to the end of second query - cy.clickLine(4); - cy.clickRun(); + cy.clickLine(4).type(`${ctrlOrCmd}{enter}`); cy.getGridCol(0).should("contain", "1"); cy.getGridRow(0).should("contain", "1"); @@ -36,16 +36,16 @@ describe("run query", () => { cy.clickLine(4); cy.realPress("ArrowLeft"); cy.realPress("ArrowLeft"); - cy.clickRun(); + cy.focused().type(`${ctrlOrCmd}{enter}`); cy.getColumnName(0).should("contain", "1"); cy.getGridRow(0).should("contain", "1"); // go to the end of first query cy.clickLine(4); - for (let i = 0; i < 9; i++) { + for (let i = 0; i < 10; i++) { cy.realPress("ArrowLeft"); } - cy.clickRun(); + cy.focused().type(`${ctrlOrCmd}{enter}`); cy.getColumnName(0).should("contain", "count"); cy.getGridRow(0).should("contain", "100"); @@ -54,20 +54,291 @@ describe("run query", () => { for (let i = 0; i < 11; i++) { cy.realPress("ArrowLeft"); } - cy.clickRun(); + cy.focused().type(`${ctrlOrCmd}{enter}`); cy.getColumnName(0).should("contain", "count"); cy.getGridRow(0).should("contain", "100"); }); - it("should not suggest any query for running if the cursor is in an empty line between queries", () => { - cy.typeQuery("select 1;\n\nselect 2;"); - cy.getCursorQueryGlyph().should("be.visible"); + it("should provide query selection dropdown when multiple queries start from the same line", () => { + cy.typeQuery("select 1;select 2;select 3;"); + cy.clickRunIconInLine(1); + cy.getByDataHook("dropdown-item-run-query-0").should( + "contain", + `Run "select 1"` + ); + cy.getByDataHook("dropdown-item-run-query-1").should( + "contain", + `Run "select 2"` + ); + cy.getByDataHook("dropdown-item-run-query-2").should( + "contain", + `Run "select 3"` + ); + cy.getByDataHook("dropdown-item-run-query-1") + .contains(`Run "select 2"`) + .click(); + cy.getByDataHook("success-notification").should("contain", "select 2"); + }); +}); - cy.realPress("ArrowUp"); - cy.getCursorQueryGlyph().should("not.exist"); +describe("run query with selection", () => { + beforeEach(() => { + cy.loadConsoleWithAuth(); + cy.getEditorContent().should("be.visible"); + cy.clearEditor(); + }); - cy.realPress("ArrowUp"); - cy.getCursorQueryGlyph().should("be.visible"); + it("should correctly identify and run selected queries", () => { + // Given + cy.getByDataHook("button-run-query").should("be.disabled"); + + // When + cy.typeQuery("select 11;select 22;select 33;"); + // Then + cy.getByDataHook("button-run-query").should("contain", "Run query"); + + // When + cy.clickRunQuery(); + // Then + cy.getByDataHook("success-notification").should("contain", "select 33"); + + // When + cy.selectRange({ lineNumber: 1, column: 1 }, { lineNumber: 1, column: 9 }); + // Then + cy.getByDataHook("button-run-query").should( + "contain", + "Run selected query" + ); + + // When + cy.clickRunQuery(); + // Then + cy.getByDataHook("success-notification").should("contain", "select 1"); + + // When + cy.selectRange({ lineNumber: 1, column: 1 }, { lineNumber: 1, column: 10 }); + // Then + cy.getByDataHook("button-run-query").should( + "contain", + "Run selected query" + ); + + // When + cy.clickRunQuery(); + // Then + cy.getByDataHook("success-notification").should("contain", "select 11"); + + // When + cy.selectRange({ lineNumber: 1, column: 1 }, { lineNumber: 1, column: 19 }); + // Then + cy.getByDataHook("button-run-query").should( + "contain", + "Run 2 selected queries" + ); + + // When + cy.clickRunQuery(); + // Then + cy.getByDataHook("success-notification") + .invoke("text") + .should( + "match", + /Running completed in \d+ms with\s+2 successful\s+queries/ + ); + + // When + cy.expandNotifications(); + // Then + cy.getExpandedNotifications().should("contain", "select 11"); + cy.getExpandedNotifications().should("contain", "select 2"); + }); + + it("should run and explain a specific query from the line glyph", () => { + const subQuery = "select md5(concat('1', x)) as md from long_sequence(100)"; + const subQueryTruncated = "select md5(concat('1', x)) as ..."; + + // When + cy.typeQueryDirectly( + `create table long_seq as (\n ${subQuery}\n --comment\n);` + ); + cy.openRunDropdownInLine(1); + // Then + cy.getByDataHook("dropdown-item-run-query").should( + "contain", + `Run "create table long_seq as (` + ); + cy.getByDataHook("dropdown-item-get-query-plan").should( + "contain", + `Get query plan for "create table long_seq as (` + ); + + // When + cy.getByDataHook("dropdown-item-get-query-plan").click(); + // Then + cy.getByDataHook("success-notification").should( + "contain", + "EXPLAIN create table long_seq as" + ); + + // When + cy.selectRange( + { lineNumber: 2, column: 3 }, + { lineNumber: 2, column: 3 + subQuery.length } + ); + // Then + cy.getByDataHook("button-run-query").should( + "contain", + "Run selected query" + ); + + // When + cy.openRunDropdownInLine(1); + // Then + cy.getByDataHook("dropdown-item-run-query").should( + "contain", + `Run "${subQueryTruncated}"` + ); + cy.getByDataHook("dropdown-item-get-query-plan").should( + "contain", + `Get query plan for "${subQueryTruncated}"` + ); + + // When + cy.getByDataHook("dropdown-item-run-query").click(); + // Then + cy.getByDataHook("success-notification").should("contain", subQuery); + + // When + cy.openRunDropdownInLine(1); + cy.getByDataHook("dropdown-item-get-query-plan").click(); + // Then + cy.getByDataHook("success-notification").should( + "contain", + `EXPLAIN ${subQuery}` + ); + }); +}); + +describe("run all queries in tab", () => { + beforeEach(() => { + cy.loadConsoleWithAuth(); + cy.getEditorContent().should("be.visible"); + cy.clearEditor(); + }); + + it("should run all queries in tab", () => { + // Given + cy.typeQueryDirectly( + "select 1;select 2;select 3;\n\ncreate table long_seq as (\nselect md5(concat('1', x)) as md from long_sequence(100)\n--comment\n);\ndrop table long_seq;\n\ndrop table long_seq;\n;\n;\n ;\n ;\n; ;;\n" + ); + + // When + cy.typeQuery(`${ctrlOrCmd}a`); + // Then + cy.getByDataHook("button-run-query").should( + "contain", + "Run 6 selected queries" + ); + + // When + cy.clickLine(1); + // Then + cy.getByDataHook("button-run-script").should("not.exist"); + + // When + cy.getByDataHook("button-run-query-dropdown").click(); + // Then + cy.getByDataHook("button-run-script").should("be.visible"); + + // When + cy.getByDataHook("button-run-script").click(); + // Then + cy.getByRole("dialog").should("be.visible"); + cy.getByDataHook("stop-after-failure-checkbox").should("be.checked"); + + // When + cy.getByDataHook("run-all-queries-confirm").click(); + // Then + cy.getByDataHook("success-notification") + .invoke("text") + .should( + "match", + /Running completed in \d+ms with\s+5 successful\s+and\s+1 failed\s+queries/ + ); + + // When + cy.scrollToLine(1); + + // Then + cy.get(".success-glyph").should("have.length", 3); + cy.get(".error-glyph").should("have.length", 1); + + // When + cy.clickLine(9); + // Then + cy.getByDataHook("error-notification").should( + "contain", + "table does not exist" + ); + cy.getByDataHook("error-notification").should( + "contain", + "drop table long_seq" + ); + + // When + cy.expandNotifications(); + // Then + cy.getExpandedNotifications().children().should("have.length", 8); + }); + + it("should not run all queries if stop after failure is checked", () => { + // Given + cy.typeQuery("select 1;\nselect a;\nselect 3;"); + + // When + cy.getByDataHook("button-run-query-dropdown").click(); + cy.getByDataHook("button-run-script").click(); + // Then + cy.getByRole("dialog").should("be.visible"); + cy.getByDataHook("stop-after-failure-checkbox").should("be.checked"); + + // When + cy.getByDataHook("run-all-queries-confirm").click(); + // Then + cy.getByDataHook("error-notification") + .invoke("text") + .should( + "match", + /Stopped after running\s+1 successful\s+and\s+1 failed\s+queries/ + ); + cy.get(".success-glyph").should("have.length", 1); + cy.get(".error-glyph").should("have.length", 1); + cy.get(".cursorQueryGlyph").should("have.length", 3); + }); + + it("should run all queries if stop after failure is unchecked", () => { + // Given + cy.typeQuery("select 1;\nselect a;\nselect 3;"); + + // When + cy.getByDataHook("button-run-query-dropdown").click(); + cy.getByDataHook("button-run-script").click(); + // Then + cy.getByRole("dialog").should("be.visible"); + cy.getByDataHook("stop-after-failure-checkbox").uncheck(); + + // When + cy.getByDataHook("run-all-queries-confirm").click(); + // Then + cy.getByDataHook("success-notification") + .invoke("text") + .should( + "match", + /Running completed in \d+ms with\s+2 successful\s+and\s+1 failed\s+queries/ + ); + cy.get(".success-glyph").should("have.length", 2); + cy.get(".error-glyph").should("have.length", 1); + cy.get(".cursorQueryGlyph").should("have.length", 3); }); }); @@ -199,7 +470,8 @@ describe("&query URL param", () => { }); it("should append and select single line query", () => { - cy.typeQuery("select x from long_sequence(1)"); // running query caches it, it's available after refresh + cy.typeQueryDirectly("select x from long_sequence(1)"); // running query caches it, it's available after refresh + cy.getCursorQueryGlyph().should("be.visible"); const query = encodeURIComponent("select x+1 from long_sequence(1)"); cy.visit(`${baseUrl}/?query=${query}&executeQuery=true`); cy.getEditorContent().should("be.visible"); @@ -208,11 +480,12 @@ describe("&query URL param", () => { }); it("should append and select multiline query", () => { - cy.typeQuery( + cy.typeQueryDirectly( `select x\nfrom long_sequence(1);\n\n-- a\n-- b\n-- c\n${"{upArrow}".repeat( 5 )}` ); + cy.getCursorQueryGlyph().should("be.visible"); const query = encodeURIComponent("select x+1\nfrom\nlong_sequence(1);"); cy.visit(`${baseUrl}?query=${query}&executeQuery=true`); cy.getEditorContent().should("be.visible"); @@ -222,16 +495,16 @@ describe("&query URL param", () => { it("should not append query if it already exists in editor", () => { const query = "select x\nfrom long_sequence(1);\n\n-- a\n-- b\n-- c"; - cy.typeQuery(query); - cy.clickLine(1); - cy.clickRun(); + cy.typeQueryDirectly(query); + cy.clickRunIconInLine(1); cy.visit(`${baseUrl}?query=${encodeURIComponent(query)}&executeQuery=true`); cy.getEditorContent().should("be.visible"); cy.getEditorContent().should("have.value", query); }); it("should append query and scroll to it", () => { - cy.typeQuery("select x from long_sequence(1);"); + cy.typeQueryDirectly("select x from long_sequence(1);"); + cy.getCursorQueryGlyph().should("be.visible"); cy.typeQuery("\n".repeat(20)); const appendedQuery = "-- hello world"; @@ -611,7 +884,7 @@ describe("handling comments", () => { it("should highlight and execute sql with line comments in front", () => { cy.typeQuery("-- comment\n-- comment\nselect x from long_sequence(1);"); - cy.getCursorQueryDecoration().should("have.length", 3); + cy.getCursorQueryDecoration().should("have.length", 1); cy.getCursorQueryGlyph().should("have.length", 1); cy.runLine(); cy.getGridRow(0).should("contain", "1"); @@ -619,7 +892,7 @@ describe("handling comments", () => { it("should highlight and execute sql with empty line comment in front", () => { cy.typeQuery("--\nselect x from long_sequence(1);"); - cy.getCursorQueryDecoration().should("have.length", 2); + cy.getCursorQueryDecoration().should("have.length", 1); cy.getCursorQueryGlyph().should("have.length", 1); cy.runLine(); cy.getGridRow(0).should("contain", "1"); @@ -627,17 +900,21 @@ describe("handling comments", () => { it("should highlight and execute sql with block comments", () => { cy.typeQuery("/* comment */\nselect x from long_sequence(1);"); - cy.getCursorQueryDecoration().should("have.length", 2); + cy.getCursorQueryDecoration().should("have.length", 1); cy.getCursorQueryGlyph().should("have.length", 1); cy.runLine(); cy.getGridRow(0).should("contain", "1"); cy.clearEditor(); cy.typeQuery("/*\ncomment\n*/\nselect x from long_sequence(1);"); - cy.getCursorQueryDecoration().should("have.length", 4); + cy.getCursorQueryDecoration().should("have.length", 1); cy.getCursorQueryGlyph().should("have.length", 1); cy.runLine(); cy.getGridRow(0).should("contain", "1"); + cy.getByDataHook("success-notification").should( + "contain", + "select x from long_sequence(1)" + ); }); it("should highlight and execute sql with line comments inside", () => { @@ -654,5 +931,199 @@ describe("handling comments", () => { cy.getCursorQueryGlyph().should("have.length", 1); cy.runLine(); cy.getGridRow(0).should("contain", "1"); + cy.getByDataHook("success-notification").should( + "contain", + "select x from long_sequence(1)" + ); + }); + + it("should extract only two queries when comments have semicolons", () => { + cy.typeQueryDirectly( + "-- not a query;\n/* not a query 2;\n not a query 3; */select /* not; a; query;*/ 1; --not a query /* ; 4;\nselect\n\n --line;\n 2;" + ); + cy.clickLine(4); + cy.getCursorQueryDecoration().should("have.length", 4); + cy.getCursorQueryGlyph().should("have.length", 2); + cy.clickRunIconInLine(3); + cy.getByDataHook("success-notification").should( + "contain", + "select /* not; a; query;*/ 1" + ); + + cy.clickRunIconInLine(4); + cy.getByDataHook("success-notification").should( + "contain", + `select\n\n --line;\n 2` + ); + }); +}); + +describe("multiple run buttons with dynamic query log", () => { + beforeEach(() => { + cy.loadConsoleWithAuth(); + cy.getEditorContent().should("be.visible"); + cy.clearEditor(); + }); + + it("should click run icon in specific line and open dropdown", () => { + cy.typeQuery("select 1;\n\nselect 2;\n\nselect 3;"); + cy.openRunDropdownInLine(3); + + cy.getByDataHook("dropdown-item-run-query").should( + "contain", + `Run "select 2"` + ); + cy.getByDataHook("dropdown-item-get-query-plan").should( + "contain", + `Get query plan for "select 2"` + ); + cy.getByDataHook("dropdown-item-run-query").click(); + cy.getByDataHook("success-notification").should("contain", "select 2"); + + cy.openRunDropdownInLine(1); + cy.getByDataHook("dropdown-item-run-query").should( + "contain", + `Run "select 1"` + ); + cy.getByDataHook("dropdown-item-get-query-plan").should( + "contain", + `Get query plan for "select 1"` + ); + cy.getByDataHook("dropdown-item-get-query-plan").click(); + cy.getByDataHook("success-notification").should( + "contain", + "EXPLAIN select 1" + ); + + cy.openRunDropdownInLine(5); + cy.getByDataHook("dropdown-item-run-query").should( + "contain", + `Run "select 3"` + ); + cy.getByDataHook("dropdown-item-get-query-plan").should( + "contain", + `Get query plan for "select 3"` + ); + }); + + it("should run query from specific line using dropdown", () => { + cy.typeQuery("select 1;\n\nselect 2;\n\nselect 3;"); + cy.clickRunIconInLine(3); + + cy.getByDataHook("success-notification").should("contain", "select 2"); + }); + + it("should get query plan from specific line using dropdown", () => { + cy.typeQuery("select 1;\n\nselect 2;\n\nselect 3;"); + + cy.openRunDropdownInLine(5).clickDropdownGetQueryPlan(); + + cy.getColumnName(0).should("contain", "QUERY PLAN"); + }); + + it("should indicate error in glyph and notification", () => { + cy.typeQuery("select * from non_existent_table;\n\nselect 1;\n\nselect 2;"); + + cy.clickRunIconInLine(3); + + cy.getByDataHook("success-notification").should("contain", "select 1"); + + cy.clickRunIconInLine(5); + + cy.getByDataHook("success-notification").should("contain", "select 2"); + + cy.clickRunIconInLine(1); + cy.getByDataHook("error-notification") + .should("contain", "table does not exist") + .should("contain", "select * from non_existent_table"); + + cy.openRunDropdownInLine(3).clickDropdownGetQueryPlan(); + cy.getByDataHook("success-notification").should( + "contain", + "EXPLAIN select 1" + ); + + cy.expandNotifications(); + // +1 for clear query log button + cy.getExpandedNotifications().children().should("have.length", 5); + [ + "select 1", + "select 2", + "select * from non_existent_table", + "EXPLAIN select 1", + ].forEach((notification) => { + cy.getExpandedNotifications().should("contain", notification); + }); + + cy.collapseNotifications(); + + cy.clickLine(1); + cy.getByDataHook("error-notification").should( + "contain", + "table does not exist" + ); + + cy.clickLine(3); + cy.getByDataHook("success-notification").should( + "contain", + "EXPLAIN select 1" + ); + + cy.clickLine(5); + cy.getByDataHook("success-notification").should("contain", "select 2"); + }); + + it("should keep execution info per tab", () => { + // When + cy.typeQuery("select 1;\nselect a;\nselect 3;"); + cy.getByDataHook("button-run-query-dropdown").click(); + cy.getByDataHook("button-run-script").click(); + // Then + cy.getByRole("dialog").should("be.visible"); + cy.getByDataHook("stop-after-failure-checkbox").uncheck(); + + // When + cy.getByDataHook("run-all-queries-confirm").click(); + // Then + cy.getByDataHook("success-notification") + .invoke("text") + .should( + "match", + /Running completed in \d+ms with\s+2 successful\s+and\s+1 failed\s+queries/ + ); + cy.get(".success-glyph").should("have.length", 2); + cy.get(".error-glyph").should("have.length", 1); + cy.get(".cursorQueryGlyph").should("have.length", 3); + + // When + cy.get(".new-tab-button").click(); + // Then + cy.getEditorTabByTitle("SQL 1") + .should("be.visible") + .should("have.attr", "active"); + + // When + cy.typeQuery("select 1;\nselect a;\nselect 3;"); + // Then + cy.get(".success-glyph").should("have.length", 0); + cy.get(".error-glyph").should("have.length", 0); + cy.get(".cursorQueryGlyph").should("have.length", 3); + + // When + cy.clickRunIconInLine(3); + // Then + cy.get(".success-glyph").should("have.length", 1); + cy.get(".error-glyph").should("have.length", 0); + cy.get(".cursorQueryGlyph").should("have.length", 3); + + // When + cy.getEditorTabByTitle("SQL").within(() => { + cy.get(".chrome-tab-drag-handle").click(); + }); + // Then + cy.getEditorTabByTitle("SQL").should("have.attr", "active"); + cy.get(".success-glyph").should("have.length", 2); + cy.get(".error-glyph").should("have.length", 1); + cy.get(".cursorQueryGlyph").should("have.length", 3); }); }); diff --git a/packages/browser-tests/cypress/integration/console/result_charts.spec.js b/packages/browser-tests/cypress/integration/console/result_charts.spec.js index 880b0496e..f3ab8bfff 100644 --- a/packages/browser-tests/cypress/integration/console/result_charts.spec.js +++ b/packages/browser-tests/cypress/integration/console/result_charts.spec.js @@ -9,7 +9,8 @@ describe("questdb charts", () => { cy.typeQueryDirectly( "SELECT rnd_timestamp(to_timestamp('2024-07-19:00:00:00.000000', 'yyyy-MM-dd:HH:mm:ss.SSSUUU'), to_timestamp('2024-07-20:00:00:00.000000', 'yyyy-MM-dd:HH:mm:ss.SSSUUU'), 0), x FROM long_sequence(10);" ); - cy.clickRun(); + cy.get(".cursorQueryGlyph-line-1").should("be.visible"); + cy.clickRunIconInLine(1); cy.getByDataHook("chart-panel-button").should("be.visible").click(); cy.get(".quick-vis-canvas").click(); cy.getByDataHook("chart-panel-labels-select").should( diff --git a/packages/browser-tests/cypress/integration/console/schema.spec.js b/packages/browser-tests/cypress/integration/console/schema.spec.js index d9d6e206b..a59edb469 100644 --- a/packages/browser-tests/cypress/integration/console/schema.spec.js +++ b/packages/browser-tests/cypress/integration/console/schema.spec.js @@ -220,10 +220,11 @@ describe("keyboard navigation", () => { cy.contains("btc_trades_mv").should("not.exist"); }); - it("should switch the focus between grid and schema", () => { + it("should switch the focus between editor, grid, and schema", () => { cy.getEditorContent().should("be.visible"); cy.typeQuery("SELECT 123123;"); cy.runLine(); + cy.contains(".qg-c", "123123").click(); cy.focused().should("contain", "123123"); cy.expandMatViews(); @@ -249,6 +250,16 @@ describe("keyboard navigation", () => { const lastMatView = materializedViews[materializedViews.length - 1]; cy.focused().should("contain", lastMatView); }); + + after(() => { + cy.loadConsoleWithAuth(); + tables.forEach((table) => { + cy.dropTable(table); + }); + materializedViews.forEach((mv) => { + cy.dropMaterializedView(mv); + }); + }); }); describe("questdb schema with suspended tables with Linux OS error codes", () => { @@ -262,13 +273,13 @@ describe("questdb schema with suspended tables with Linux OS error codes", () => cy.typeQuery( "ALTER TABLE btc_trades SUSPEND WAL WITH 24, 'Too many open files';" ) - .clickRun() + .clickRunIconInLine(1) .clearEditor(); cy.typeQuery( "ALTER TABLE ecommerce_stats SUSPEND WAL WITH 12, 'Out of memory';" ) - .clickRun() + .clickRunIconInLine(1) .clearEditor(); }); beforeEach(() => { @@ -490,7 +501,7 @@ describe("materialized views", () => { cy.typeQuery( "ALTER MATERIALIZED VIEW btc_trades_mv SUSPEND WAL WITH 24, 'Too many open files';" ) - .clickRun() + .runLine() .clearEditor(); cy.refreshSchema(); @@ -502,7 +513,7 @@ describe("materialized views", () => { .contains("btc_trades_mv") .rightclick(); cy.getByDataHook("table-context-menu-resume-wal") - .should("not.be.disabled") + .filter(":visible") .click(); cy.getByDataHook("schema-suspension-dialog").should( @@ -513,6 +524,7 @@ describe("materialized views", () => { cy.getByDataHook("schema-suspension-dialog-restart-transaction").click(); cy.getByDataHook("schema-suspension-dialog-dismiss").click(); cy.getByDataHook("schema-suspension-dialog").should("not.exist"); + cy.refreshSchema(); cy.getByDataHook("schema-filter-suspended-button").should("not.exist"); cy.getByDataHook("schema-row-error-icon").should("not.exist"); }); diff --git a/packages/browser-tests/questdb b/packages/browser-tests/questdb index 2c5a34b30..14d4c8f5f 160000 --- a/packages/browser-tests/questdb +++ b/packages/browser-tests/questdb @@ -1 +1 @@ -Subproject commit 2c5a34b30871bdc49509ef676994ae22b070896d +Subproject commit 14d4c8f5f900a9a57f3c866193fdc207af13da29 diff --git a/packages/web-console/package.json b/packages/web-console/package.json index eb8a9e0d3..a88e7c3f9 100644 --- a/packages/web-console/package.json +++ b/packages/web-console/package.json @@ -32,6 +32,7 @@ "@questdb/sql-grammar": "1.2.4", "@radix-ui/react-context-menu": "2.1.5", "@radix-ui/react-dialog": "^1.0.3", + "@radix-ui/react-dropdown-menu": "^2.1.15", "@styled-icons/bootstrap": "10.47.0", "@styled-icons/boxicons-logos": "10.47.0", "@styled-icons/boxicons-regular": "10.47.0", diff --git a/packages/web-console/src/components/ContextMenu/index.tsx b/packages/web-console/src/components/ContextMenu/index.tsx index 1593cc777..79d99bbee 100644 --- a/packages/web-console/src/components/ContextMenu/index.tsx +++ b/packages/web-console/src/components/ContextMenu/index.tsx @@ -13,23 +13,32 @@ const StyledContent = styled(ContextMenuPrimitive.Content)` const StyledItem = styled(ContextMenuPrimitive.Item)` font-size: 1.3rem; - height: 2.6rem; + height: 3rem; font-family: "system-ui", sans-serif; cursor: pointer; color: rgb(248, 248, 242); /* vscode-menu-foreground */ display: flex; align-items: center; - padding: 0 1.2rem; + padding: 1rem 1.2rem; border-radius: 0.4rem; + border: 1px solid transparent; &[data-highlighted] { - background-color: #45475a; - color: rgb(240, 240, 240); + background: #043c5c; + border: 1px solid #8be9fd; + } + + &[data-disabled] { + opacity: 0.5; + pointer-events: none; } ` const IconWrapper = styled.span` - margin-right: 10px; + margin-right: 1.2rem; + display: flex; + align-items: center; + justify-content: center; ` export const ContextMenu = ContextMenuPrimitive.Root @@ -39,7 +48,7 @@ export const ContextMenuContent = React.forwardRef< HTMLDivElement, ContextMenuPrimitive.ContextMenuContentProps >((props, forwardedRef) => ( - + )) diff --git a/packages/web-console/src/components/DropdownMenu/index.tsx b/packages/web-console/src/components/DropdownMenu/index.tsx new file mode 100644 index 000000000..57027a008 --- /dev/null +++ b/packages/web-console/src/components/DropdownMenu/index.tsx @@ -0,0 +1,53 @@ +import * as RadixDropdownMenu from "@radix-ui/react-dropdown-menu"; +import styled from "styled-components"; + +export const DropdownMenu = { + Root: RadixDropdownMenu.Root, + + Trigger: styled(RadixDropdownMenu.Trigger)` + cursor: pointer; + `, + + Portal: styled(RadixDropdownMenu.Portal)``, + + Content: styled(RadixDropdownMenu.Content)` + display: grid; + gap: 0.5rem; + min-width: 22rem; + background: ${({ theme }) => theme.color.backgroundLighter}; + border-radius: ${({ theme }) => theme.borderRadius}; + box-shadow: 0 5px 5px 0 ${({ theme }) => theme.color.black40}; + padding: 1rem 0; + `, + + Arrow: styled(RadixDropdownMenu.Arrow)` + fill: ${({ theme }) => theme.color.black40}; + `, + + Item: styled(RadixDropdownMenu.Item)` + border-radius: 3px; + display: flex; + gap: 1.5rem; + align-items: center; + padding: 0.5rem 1rem; + margin: 0 0.5rem; + user-select: none; + outline: none; + + &[data-disabled] { + pointer-events: none; + opacity: 0.8; + } + + &:focus { + background: ${({ theme }) => theme.color.comment}; + cursor: pointer; + } + `, + + Divider: styled.div` + height: 1px; + background: ${({ theme }) => theme.color.selection}; + margin: 0.5rem 0; + `, +}; diff --git a/packages/web-console/src/components/icons/play-filled.tsx b/packages/web-console/src/components/icons/play-filled.tsx new file mode 100644 index 000000000..d0f8f826e --- /dev/null +++ b/packages/web-console/src/components/icons/play-filled.tsx @@ -0,0 +1,18 @@ +import React from "react" + +type Props = { + size?: number | string + color?: string +} + +export const PlayFilled = ({ size = 24, color = "currentColor" }: Props) => ( + + + +) \ No newline at end of file diff --git a/packages/web-console/src/js/console/grid.js b/packages/web-console/src/js/console/grid.js index f52ed1a86..dab66ad3c 100644 --- a/packages/web-console/src/js/console/grid.js +++ b/packages/web-console/src/js/console/grid.js @@ -191,6 +191,8 @@ export function grid(rootElement, _paginationFn, id) { // Cell will pulse when its content is copied to clipboard let activeCellPulseClearTimer + let initialFocusSkipped = false + function getColumn(index) { return columns[columnPositions[index]] } @@ -1966,6 +1968,10 @@ export function grid(rootElement, _paginationFn, id) { } function focusTopLeftCell() { + if (!initialFocusSkipped) { + initialFocusSkipped = true + return + } focusedRowIndex = 0 focusedRowContainer = rows[focusedRowIndex] focusedRowContainerLeft = rowsLeft[focusedRowIndex] @@ -2122,6 +2128,7 @@ export function grid(rootElement, _paginationFn, id) { } function setData(_data) { + initialFocusSkipped = false setTimeout(() => { setDataPart1(_data) // This part of the update sequence requires layoutStore access. diff --git a/packages/web-console/src/scenes/Editor/ButtonBar/index.tsx b/packages/web-console/src/scenes/Editor/ButtonBar/index.tsx new file mode 100644 index 000000000..335057753 --- /dev/null +++ b/packages/web-console/src/scenes/Editor/ButtonBar/index.tsx @@ -0,0 +1,276 @@ +import React, { useCallback, useState } from "react" +import styled from "styled-components" +import { useDispatch, useSelector } from "react-redux" +import { Stop } from "@styled-icons/remix-line" +import { CornerDownLeft } from "@styled-icons/evaicons-solid" +import { ChevronDown } from "@styled-icons/boxicons-solid" +import { PopperToggle } from "../../../components" +import { Box, Button } from "@questdb/react-components" +import { actions, selectors } from "../../../store" +import { platform, color } from "../../../utils" +import { RunningType } from "../../../store/Query/types" + +const ButtonBarWrapper = styled.div` + position: absolute; + top: 1rem; + right: 2.4rem; + z-index: 1; +` + +const ButtonGroup = styled.div` + display: flex; + gap: 0; +` + +const SuccessButton = styled(Button)` + background-color: ${color("greenDarker")}; + border-color: ${color("greenDarker")}; + color: ${color("foreground")}; + + &:hover:not(:disabled) { + background-color: ${color("green")}; + border-color: ${color("green")}; + color: ${color("gray1")}; + } + + &:disabled { + background-color: ${color("greenDarker")}; + border-color: ${color("greenDarker")}; + color: ${color("foreground")}; + opacity: 0.6; + } + + svg { + color: ${color("foreground")}; + } + + &:hover:not(:disabled) svg { + color: ${color("gray1")}; + } + + &:disabled svg { + color: ${color("foreground")}; + } +` + +const StopButton = styled(Button)` + background-color: ${color("red")}; + border-color: ${color("red")}; + color: ${color("foreground")}; + + &:hover:not(:disabled) { + background-color: ${color("red")}; + border-color: ${color("red")}; + color: ${color("foreground")}; + filter: brightness(1.2); + } + + &:disabled { + background-color: ${color("red")}; + border-color: ${color("red")}; + color: ${color("foreground")}; + opacity: 0.6; + } + + svg { + color: ${color("foreground")}; + } + + &:hover:not(:disabled) svg { + color: ${color("foreground")}; + } + + &:disabled svg { + color: ${color("foreground")}; + } +` + +const MainRunButton = styled(SuccessButton)` + border-top-right-radius: 0; + border-bottom-right-radius: 0; +` + +const DropdownButton = styled(SuccessButton)<{ $open: boolean }>` + border-top-left-radius: 0; + border-bottom-left-radius: 0; + padding: 0 0.5rem; + min-width: auto; + svg { + transform: ${({ $open }) => $open ? "rotate(180deg)" : "rotate(0deg)"}; + } +` + +const DropdownMenu = styled.div` + background: ${color("backgroundDarker")}; + border-radius: 0.4rem; + box-shadow: 0 0.4rem 1.2rem rgba(0, 0, 0, 0.3); + overflow: hidden; + transform: translateX(-7rem) translateY(0.5rem); + padding: 0; + min-width: unset; + border: 0; + + > * { + justify-content: space-between; + width: 100%; + font-size: 1.4rem; + } +` + +const Key = styled(Box).attrs({ alignItems: "center" })` + padding: 0 0.4rem; + background: ${color("gray1")}; + border-radius: 0.2rem; + font-size: 1.2rem; + height: 1.8rem; + color: ${color("green")}; + + &:not(:last-child) { + margin-right: 0.25rem; + } + + svg { + color: ${color("green")} !important; + } +` + +const RunShortcut = styled(Box).attrs({ alignItems: "center", gap: "0" })` + margin-left: 1rem; +` + +const ctrlCmd = platform.isMacintosh || platform.isIOS ? "⌘" : "Ctrl" +const shortcutTitles = platform.isMacintosh || platform.isIOS ? { + [RunningType.QUERY]: "Cmd+Enter", + [RunningType.SCRIPT]: "Cmd+Shift+Enter", +} : { + [RunningType.QUERY]: "Ctrl+Enter", + [RunningType.SCRIPT]: "Ctrl+Shift+Enter", +} + +const ButtonBar = ({ onTriggerRunScript }: { onTriggerRunScript: (runAll?: boolean) => void }) => { + const dispatch = useDispatch() + const running = useSelector(selectors.query.getRunning) + const queriesToRun = useSelector(selectors.query.getQueriesToRun) + const [dropdownActive, setDropdownActive] = useState(false) + + const handleClickQueryButton = useCallback(() => { + if (queriesToRun.length > 1) { + onTriggerRunScript() + } else { + dispatch(actions.query.toggleRunning()) + } + }, [dispatch, queriesToRun, onTriggerRunScript]) + + const handleClickScriptButton = useCallback(() => { + onTriggerRunScript(true) + setDropdownActive(false) + }, [dispatch, onTriggerRunScript]) + + const handleDropdownToggle = useCallback((active: boolean) => { + setDropdownActive(active) + }, []) + + const renderRunScriptButton = () => { + if (running === RunningType.SCRIPT) { + return ( + } + > + Cancel + + ) + } + return ( + + Run all queries + + {ctrlCmd} + + + + + ) + } + + const renderRunQueryButton = () => { + if (running !== RunningType.NONE && running !== RunningType.SCRIPT) { + return ( + + } + > + Cancel + + + ) + } + + const getQueryButtonText = () => { + const numQueries = queriesToRun.length + if (numQueries === 1) { + return queriesToRun[0].selection ? "Run selected query" : "Run query" + } + if (numQueries > 1) { + return `Run ${numQueries} selected queries` + } + return "Run query" + } + + return ( + + + {getQueryButtonText()} + + {ctrlCmd} + + + + + + + } + > + + {renderRunScriptButton()} + + + + ) + } + + return ( + + {running === RunningType.SCRIPT ? renderRunScriptButton() : renderRunQueryButton()} + + ) +} + +export default ButtonBar \ No newline at end of file diff --git a/packages/web-console/src/scenes/Editor/Menu/index.tsx b/packages/web-console/src/scenes/Editor/Menu/index.tsx index 361f7f9dd..5b831e844 100644 --- a/packages/web-console/src/scenes/Editor/Menu/index.tsx +++ b/packages/web-console/src/scenes/Editor/Menu/index.tsx @@ -26,9 +26,8 @@ import { useCallback, useEffect, useState } from "react" import { useDispatch, useSelector } from "react-redux" import { CSSTransition } from "react-transition-group" import styled from "styled-components" -import { Add, Close as _CloseIcon, Play, Stop } from "@styled-icons/remix-line" +import { Add, Close as _CloseIcon } from "@styled-icons/remix-line" import { Menu as _MenuIcon } from "@styled-icons/remix-fill" -import { CornerDownLeft } from "@styled-icons/evaicons-solid" import { PaneMenu, @@ -40,7 +39,7 @@ import { } from "../../../components" import { Box } from "@questdb/react-components" import { actions, selectors } from "../../../store" -import { color, platform } from "../../../utils" +import { color } from "../../../utils" import QueryPicker from "../QueryPicker" import { useLocalStorage } from "../../../providers/LocalStorageProvider" import { StoreKey } from "../../../utils/localStorage/types" @@ -139,20 +138,14 @@ const MenuItems = styled.div` align-items: center; ` -const ctrlCmd = platform.isMacintosh || platform.isIOS ? "⌘" : "Ctrl" - const Menu = () => { const dispatch = useDispatch() const [queriesPopperActive, setQueriesPopperActive] = useState() const escPress = useKeyPress("Escape") const { consoleConfig } = useSettings() - const running = useSelector(selectors.query.getRunning) const opened = useSelector(selectors.console.getSideMenuOpened) const { sm } = useScreenSize() const { exampleQueriesVisited, updateSettings } = useLocalStorage() - const handleClick = useCallback(() => { - dispatch(actions.query.toggleRunning()) - }, [dispatch]) const handleQueriesToggle = useCallback((active: boolean) => { if (!exampleQueriesVisited && active) { updateSettings(StoreKey.EXAMPLE_QUERIES_VISITED, true) @@ -203,33 +196,6 @@ const Menu = () => { - {running.value && ( - - )} - - {!running.value && ( - - )} - ` + position: fixed; + width: 1px; + height: 1px; + opacity: 0; + pointer-events: none; + top: ${props => props.style?.top || '0px'}; + left: ${props => props.style?.left || '0px'}; + z-index: 9998; +` + +interface QueryDropdownProps { + open: boolean + onOpenChange: (open: boolean) => void + positionRef: React.MutableRefObject<{ x: number; y: number } | null> + queriesRef: React.MutableRefObject + isContextMenuRef: React.MutableRefObject + onRunQuery: (query?: Request) => void + onExplainQuery: (query?: Request) => void +} + +export const QueryDropdown: React.FC = ({ + open, + onOpenChange, + positionRef, + queriesRef, + isContextMenuRef, + onRunQuery, + onExplainQuery, +}) => { + const handleOpenChange = (isOpen: boolean) => { + onOpenChange(isOpen) + } + + const extractQueryTextToRun = (query: Request) => { + if (!query) return "query" + const queryText = query.selection ? query.selection.queryText : query.query + return queryText.length > 30 + ? `"${queryText.substring(0, 30)}..."` + : `"${queryText}"` + } + + const isExplainDisabled = (query: Request) => { + if (!query) return false + const queryText = query.selection ? query.selection.queryText : query.query + return queryText.startsWith("EXPLAIN") || queryText.startsWith("explain") + } + + return ( + + + + + + + {queriesRef.current.length > 1 ? ( + // Multiple queries - show options for each + queriesRef.current.map((query, index) => { + const items = [ + onRunQuery(query)} + data-hook={`dropdown-item-run-query-${index}`} + > + + Run {extractQueryTextToRun(query)} + + ] + + if (isContextMenuRef.current) { + items.push( + onExplainQuery(query)} + data-hook={`dropdown-item-explain-query-${index}`} + > + + Get query plan for {extractQueryTextToRun(query)} + + ) + } + + return items + }).flat() + ) : ( + [ + onRunQuery(queriesRef.current[0])} data-hook="dropdown-item-run-query"> + + Run {extractQueryTextToRun(queriesRef.current[0])} + , + onExplainQuery(queriesRef.current[0])} data-hook="dropdown-item-get-query-plan"> + + Get query plan for {extractQueryTextToRun(queriesRef.current[0])} + + ] + )} + + + + ) +} \ No newline at end of file diff --git a/packages/web-console/src/scenes/Editor/Monaco/editor-addons.ts b/packages/web-console/src/scenes/Editor/Monaco/editor-addons.ts index 36dab9817..212b62846 100644 --- a/packages/web-console/src/scenes/Editor/Monaco/editor-addons.ts +++ b/packages/web-console/src/scenes/Editor/Monaco/editor-addons.ts @@ -38,6 +38,7 @@ import type { editor } from "monaco-editor" enum Command { EXECUTE = "execute", + RUN_SCRIPT = "run_script", FOCUS_GRID = "focus_grid", ADD_NEW_TAB = "add_new_tab", CLOSE_ACTIVE_TAB = "close_active_tab", @@ -48,15 +49,18 @@ export const registerEditorActions = ({ editor, monaco, runQuery, - dispatch, + runScript, editorContext, }: { editor: editor.IStandaloneCodeEditor monaco: Monaco runQuery: () => void - dispatch: Dispatch + runScript: () => void editorContext: EditorContext }) => { + editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Enter, () => { + }) + editor.addAction({ id: Command.EXECUTE, label: "Execute command", @@ -69,6 +73,15 @@ export const registerEditorActions = ({ }, }) + editor.addAction({ + id: Command.RUN_SCRIPT, + label: "Run script", + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.Enter], + run: () => { + runScript() + }, + }) + editor.addAction({ id: Command.ADD_NEW_TAB, label: "Add new tab", diff --git a/packages/web-console/src/scenes/Editor/Monaco/index.tsx b/packages/web-console/src/scenes/Editor/Monaco/index.tsx index dc0211103..bb1676f2d 100644 --- a/packages/web-console/src/scenes/Editor/Monaco/index.tsx +++ b/packages/web-console/src/scenes/Editor/Monaco/index.tsx @@ -1,16 +1,20 @@ import Editor, { loader, Monaco } from "@monaco-editor/react" -import { Box, Button } from "@questdb/react-components" +import { Box, Button, Checkbox, Dialog, ForwardRef, Overlay } from "@questdb/react-components" import { Stop } from "@styled-icons/remix-line" -import type { editor, IDisposable, IRange } from "monaco-editor" -import type { BaseSyntheticEvent } from "react" +import type { editor, IDisposable } from "monaco-editor" import React, { useContext, useEffect, useRef, useState } from "react" +import type { ReactNode } from "react" import { useDispatch, useSelector } from "react-redux" import styled from "styled-components" +import type { ExecutionRefs } from "../../Editor" import { PaneContent, Text } from "../../../components" +import { formatTiming } from "../QueryResult" import { eventBus } from "../../../modules/EventBus" import { EventType } from "../../../modules/EventBus/types" import { QuestContext, useEditor } from "../../../providers" import { actions, selectors } from "../../../store" +import { RunningType } from "../../../store/Query/types" +import type { NotificationShape } from "../../../store/Query/types" import { theme } from "../../../theme" import { NotificationType } from "../../../types" import type { ErrorResult } from "../../../utils" @@ -23,7 +27,7 @@ import { registerEditorActions, registerLanguageAddons } from "./editor-addons" import { registerLegacyEventBusEvents } from "./legacy-event-bus" import { QueryInNotification } from "./query-in-notification" import { createSchemaCompletionProvider } from "./questdb-sql" -import type { Request } from "./utils" +import { Request } from "./utils" import { appendQuery, clearModelMarkers, @@ -33,9 +37,28 @@ import { getQueryRequestFromEditor, getQueryRequestFromLastExecutedQuery, QuestDBLanguageName, - setErrorMarker, - stripSQLComments, + getAllQueries, + getQueriesInRange, + normalizeQueryText, + QueryKey, + createQueryKey, + parseQueryKey, + createQueryKeyFromRequest, + validateQueryAtOffset, + setErrorMarkerForQuery, + getQueryStartOffset, + getQueriesToRun, + getQueriesStartingFromLine, } from "./utils" +import { toast } from "../../../components/Toast" +import ButtonBar from "../ButtonBar" +import { QueryDropdown } from "./QueryDropdown" + +type IndividualQueryResult = { + success: boolean, + result?: QuestDB.QueryRawResult + notification: Partial & { content: ReactNode, query: QueryKey } | null +} loader.config({ paths: { @@ -43,6 +66,8 @@ loader.config({ }, }) +export const LINE_NUMBER_HARD_LIMIT = 99999 + const Content = styled(PaneContent)` position: relative; overflow: hidden; @@ -60,13 +85,23 @@ const Content = styled(PaneContent)` .cursorQueryDecoration { width: 0.2rem !important; background: ${color("green")}; - margin-left: 1.2rem; + margin-left: 0.5rem; &.hasError { background: ${color("red")}; } } + .selectionErrorHighlight { + background-color: rgba(255, 85, 85, 0.15); + border-radius: 2px; + } + + .selectionSuccessHighlight { + background-color: rgba(80, 250, 123, 0.15); + border-radius: 2px; + } + .cursorQueryGlyph, .cancelQueryGlyph { margin-left: 2rem; @@ -76,31 +111,33 @@ const Content = styled(PaneContent)` &:after { display: block; content: ""; - width: 18px; - height: 18px; + width: 22px; + height: 22px; + background-repeat: no-repeat; + background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGhlaWdodD0iMjJweCIgd2lkdGg9IjIycHgiIGFyaWEtaGlkZGVuPSJ0cnVlIiBmb2N1c2FibGU9ImZhbHNlIiBmaWxsPSIjNTBmYTdiIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJTdHlsZWRJY29uQmFzZS1zYy1lYTl1bGotMCBrZkRiTmwiPjxwYXRoIGZpbGw9Im5vbmUiIGQ9Ik0wIDBoMjR2MjRIMHoiPjwvcGF0aD48cGF0aCBkPSJNMTYuMzk0IDEyIDEwIDcuNzM3djguNTI2TDE2LjM5NCAxMnptMi45ODIuNDE2TDguNzc3IDE5LjQ4MkEuNS41IDAgMCAxIDggMTkuMDY2VjQuOTM0YS41LjUgMCAwIDEgLjc3Ny0uNDE2bDEwLjU5OSA3LjA2NmEuNS41IDAgMCAxIDAgLjgzMnoiPjwvcGF0aD48L3N2Zz4K"); + transform: scale(1.1); + } + &:hover:after { + filter: brightness(1.3); } } - .cursorQueryGlyph { - &:after { - background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGhlaWdodD0iMThweCIgd2lkdGg9IjE4cHgiIGFyaWEtaGlkZGVuPSJ0cnVlIiBmb2N1c2FibGU9ImZhbHNlIiBmaWxsPSIjNTBmYTdiIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJTdHlsZWRJY29uQmFzZS1zYy1lYTl1bGotMCBrZkRiTmwiPjxwYXRoIGZpbGw9Im5vbmUiIGQ9Ik0wIDBoMjR2MjRIMHoiPjwvcGF0aD48cGF0aCBkPSJNMTYuMzk0IDEyIDEwIDcuNzM3djguNTI2TDE2LjM5NCAxMnptMi45ODIuNDE2TDguNzc3IDE5LjQ4MkEuNS41IDAgMCAxIDggMTkuMDY2VjQuOTM0YS41LjUgMCAwIDEgLjc3Ny0uNDE2bDEwLjU5OSA3LjA2NmEuNS41IDAgMCAxIDAgLjgzMnoiPjwvcGF0aD48L3N2Zz4K"); - } + .cursorQueryGlyph.success-glyph:after { + background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGhlaWdodD0iMjJweCIgd2lkdGg9IjIycHgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8ZGVmcz4KICAgICAgICA8Y2xpcFBhdGggaWQ9ImNsaXAwIj48cmVjdCB3aWR0aD0iMjQiIGhlaWdodD0iMjQiLz48L2NsaXBQYXRoPgogICAgPC9kZWZzPgogICAgPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSI+CiAgICAgICAgPHBhdGggZD0iTTggNC45MzR2MTQuMTMyYzAgLjQzMy40NjYuNzAyLjgxMi40ODRsMTAuNTYzLTcuMDY2YS41LjUgMCAwIDAgMC0uODMyTDguODEyIDQuNjE2QS41LjUgMCAwIDAgOCA0LjkzNFoiIGZpbGw9IiM1MGZhN2IiLz4KICAgICAgICA8Y2lyY2xlIGN4PSIxOCIgY3k9IjgiIHI9IjYiIGZpbGw9IiMwMGFhM2IiLz4KICAgICAgICA8cGF0aCBkPSJtMTUgOC41IDIgMiA0LTQiIHN0cm9rZT0id2hpdGUiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGZpbGw9Im5vbmUiLz4KICAgIDwvZz4KPC9zdmc+"); + } + + .cursorQueryGlyph.error-glyph:after { + background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGhlaWdodD0iMjJweCIgd2lkdGg9IjIycHgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8ZGVmcz4KICAgICAgICA8Y2xpcFBhdGggaWQ9ImNsaXAwIj48cmVjdCB3aWR0aD0iMjQiIGhlaWdodD0iMjQiLz48L2NsaXBQYXRoPgogICAgPC9kZWZzPgogICAgPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSI+CiAgICAgICAgPHBhdGggZD0iTTggNC45MzR2MTQuMTMyYzAgLjQzMy40NjYuNzAyLjgxMi40ODRsMTAuNTYzLTcuMDY2YS41LjUgMCAwIDAgMC0uODMyTDguODEyIDQuNjE2QS41LjUgMCAwIDAgOCA0LjkzNFoiIGZpbGw9IiM1MGZhN2IiLz4KICAgICAgICA8Y2lyY2xlIGN4PSIxOCIgY3k9IjgiIHI9IjYiIGZpbGw9IiNmZjU1NTUiLz4KICAgICAgICA8cmVjdCB4PSIxNyIgeT0iNCIgd2lkdGg9IjIiIGhlaWdodD0iNSIgZmlsbD0id2hpdGUiIHJ4PSIwLjUiLz4KICAgICAgICA8Y2lyY2xlIGN4PSIxOCIgY3k9IjExIiByPSIxIiBmaWxsPSJ3aGl0ZSIvPgogICAgPC9nPgo8L3N2Zz4="); } .cancelQueryGlyph { &:after { - background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGhlaWdodD0iMThweCIgd2lkdGg9IjE4cHgiIGFyaWEtaGlkZGVuPSJ0cnVlIiBmb2N1c2FibGU9ImZhbHNlIiBmaWxsPSIjZmY1NTU1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJTdHlsZWRJY29uQmFzZS1zYy1lYTl1bGotMCBqQ2hkR0siPjxwYXRoIGZpbGw9Im5vbmUiIGQ9Ik0wIDBoMjR2MjRIMHoiPjwvcGF0aD48cGF0aCBkPSJNNyA3djEwaDEwVjdIN3pNNiA1aDEyYTEgMSAwIDAgMSAxIDF2MTJhMSAxIDAgMCAxLTEgMUg2YTEgMSAwIDAgMS0xLTFWNmExIDEgMCAwIDEgMS0xeiI+PC9wYXRoPjwvc3ZnPgo="); + background-image: url("data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMjQgMjQiIGhlaWdodD0iMjJweCIgd2lkdGg9IjIycHgiIGFyaWEtaGlkZGVuPSJ0cnVlIiBmb2N1c2FibGU9ImZhbHNlIiBmaWxsPSIjZmY1NTU1IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJTdHlsZWRJY29uQmFzZS1zYy1lYTl1bGotMCBqQ2hkR0siPjxwYXRoIGZpbGw9Im5vbmUiIGQ9Ik0wIDBoMjR2MjRIMHoiPjwvcGF0aD48cGF0aCBkPSJNNyA3djEwaDEwVjdIN3pNNiA1aDEyYTEgMSAwIDAgMSAxIDF2MTJhMSAxIDAgMCAxLTEgMUg2YTEgMSAwIDAgMS0xLTFWNmExIDEgMCAwIDEgMS0xeiI+PC9wYXRoPjwvc3ZnPgo="); } - } - .errorGlyph { - margin-left: 2.5rem; - margin-top: 0.5rem; - z-index: 1; - width: 0.75rem !important; - height: 0.75rem !important; - border-radius: 50%; - background: ${color("red")}; + &:hover:after { + filter: brightness(1.3); + } } ` @@ -108,9 +145,22 @@ const CancelButton = styled(Button)` padding: 1.2rem 0.6rem; ` +const StyledDialogDescription = styled(Dialog.Description)` + font-size: 1.4rem; +` + +const StyledDialogButton = styled(Button)` + padding: 1.2rem 0.6rem; + font-size: 1.4rem; + + &:focus { + outline: 1px solid ${({ theme }) => theme.color.foreground}; + } +` + const DEFAULT_LINE_CHARS = 5 -const MonacoEditor = () => { +const MonacoEditor = ({ executionRefs }: { executionRefs: React.MutableRefObject }) => { const editorContext = useEditor() const { buffers, @@ -120,27 +170,44 @@ const MonacoEditor = () => { activeBuffer, updateBuffer, editorReadyTrigger, - addBuffer, } = editorContext const { quest } = useContext(QuestContext) const [request, setRequest] = useState() const [editorReady, setEditorReady] = useState(false) const [lastExecutedQuery, setLastExecutedQuery] = useState("") const [refreshingTables, setRefreshingTables] = useState(false) + const [dropdownOpen, setDropdownOpen] = useState(false) + const [scriptConfirmationOpen, setScriptConfirmationOpen] = useState(false) const dispatch = useDispatch() const running = useSelector(selectors.query.getRunning) const tables = useSelector(selectors.query.getTables) const columns = useSelector(selectors.query.getColumns) + const activeNotification = useSelector(selectors.query.getActiveNotification) + const queryNotifications = useSelector(selectors.query.getQueryNotificationsForBuffer(activeBuffer.id as number)) const [schemaCompletionHandle, setSchemaCompletionHandle] = useState() - const decorationsRef = useRef() - const runningValueRef = useRef(running.value) + const isRunningScriptRef = useRef(false) + const queryOffsetsRef = useRef<{ startOffset: number, endOffset: number }[] | null>([]) + const queriesToRunRef = useRef([]) + const scriptStopRef = useRef(false) + const stopAfterFailureRef = useRef(true) + const lineMarkingDecorationIdsRef = useRef([]) + const runningValueRef = useRef(running) const activeBufferRef = useRef(activeBuffer) const requestRef = useRef(request) - - const errorRefs = useRef< - Record - >({}) + const queryNotificationsRef = useRef(queryNotifications) + const activeNotificationRef = useRef(activeNotification) + const contentJustChangedRef = useRef(false) + const cursorChangeTimeoutRef = useRef(null) + const decorationCollectionRef = useRef(null) + const visibleLinesRef = useRef<{ startLine: number; endLine: number }>({ startLine: 1, endLine: 1 }) + const scrollTimeoutRef = useRef(null) + const notificationTimeoutRef = useRef(null) + const targetPositionRef = useRef<{ lineNumber: number; column: number } | null>(null) + const currentBufferValueRef = useRef(activeBuffer.value) + const dropdownPositionRef = useRef<{ x: number; y: number } | null>(null) + const dropdownQueriesRef = useRef([]) + const isContextMenuDropdownRef = useRef(false) // Set the initial line number width in chars based on the number of lines in the active buffer const [lineNumbersMinChars, setLineNumbersMinChars] = useState( @@ -149,8 +216,86 @@ const MonacoEditor = () => { 1, ) - const toggleRunning = (isRefresh: boolean = false) => { - dispatch(actions.query.toggleRunning(isRefresh)) + const toggleRunning = (runningType?: RunningType) => { + dispatch(actions.query.toggleRunning(runningType)) + } + + const updateQueryNotification = (queryKey?: QueryKey) => { + let newActiveNotification: NotificationShape | null = null + + if (queryKey) { + const queryNotifications = queryNotificationsRef.current?.[queryKey] + if (queryNotifications) { + newActiveNotification = queryNotifications.latest || null + } + } + + if (activeNotificationRef.current?.query !== newActiveNotification?.query) { + dispatch(actions.query.setActiveNotification(newActiveNotification)) + } + } + + const getDropdownQueries = (lineNumber: number): Request[] => { + const queriesOnLine = getQueriesStartingFromLine(editorRef.current!, lineNumber, queryOffsetsRef.current || []) + const queriesToRun = queriesToRunRef.current || [] + + const selectionsOnLine = queriesToRun.filter(query => + query.selection && query.row + 1 === lineNumber + ) + const nonSelectedQueries = queriesOnLine.filter(query => !selectionsOnLine.some(q => q.row === query.row && q.column === query.column && q.endRow === query.endRow && q.endColumn === query.endColumn)) + return [...nonSelectedQueries, ...selectionsOnLine].sort((a, b) => a.column - b.column) + } + + const setCursorBeforeRunning = (query: Request) => { + const editor = editorRef.current + const model = editor?.getModel() + if (!editor || !model) return + + if (query.selection) { + const startPosition = model.getPositionAt(query.selection.startOffset) + const endPosition = model.getPositionAt(query.selection.endOffset) + editor.setSelection({ + startLineNumber: startPosition.lineNumber, + startColumn: startPosition.column, + endLineNumber: endPosition.lineNumber, + endColumn: endPosition.column + }) + } else { + editor.setPosition({ + lineNumber: query.row + 1, + column: query.column + }) + } + } + + const openDropdownAtPosition = (posX: number, posY: number, targetPosition: { lineNumber: number; column: number }, isContextMenu: boolean = false) => { + targetPositionRef.current = targetPosition + isContextMenuDropdownRef.current = isContextMenu + + if (editorRef.current) { + const editorContainer = editorRef.current.getDomNode() + const containerRect = editorContainer?.getBoundingClientRect() + + let finalPosition: { x: number; y: number } + + if (containerRect) { + const lineHeight = 24 + const lineNumber = targetPosition.lineNumber + const scrollTop = editorRef.current.getScrollTop() + + const yPosition = containerRect.top + (lineNumber - 1) * lineHeight - scrollTop + lineHeight / 2 + 5 + const xPosition = containerRect.left + 115 + + finalPosition = { x: xPosition, y: yPosition } + } else { + // Fallback to click coordinates + finalPosition = { x: posX, y: posY } + } + + dropdownPositionRef.current = finalPosition + isContextMenuDropdownRef.current = isContextMenu + setDropdownOpen(true) + } } const beforeMount = (monaco: Monaco) => { @@ -159,119 +304,224 @@ const MonacoEditor = () => { monaco.editor.defineTheme("dracula", dracula) } - // To ensure the fixed position of the "run query" glyph we adjust the width of the line count element. - // This width is represented in char numbers. - const setLineCharsWidth = () => { - const lineCount = editorRef.current?.getModel()?.getLineCount() - if (lineCount) { - setLineNumbersMinChars( - DEFAULT_LINE_CHARS + (lineCount.toString().length - 1), - ) + const handleEditorClick = (e: React.MouseEvent) => { + if (isRunningScriptRef.current && e.target instanceof Element && e.target.classList.contains("cursorQueryGlyph")) { + return + } + const editor = editorRef.current + const model = editor?.getModel() + if (!editor || !model) return + + if (e.target instanceof Element && e.target.classList.contains("cancelQueryGlyph")) { + toggleRunning(RunningType.NONE) + return + } + + if (e.target instanceof Element && e.target.classList.contains("cursorQueryGlyph")) { + editor.focus() + const target = editor.getTargetAtClientPoint(e.clientX, e.clientY) + + if (target && target.position) { + const position = { + lineNumber: target.position.lineNumber, + column: 1 + } + const dropdownQueries = getDropdownQueries(position.lineNumber) + if (dropdownQueries.length > 1) { + dropdownQueriesRef.current = dropdownQueries + openDropdownAtPosition(e.clientX, e.clientY, position, false) + return + } + if (dropdownQueries.length === 1) { + setCursorBeforeRunning(dropdownQueries[0]) + toggleRunning() + } + } } } - const handleEditorClick = (e: BaseSyntheticEvent) => { - if ( - e.target.classList.contains("cursorQueryGlyph") || - e.target.classList.contains("cancelQueryGlyph") - ) { - editorRef?.current?.focus() - toggleRunning() + const handleRunQuery = (query?: Request) => { + setDropdownOpen(false) + + if (query) { + setCursorBeforeRunning(query) + } else if (targetPositionRef.current) { + editorRef.current?.setPosition(targetPositionRef.current) } + + toggleRunning() } - const renderLineMarkings = ( + const handleExplainQuery = (query?: Request) => { + setDropdownOpen(false) + if (query) { + setCursorBeforeRunning(query) + } else if (targetPositionRef.current) { + editorRef.current?.setPosition(targetPositionRef.current) + } + + toggleRunning(RunningType.EXPLAIN) + } + + const applyLineMarkings = ( monaco: Monaco, editor: editor.IStandaloneCodeEditor, + source?: string ) => { + const model = editor.getModel() + const editorValue = editor.getValue() + + if (!editorValue || !model) { + return + } + const queryAtCursor = getQueryFromCursor(editor) + const activeBufferId = activeBufferRef.current.id as number - if (!queryAtCursor) { - decorationsRef.current?.clear() - return + const lineMarkingDecorations: editor.IModelDeltaDecoration[] = [] + const bufferExecutions = executionRefs.current[activeBufferId] || {} + + if (queryAtCursor) { + const queryKey = createQueryKeyFromRequest(editor, queryAtCursor) + const queryExecutionBuffer = bufferExecutions[queryKey] + const hasError = queryExecutionBuffer && queryExecutionBuffer.error !== undefined + const startLineNumber = queryAtCursor.row + 1 + const endLineNumber = queryAtCursor.endRow + 1 + + if (queryExecutionBuffer && queryExecutionBuffer.selection) { + const startPosition = model.getPositionAt(queryExecutionBuffer.selection.startOffset) + const endPosition = model.getPositionAt(queryExecutionBuffer.selection.endOffset) + const isError = queryExecutionBuffer.error !== undefined + const isSuccess = queryExecutionBuffer.success === true + + lineMarkingDecorations.push({ + range: new monaco.Range( + startPosition.lineNumber, + startPosition.column, + endPosition.lineNumber, + endPosition.column, + ), + options: { + isWholeLine: false, + className: isError ? 'selectionErrorHighlight' : isSuccess ? 'selectionSuccessHighlight' : 'selectionErrorHighlight', + } + }) + } + + lineMarkingDecorations.push({ + range: new monaco.Range( + startLineNumber, + queryAtCursor.column, + endLineNumber, + queryAtCursor.endColumn, + ), + options: { + isWholeLine: true, + linesDecorationsClassName: `cursorQueryDecoration ${hasError ? "hasError" : ""}`, + } + }) } - const model = editor.getModel() - if (queryAtCursor && model !== null) { - const activeBufferId = activeBufferRef.current.id as number + const newLineMarkingIds = editor.deltaDecorations( + lineMarkingDecorationIdsRef.current, + lineMarkingDecorations + ) + lineMarkingDecorationIdsRef.current = newLineMarkingIds + if (!["scroll", "script"].includes(source ?? "") && !isRunningScriptRef.current) { + updateQueryNotification(queryAtCursor ? createQueryKeyFromRequest(editor, queryAtCursor) : undefined) + } + if (bufferExecutions) { + setErrorMarkerForQuery(monaco, editor, bufferExecutions, queryAtCursor) + } + } - const cleanedModel = monaco.editor.createModel( - stripSQLComments(model.getValue()), - QuestDBLanguageName, - ) + const applyGlyphsAndLineMarkings = ( + monaco: Monaco, + editor: editor.IStandaloneCodeEditor, + source?: string + ) => { + const model = editor.getModel() + const editorValue = editor.getValue() - const matches = findMatches(cleanedModel, queryAtCursor.query) + if (!editorValue || !model) { + return + } + let queries: Request[] = [] - cleanedModel.dispose() + const visibleLines = visibleLinesRef.current + + if (!visibleLines) { + queries = getAllQueries(editor) + } else { + const totalLines = model.getLineCount() + const bufferSize = 500 + + const startLine = Math.max(1, visibleLines.startLine - bufferSize) + const endLine = Math.min(totalLines, visibleLines.endLine + bufferSize) + + const startPosition = { lineNumber: startLine, column: 1 } + const endPosition = { lineNumber: endLine, column: editor.getModel()?.getLineMaxColumn(endLine) ?? 1 } + + queries = getQueriesInRange(editor, startPosition, endPosition) + } + + const activeBufferId = activeBufferRef.current.id as number - if (matches.length > 0) { - const hasError = - errorRefs.current && - errorRefs.current[activeBufferId]?.error?.query === - queryAtCursor.query - const errorRange = - errorRefs.current && errorRefs.current[activeBufferId]?.range - const cursorMatch = matches.find( - (m) => m.range.startLineNumber === queryAtCursor.row + 1, - ) - if (cursorMatch) { - decorationsRef.current?.clear() - decorationsRef.current = editor.createDecorationsCollection([ - { - range: new monaco.Range( - cursorMatch.range.startLineNumber, - 1, - cursorMatch.range.endLineNumber, - 1, - ), - options: { - isWholeLine: true, - linesDecorationsClassName: `cursorQueryDecoration ${hasError ? "hasError" : "" - }`, - }, - }, - { - range: new monaco.Range( - cursorMatch.range.startLineNumber, - 1, - cursorMatch.range.startLineNumber, - 1, - ), - options: { - isWholeLine: false, - glyphMarginClassName: - runningValueRef.current && - requestRef.current?.row && - requestRef.current?.row + 1 === - cursorMatch.range.startLineNumber - ? "cancelQueryGlyph" - : runningValueRef.current - ? "" - : "cursorQueryGlyph", - }, - }, - ...(hasError && - errorRange && - cursorMatch.range.startLineNumber !== errorRange.startLineNumber - ? [ - { - range: new monaco.Range( - errorRange.startLineNumber, - 0, - errorRange.startLineNumber, - 0, - ), - options: { - isWholeLine: false, - glyphMarginClassName: "errorGlyph", - }, - }, - ] - : []), - ]) + const allDecorations: editor.IModelDeltaDecoration[] = [] + const allQueryOffsets: { startOffset: number, endOffset: number }[] = [] + + // Add decorations for queries in range + if (queries.length > 0) { + queries.forEach(query => { + const queryOffsets = { + startOffset: model.getOffsetAt({ lineNumber: query.row + 1, column: query.column }), + endOffset: model.getOffsetAt({ lineNumber: query.endRow + 1, column: query.endColumn }) } - } + allQueryOffsets.push(queryOffsets) + const bufferExecutions = executionRefs.current[activeBufferId] || {} + const queryKey = createQueryKeyFromRequest(editor, query) + const queryExecutionBuffer = bufferExecutions[queryKey] + const hasError = queryExecutionBuffer && queryExecutionBuffer.error !== undefined + const isSuccessful = queryNotificationsRef.current?.[queryKey]?.latest?.type === NotificationType.SUCCESS + + // Convert 0-based row to 1-based line number for Monaco + const startLineNumber = query.row + 1 + + // Add glyph for all queries with line number in class name + const glyphClassName = + runningValueRef.current !== RunningType.NONE && + requestRef.current?.row !== undefined && + requestRef.current?.row + 1 === startLineNumber + ? `cancelQueryGlyph cancelQueryGlyph-line-${startLineNumber}` + : hasError + ? `cursorQueryGlyph error-glyph cursorQueryGlyph-line-${startLineNumber}` + : isSuccessful + ? `cursorQueryGlyph success-glyph cursorQueryGlyph-line-${startLineNumber}` + : `cursorQueryGlyph cursorQueryGlyph-line-${startLineNumber}` + + allDecorations.push({ + range: new monaco.Range( + startLineNumber, + 1, + startLineNumber, + 1 + ), + options: { + isWholeLine: false, + glyphMarginClassName: glyphClassName, + }, + }) + }) } + + if (decorationCollectionRef.current) { + decorationCollectionRef.current.clear() + } + + decorationCollectionRef.current = editor.createDecorationsCollection(allDecorations) + queryOffsetsRef.current = allQueryOffsets + + applyLineMarkings(monaco, editor, source) } const onMount = (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => { @@ -290,15 +540,43 @@ const MonacoEditor = () => { editor, monaco, runQuery: () => { - if (!runningValueRef.current) { - toggleRunning() + if (runningValueRef.current === RunningType.NONE) { + if (queriesToRunRef.current.length === 1) { + toggleRunning() + } else if (queriesToRunRef.current.length > 1) { + handleTriggerRunScript() + } + } + }, + runScript: () => { + if (runningValueRef.current === RunningType.NONE) { + handleTriggerRunScript(true) } }, - dispatch, editorContext, }) + + editor.onContextMenu((e) => { + if (e.target.element && e.target.element.classList.contains("cursorQueryGlyph")) { + const posX = e.event.posx, posY = e.event.posy + if (editorRef.current) { + const target = editorRef.current.getTargetAtClientPoint(posX, posY) + + if (target && target.position) { + const linePosition = { lineNumber: target.position.lineNumber, column: 1 } + + const dropdownQueries = getDropdownQueries(linePosition.lineNumber) + + if (dropdownQueries.length > 0) { + dropdownQueriesRef.current = dropdownQueries + openDropdownAtPosition(posX, posY, linePosition, true) + } + } + } + } + }) - editor.onDidChangeCursorPosition(() => { + editor.onDidChangeCursorPosition((e) => { // To ensure the fixed position of the "run query" glyph we adjust the width of the line count element. // This width is represented in char numbers. const lineCount = editorRef.current?.getModel()?.getLineCount() @@ -307,7 +585,158 @@ const MonacoEditor = () => { DEFAULT_LINE_CHARS + (lineCount.toString().length - 1), ) } - renderLineMarkings(monaco, editor) + + if (contentJustChangedRef.current) { + return + } + + if (cursorChangeTimeoutRef.current) { + window.clearTimeout(cursorChangeTimeoutRef.current); + } + + cursorChangeTimeoutRef.current = window.setTimeout(() => { + const queriesToRun = getQueriesToRun(editor, queryOffsetsRef.current ?? []) + queriesToRunRef.current = queriesToRun + dispatch(actions.query.setQueriesToRun(queriesToRun)) + + if (monacoRef.current && editorRef.current) { + applyLineMarkings(monaco, editor, e.source) + } + cursorChangeTimeoutRef.current = null + }, 50); + }) + + editor.onDidChangeModelContent((e) => { + const model = editor.getModel() + if (!model) return + + contentJustChangedRef.current = true + + const activeBufferId = activeBufferRef.current.id as number + const bufferExecutions = executionRefs.current[activeBufferId] + + const notificationUpdates: Array<() => void> = [] + + if (bufferExecutions) { + const keysToUpdate: Array<{ oldKey: QueryKey, newKey: QueryKey, data: any }> = [] + const keysToRemove: QueryKey[] = [] + + Object.keys(bufferExecutions).forEach((key) => { + const queryKey = key as QueryKey + const { queryText, startOffset, endOffset } = bufferExecutions[queryKey] + + const effectiveOffsetDelta = e.changes + .filter(change => change.rangeOffset < endOffset) + .reduce((acc, change) => acc + change.text.length - change.rangeLength, 0) + + if (effectiveOffsetDelta === 0) { + return + } + + const newOffset = startOffset + effectiveOffsetDelta + if (validateQueryAtOffset(editor, queryText, newOffset)) { + const selection = bufferExecutions[queryKey].selection + const shiftedSelection = selection ? { + startOffset: selection.startOffset + effectiveOffsetDelta, + endOffset: selection.endOffset + effectiveOffsetDelta + } : undefined + keysToUpdate.push({ + oldKey: queryKey, + newKey: createQueryKey(queryText, newOffset), + data: { ...bufferExecutions[queryKey], startOffset: newOffset, endOffset: endOffset + effectiveOffsetDelta, selection: shiftedSelection } + }) + } else { + keysToRemove.push(queryKey) + notificationUpdates.push(() => dispatch(actions.query.removeNotification(queryKey, activeBufferId))) + } + }) + + keysToRemove.forEach(key => { + delete bufferExecutions[key] + }) + + keysToUpdate.forEach(({ oldKey, newKey, data }) => { + delete bufferExecutions[oldKey] + bufferExecutions[newKey] = data + notificationUpdates.push(() => dispatch(actions.query.updateNotificationKey(oldKey, newKey, activeBufferId))) + }) + } + + const currentNotifications = queryNotificationsRef.current || {} + Object.keys(currentNotifications).forEach((key) => { + const queryKey = key as QueryKey + const { queryText, startOffset, endOffset } = parseQueryKey(queryKey) + const effectiveOffsetDelta = e.changes + .filter(change => change.rangeOffset < endOffset) + .reduce((acc, change) => acc + change.text.length - change.rangeLength, 0) + + if (effectiveOffsetDelta === 0) { + return + } + + const newOffset = startOffset + effectiveOffsetDelta + + if (validateQueryAtOffset(editor, queryText, newOffset)) { + const newKey = createQueryKey(queryText, newOffset) + notificationUpdates.push(() => dispatch(actions.query.updateNotificationKey(queryKey, newKey, activeBufferId))) + } else { + notificationUpdates.push(() => dispatch(actions.query.removeNotification(queryKey, activeBufferId))) + } + }) + + if (bufferExecutions && Object.keys(bufferExecutions).length === 0) { + delete executionRefs.current[activeBufferId] + } + executionRefs.current[activeBufferId] = bufferExecutions + + applyGlyphsAndLineMarkings(monaco, editor) + + const queriesToRun = getQueriesToRun(editor, queryOffsetsRef.current ?? []) + queriesToRunRef.current = queriesToRun + dispatch(actions.query.setQueriesToRun(queriesToRun)) + + contentJustChangedRef.current = false + notificationUpdates.forEach(update => update()) + }) + + editor.onDidChangeModel(() => { + setTimeout(() => { + if (monacoRef.current && editorRef.current) { + applyGlyphsAndLineMarkings(monacoRef.current, editorRef.current) + } + }, 10) + }) + + editor.onDidScrollChange((e) => { + if (scrollTimeoutRef.current) { + window.clearTimeout(scrollTimeoutRef.current) + } + + scrollTimeoutRef.current = window.setTimeout(() => { + const visibleRanges = editor.getVisibleRanges() + if (visibleRanges.length > 0) { + const firstRange = visibleRanges[0] + + const newVisibleLines = { + startLine: firstRange.startLineNumber, + endLine: firstRange.endLineNumber + } + + // Check if visible range has changed significantly (more than 100 lines) + const oldVisibleLines = visibleLinesRef.current + const startLineDiff = Math.abs(newVisibleLines.startLine - oldVisibleLines.startLine) + const endLineDiff = Math.abs(newVisibleLines.endLine - oldVisibleLines.endLine) + + visibleLinesRef.current = newVisibleLines + + if (startLineDiff > 100 || endLineDiff > 100) { + if (monacoRef.current && editorRef.current) { + applyGlyphsAndLineMarkings(monacoRef.current, editorRef.current, "scroll") + } + } + } + scrollTimeoutRef.current = null + }, 200) }) // Insert query, if one is found in the URL @@ -316,39 +745,336 @@ const MonacoEditor = () => { const query = params.get("query") const model = editor.getModel() if (query && model) { + const trimmedQuery = query.trim() // Find if the query is already in the editor - const matches = findMatches(model, query) + const matches = findMatches(model, trimmedQuery) if (matches && matches.length > 0) { editor.setSelection(matches[0].range) // otherwise, append the query } else { - appendQuery(editor, query, { appendAt: "end" }) + appendQuery(editor, trimmedQuery, { appendAt: "end" }) const newValue = editor.getValue() updateBuffer(activeBuffer.id as number, { value: newValue }) } } + const initialVisibleRanges = editor.getVisibleRanges() + if (initialVisibleRanges.length > 0) { + const firstRange = initialVisibleRanges[0] + + visibleLinesRef.current = { + startLine: firstRange.startLineNumber, + endLine: firstRange.endLineNumber + } + } + + // Initial decoration setup + applyGlyphsAndLineMarkings(monaco, editor) + const queriesToRun = getQueriesToRun(editor, queryOffsetsRef.current ?? []) + queriesToRunRef.current = queriesToRun + dispatch(actions.query.setQueriesToRun(queriesToRun)) + const executeQuery = params.get("executeQuery") if (executeQuery) { - toggleRunning() + if (queriesToRun.length > 1) { + handleTriggerRunScript() + } else { + toggleRunning() + } } } + const runIndividualQuery = async (query: Request, isLast: boolean): Promise => { + const editor = editorRef.current + const model = editorRef.current?.getModel() + if (!editor || !model) { + return { + success: false, + notification: null, + result: undefined, + } + } + + const effectiveQueryText = query.selection ? query.selection.queryText : query.query + const queryKey = createQueryKeyFromRequest(editor, query) + const activeBufferId = activeBuffer.id as number + let notification: Partial & { content: ReactNode, query: QueryKey } | null = null + dispatch(actions.query.setResult(undefined)) + + try { + const result = await quest.queryRaw(normalizeQueryText(effectiveQueryText), { limit: "0,1000", explain: true }) + + if (executionRefs.current[activeBufferId]) { + delete executionRefs.current[activeBufferId][queryKey] + if (Object.keys(executionRefs.current[activeBufferId]).length === 0) { + delete executionRefs.current[activeBufferId] + } + } + + if (result.type === QuestDB.Type.DDL || result.type === QuestDB.Type.DML) { + notification = { + query: queryKey, + content: , + } + } + + if (result.type === QuestDB.Type.NOTICE) { + notification = { + query: queryKey, + content: ( + + {result.notice} + {effectiveQueryText !== undefined && effectiveQueryText !== "" && `: ${effectiveQueryText}`} + + ), + sideContent: , + type: NotificationType.NOTICE, + } + } + + if (result.type === QuestDB.Type.DQL) { + setLastExecutedQuery(effectiveQueryText) + notification = { + query: queryKey, + jitCompiled: result.explain?.jitCompiled ?? false, + content: , + sideContent: , + } + } + + if (query.selection) { + if (!executionRefs.current[activeBufferId]) { + executionRefs.current[activeBufferId] = {} + } + + const queryStartOffset = getQueryStartOffset(editor, query) + executionRefs.current[activeBufferId][queryKey] = { + success: true, + selection: query.selection, + queryText: query.query, + startOffset: queryStartOffset, + endOffset: queryStartOffset + normalizeQueryText(query.query).length, + } + } + + if (isLast) { + dispatch(actions.query.setResult(result)) + } + + return { + success: true, + notification, + result, + } + } catch (_error: unknown) { + const error = _error as ErrorResult + + if (!executionRefs.current[activeBufferId]) { + executionRefs.current[activeBufferId] = {} + } + + const startOffset = getQueryStartOffset(editor, query) + executionRefs.current[activeBufferId][queryKey] = { + error, + queryText: query.query, + startOffset, + endOffset: startOffset + normalizeQueryText(query.query).length, + selection: query.selection, + } + + notification = { + query: queryKey, + content: {error.error}, + sideContent: , + type: NotificationType.ERROR, + } + return { + success: false, + notification, + result: undefined, + } + } + } + + const handleTriggerRunScript = (runAll?: boolean) => { + if (running === RunningType.SCRIPT) { + dispatch(actions.query.toggleRunning()) + } else if (running === RunningType.NONE) { + if (runAll) { + setScriptConfirmationOpen(true) + return + } + + const hasMultipleSelection = queriesToRunRef.current && queriesToRunRef.current.length > 1 + if (!hasMultipleSelection) { // Run all queries in the buffer + setScriptConfirmationOpen(true) + } else { // Run selected portion of each query one by one + dispatch(actions.query.toggleRunning(RunningType.SCRIPT)) + } + } + } + + const handleConfirmRunScript = () => { + setScriptConfirmationOpen(false) + queriesToRunRef.current = [] + dispatch(actions.query.toggleRunning(RunningType.SCRIPT)) + } + + const handleToggleDialog = (open: boolean) => { + setScriptConfirmationOpen(open) + if (!open) { + setTimeout(() => editorRef.current?.focus()) + } + } + + const handleRunScript = async () => { + let successfulQueries = 0 + let failedQueries = 0 + if (!editorRef.current) return + const queriesToRun = queriesToRunRef.current && queriesToRunRef.current.length > 1 ? queriesToRunRef.current : undefined + const runningAllQueries = !queriesToRun + // Clear all notifications & execution refs for the buffer + const activeBufferId = activeBuffer.id as number + if (runningAllQueries) { + dispatch(actions.query.cleanupBufferNotifications(activeBufferId)) + if (executionRefs.current[activeBufferId]) { + delete executionRefs.current[activeBufferId] + } + } + + isRunningScriptRef.current = true + + notificationTimeoutRef.current = window.setTimeout(() => { + dispatch( + actions.query.setActiveNotification({ + type: NotificationType.LOADING, + query: `${activeBufferRef.current.label}@${0}-${0}`, + content: ( + + Running queries... + + ), + createdAt: new Date(), + }), + ) + notificationTimeoutRef.current = null + }, 1000) + const startTime = Date.now() + + const queries = queriesToRun ?? getAllQueries(editorRef.current) + let lastQuery: Request | undefined + let individualQueryResults: Array = [] + + for (let i = 0; i < queries.length; i++) { + const query = queries[i] + const result = await runIndividualQuery(query, i === queries.length - 1) + individualQueryResults.push(result) + if (result.success) { + successfulQueries++ + } else { + failedQueries++ + } + lastQuery = query + if (scriptStopRef.current || (failedQueries > 0 && stopAfterFailureRef.current && runningAllQueries)) { + break + } + } + + const duration = (Date.now() - startTime) * 1e6 + + if (notificationTimeoutRef.current) { + window.clearTimeout(notificationTimeoutRef.current) + notificationTimeoutRef.current = null + } + + const lastResult = individualQueryResults[individualQueryResults.length - 1] + if (lastResult && lastResult.result?.type === QuestDB.Type.DQL) { + dispatch(actions.query.setResult(lastResult.result)) + eventBus.publish(EventType.MSG_QUERY_DATASET, lastResult.result) + } + + const querySchemaMessageTypes = [QuestDB.Type.DDL, QuestDB.Type.DML, QuestDB.Type.NOTICE] + const querySchemaMessage = individualQueryResults + .filter(result => result && result.result) + .find(result => result?.result?.type && querySchemaMessageTypes.includes(result.result.type)) + + if (querySchemaMessage) { + eventBus.publish(EventType.MSG_QUERY_SCHEMA, querySchemaMessage.result) + } + + individualQueryResults + .filter(result => result && result.notification) + .map(result => result.notification) + .forEach(notification => { + dispatch(actions.query.addNotification({ ...notification!, updateActiveNotification: false }, activeBuffer.id as number)) + }) + + if (editorRef.current && lastQuery) { + const position = { + lineNumber: lastQuery.row + 1, + column: lastQuery.column + } + editorRef.current.focus() + editorRef.current.revealPosition(position) + if (runningAllQueries) { + editorRef.current.setPosition(position, "script") + } + } + + const completedGracefully = queries.length === individualQueryResults.length + if (completedGracefully || (failedQueries > 0 && stopAfterFailureRef.current && runningAllQueries)) { + isRunningScriptRef.current = false + dispatch(actions.query.stopRunning()) + } + + const notificationPrefix = completedGracefully + ? `Running completed in ${formatTiming(duration)} with ` + : "Stopped after running " + + dispatch( + actions.query.addNotification({ + query: `${activeBufferRef.current.label}@${LINE_NUMBER_HARD_LIMIT + 1}-${LINE_NUMBER_HARD_LIMIT + 1}`, + content: + {notificationPrefix} + {successfulQueries > 0 ? `${successfulQueries} successful` : ""} + {successfulQueries > 0 && failedQueries > 0 ? " and " : ""} + {failedQueries > 0 ? `${failedQueries} failed` : ""} {failedQueries + successfulQueries > 1 ? " queries" : " query"} + , + type: completedGracefully ? NotificationType.SUCCESS : NotificationType.ERROR, + }, activeBufferRef.current.id as number), + ) + isRunningScriptRef.current = false + scriptStopRef.current = false + stopAfterFailureRef.current = true + } + useEffect(() => { - // Remove all errors for the buffers that have been deleted - Object.keys(errorRefs.current).map((key) => { + // Remove all execution information for the buffers that have been deleted + Object.keys(executionRefs.current).map((key) => { if (!buffers.find((b) => b.id === parseInt(key))) { - delete errorRefs.current[key] + delete executionRefs.current[key] } }) }, [buffers]) useEffect(() => { - activeBufferRef.current = activeBuffer - }, [activeBuffer]) + activeNotificationRef.current = activeNotification + }, [activeNotification]) useEffect(() => { - if (!running.value && request) { + const gridNotificationKeySuffix = `@${LINE_NUMBER_HARD_LIMIT + 1}-${LINE_NUMBER_HARD_LIMIT + 1}` + queryNotificationsRef.current = queryNotifications + if (monacoRef.current + && editorRef.current + && !contentJustChangedRef.current + && !activeNotification?.query.endsWith(gridNotificationKeySuffix) + ) { + applyGlyphsAndLineMarkings(monacoRef.current, editorRef.current) + } + }, [queryNotifications]) + + useEffect(() => { + if (running === RunningType.NONE && request) { quest.abort() dispatch(actions.query.stopRunning()) setRequest(undefined) @@ -356,27 +1082,39 @@ const MonacoEditor = () => { }, [request, quest, dispatch, running]) useEffect(() => { - runningValueRef.current = running.value - if (running.value && editorRef?.current) { + runningValueRef.current = running + if (![RunningType.NONE, RunningType.SCRIPT].includes(running) && editorRef?.current) { if (monacoRef?.current) { clearModelMarkers(monacoRef.current, editorRef.current) } if (monacoRef?.current && editorRef?.current) { - renderLineMarkings(monacoRef.current, editorRef?.current) + applyGlyphsAndLineMarkings(monacoRef.current, editorRef.current) } - const request = running.isRefresh + const request = running === RunningType.REFRESH ? getQueryRequestFromLastExecutedQuery(lastExecutedQuery) : getQueryRequestFromEditor(editorRef.current) + + const isRunningExplain = running === RunningType.EXPLAIN if (request?.query) { + const parentQuery = request.query + const parentQueryKey = createQueryKeyFromRequest(editorRef.current, request) + const originalQueryText = request.selection ? request.selection.queryText : request.query + let queryToRun = originalQueryText + if (isRunningExplain) { + queryToRun = `EXPLAIN ${originalQueryText}` + } + // give the notification a slight delay to prevent flashing for fast queries - setTimeout(() => { - if (runningValueRef.current) { + notificationTimeoutRef.current = window.setTimeout(() => { + if (runningValueRef.current && requestRef.current && editorRef.current) { dispatch( actions.query.addNotification({ type: NotificationType.LOADING, + query: parentQueryKey, + isExplain: isRunningExplain, content: ( Running... @@ -385,17 +1123,50 @@ const MonacoEditor = () => { ), - sideContent: , - }), + sideContent: , + }, activeBuffer.id as number), ) } + notificationTimeoutRef.current = null }, 1000) void quest - .queryRaw(request.query, { limit: "0,1000", explain: true }) + .queryRaw(normalizeQueryText(queryToRun), { limit: "0,1000", explain: true }) .then((result) => { + if (notificationTimeoutRef.current) { + window.clearTimeout(notificationTimeoutRef.current) + notificationTimeoutRef.current = null + } + setRequest(undefined) - delete errorRefs.current[activeBuffer.id as number] + if (!editorRef.current) return + + const activeBufferId = activeBuffer.id as number + + if (executionRefs.current[activeBufferId] && editorRef.current) { + delete executionRefs.current[activeBufferId][parentQueryKey] + if (Object.keys(executionRefs.current[activeBufferId]).length === 0) { + delete executionRefs.current[activeBufferId] + } + } + + if (request.selection) { + const model = editorRef.current.getModel() + if (model) { + if (!executionRefs.current[activeBufferId]) { + executionRefs.current[activeBufferId] = {} + } + + const queryStartOffset = getQueryStartOffset(editorRef.current, request) + executionRefs.current[activeBufferId][parentQueryKey] = { + success: true, + selection: request.selection, + queryText: parentQuery, + startOffset: queryStartOffset, + endOffset: queryStartOffset + normalizeQueryText(parentQuery).length, + } + } + } dispatch(actions.query.stopRunning()) dispatch(actions.query.setResult(result)) @@ -406,8 +1177,10 @@ const MonacoEditor = () => { ) { dispatch( actions.query.addNotification({ - content: , - }), + query: parentQueryKey, + isExplain: isRunningExplain, + content: , + }, activeBuffer.id as number), ) eventBus.publish(EventType.MSG_QUERY_SCHEMA) } @@ -415,92 +1188,127 @@ const MonacoEditor = () => { if (result.type === QuestDB.Type.NOTICE) { dispatch( actions.query.addNotification({ + query: parentQueryKey, + isExplain: isRunningExplain, content: ( - + {result.notice} - {result.query !== undefined && - result.query !== "" && - `: ${result.query}`} + {queryToRun !== undefined && + queryToRun !== "" && + `: ${queryToRun}`} ), + sideContent: , type: NotificationType.NOTICE, - }), + }, activeBuffer.id as number), ) eventBus.publish(EventType.MSG_QUERY_SCHEMA) } if (result.type === QuestDB.Type.DQL) { - setLastExecutedQuery(request.query) + setLastExecutedQuery(queryToRun) dispatch( actions.query.addNotification({ + query: parentQueryKey, + isExplain: isRunningExplain, jitCompiled: result.explain?.jitCompiled ?? false, content: ( ), - sideContent: , - }), + sideContent: , + }, activeBuffer.id as number), ) eventBus.publish(EventType.MSG_QUERY_DATASET, result) } }) .catch((error: ErrorResult) => { - errorRefs.current[activeBuffer.id as number] = { - error, + if (notificationTimeoutRef.current) { + window.clearTimeout(notificationTimeoutRef.current) + notificationTimeoutRef.current = null } + setRequest(undefined) dispatch(actions.query.stopRunning()) - dispatch( - actions.query.addNotification({ - content: {error.error}, - sideContent: , - type: NotificationType.ERROR, - }), - ) if (editorRef?.current && monacoRef?.current) { + // For error positioning, we need to use the original request (without EXPLAIN prefix) + // but adjust the error position if it was an EXPLAIN query + let adjustedErrorPosition = error.position + if (isRunningExplain) { + // Adjust error position to account for removed "EXPLAIN " prefix + adjustedErrorPosition = Math.max(0, error.position - 8) + } + if (request.selection) { + adjustedErrorPosition += parentQuery.indexOf(request.selection.queryText) + } + const errorRange = getErrorRange( editorRef.current, request, - error.position, + adjustedErrorPosition, ) - if (errorRange) { - errorRefs.current[activeBuffer.id as number].range = errorRange - setErrorMarker( - monacoRef?.current, - editorRef.current, - errorRange, - error.error, - ) + let errorToStore = { ...error, position: adjustedErrorPosition } + + const parentQueryKey = createQueryKeyFromRequest(editorRef.current, request) + const activeBufferId = activeBuffer.id as number + if (!executionRefs.current[activeBufferId]) { + executionRefs.current[activeBufferId] = {} + } + + const startOffset = getQueryStartOffset(editorRef.current, request) + executionRefs.current[activeBufferId][parentQueryKey] = { + error: errorToStore, + selection: request.selection, + queryText: parentQuery, + startOffset, + endOffset: startOffset + normalizeQueryText(parentQuery).length, + } + + if (errorRange) { editorRef?.current.focus() - editorRef?.current.setPosition({ - lineNumber: errorRange.startLineNumber, - column: errorRange.startColumn, - }) + if (!request.selection) { + editorRef?.current.setPosition({ + lineNumber: errorRange.startLineNumber, + column: errorRange.startColumn, + }) + } editorRef?.current.revealPosition({ lineNumber: errorRange.startLineNumber, column: errorRange.endColumn, }) } + + dispatch( + actions.query.addNotification({ + query: parentQueryKey, + isExplain: isRunningExplain, + content: {error.error}, + sideContent: , + type: NotificationType.ERROR, + }, activeBuffer.id as number), + ) } }) setRequest(request) } else { dispatch(actions.query.stopRunning()) } - } else { - if (monacoRef?.current && editorRef?.current) { - renderLineMarkings(monacoRef?.current, editorRef?.current) - } + } else if (running === RunningType.SCRIPT) { + handleRunScript() + } else if (running === RunningType.NONE && isRunningScriptRef.current) { + quest.abort() + scriptStopRef.current = true + eventBus.publish(EventType.MSG_QUERY_SCHEMA) } }, [running]) useEffect(() => { requestRef.current = request if (monacoRef?.current && editorRef?.current) { - renderLineMarkings(monacoRef?.current, editorRef?.current) + applyGlyphsAndLineMarkings(monacoRef.current, editorRef.current) } }, [request]) @@ -525,46 +1333,144 @@ const MonacoEditor = () => { }, [tables, columns, monacoRef, editorReady]) useEffect(() => { + activeBufferRef.current = activeBuffer + currentBufferValueRef.current = activeBuffer.value if (monacoRef.current && editorRef.current) { clearModelMarkers(monacoRef.current, editorRef.current) + + applyGlyphsAndLineMarkings(monacoRef.current, editorRef.current) } }, [activeBuffer]) useEffect(() => { window.addEventListener("focus", setCompletionProvider) - return () => window.removeEventListener("focus", setCompletionProvider) + return () => { + window.removeEventListener("focus", setCompletionProvider) + if (cursorChangeTimeoutRef.current) { + window.clearTimeout(cursorChangeTimeoutRef.current) + } + + if (scrollTimeoutRef.current) { + window.clearTimeout(scrollTimeoutRef.current) + } + + if (notificationTimeoutRef.current) { + window.clearTimeout(notificationTimeoutRef.current) + } + + if (decorationCollectionRef.current) { + decorationCollectionRef.current.clear() + } + } }, []) return ( - - { - updateBuffer(activeBuffer.id as number, { value }) - }} - options={{ - // initially null, but will be set during onMount with editor.setModel - model: null, - fixedOverflowWidgets: true, - fontSize: 14, - fontFamily: theme.fontMonospace, - glyphMargin: true, - renderLineHighlight: "gutter", - minimap: { - enabled: false, - }, - selectOnLineNumbers: false, - scrollBeyondLastLine: false, - tabSize: 2, - lineNumbersMinChars: lineNumbersMinChars, + <> + + + { + const lineCount = editorRef.current?.getModel()?.getLineCount() + if (lineCount && lineCount > LINE_NUMBER_HARD_LIMIT) { + if (editorRef.current && currentBufferValueRef.current !== undefined) { + editorRef.current.setValue(currentBufferValueRef.current) + } + toast.error("Maximum line limit reached") + return + } + currentBufferValueRef.current = value + updateBuffer(activeBuffer.id as number, { value }) + }} + options={{ + // initially null, but will be set during onMount with editor.setModel + model: null, + fixedOverflowWidgets: true, + fontSize: 14, + lineHeight: 24, + fontFamily: theme.fontMonospace, + glyphMargin: true, + renderLineHighlight: "gutter", + useShadowDOM: false, + minimap: { + enabled: false, + }, + selectOnLineNumbers: false, + scrollBeyondLastLine: false, + tabSize: 2, + lineNumbersMinChars: lineNumbersMinChars, + }} + theme="vs-dark" + /> + + + + { + setDropdownOpen(open) + if (!open) { + dropdownPositionRef.current = null + dropdownQueriesRef.current = [] + isContextMenuDropdownRef.current = false + } }} - theme="vs-dark" + positionRef={dropdownPositionRef} + queriesRef={dropdownQueriesRef} + isContextMenuRef={isContextMenuDropdownRef} + onRunQuery={handleRunQuery} + onExplainQuery={handleExplainQuery} /> - - + + + + + + + + handleToggleDialog(false)} + onInteractOutside={() => handleToggleDialog(false)} + > + + Run all queries + + + + + You are about to run all queries in this tab. This action may modify or delete your data permanently. + + + + + + + + + handleToggleDialog(false)}> + Cancel + + + + + Run all queries + + + + + + ) } diff --git a/packages/web-console/src/scenes/Editor/Monaco/legacy-event-bus.ts b/packages/web-console/src/scenes/Editor/Monaco/legacy-event-bus.ts index 9f4c1a997..78e9f5d4c 100644 --- a/packages/web-console/src/scenes/Editor/Monaco/legacy-event-bus.ts +++ b/packages/web-console/src/scenes/Editor/Monaco/legacy-event-bus.ts @@ -25,6 +25,7 @@ import { appendQuery, AppendQueryOptions } from "./utils" import { eventBus } from "../../../modules/EventBus" import { EventType } from "../../../modules/EventBus/types" import type { editor } from "monaco-editor" +import { RunningType } from "../../../store/Query/types" export const registerLegacyEventBusEvents = ({ editor, @@ -33,7 +34,7 @@ export const registerLegacyEventBusEvents = ({ }: { editor: editor.IStandaloneCodeEditor insertTextAtCursor: (text: string) => void - toggleRunning: (isRefresh?: boolean) => void + toggleRunning: (runningType?: RunningType) => void }) => { eventBus.subscribe(EventType.MSG_EDITOR_INSERT_COLUMN, (column) => { if (column) { @@ -53,7 +54,7 @@ export const registerLegacyEventBusEvents = ({ ) eventBus.subscribe(EventType.MSG_QUERY_EXEC, () => { - toggleRunning(true) + toggleRunning(RunningType.REFRESH) }) eventBus.subscribe(EventType.MSG_EDITOR_FOCUS, () => { diff --git a/packages/web-console/src/scenes/Editor/Monaco/utils.ts b/packages/web-console/src/scenes/Editor/Monaco/utils.ts index 7de2d029f..5e56a1605 100644 --- a/packages/web-console/src/scenes/Editor/Monaco/utils.ts +++ b/packages/web-console/src/scenes/Editor/Monaco/utils.ts @@ -21,19 +21,38 @@ * limitations under the License. * ******************************************************************************/ -import { editor, IPosition, IRange } from "monaco-editor" -import { Monaco } from "@monaco-editor/react" +import type { editor, IPosition, IRange } from "monaco-editor" +import type { Monaco } from "@monaco-editor/react" +import type { ErrorResult } from "../../../utils" type IStandaloneCodeEditor = editor.IStandaloneCodeEditor export const QuestDBLanguageName: string = "questdb-sql" +export type QueryKey = `${string}@${number}-${number}` + export type Request = Readonly<{ query: string row: number column: number + endRow: number + endColumn: number + selection?: { + startOffset: number + endOffset: number + queryText: string + } }> +type SqlTextItem = { + row: number + col: number + position: number + endRow: number + endCol: number + limit: number +} + export const stripSQLComments = (text: string): string => text.replace(/(? { if (group) { @@ -54,29 +73,132 @@ export const getSelectedText = ( return model && selection ? model.getValueInRange(selection) : undefined } -export const getQueryFromCursor = ( +export const getQueriesToRun = ( editor: IStandaloneCodeEditor, -): Request | undefined => { - const text = stripSQLComments( - editor.getValue({ preserveBOM: false, lineEnding: "\n" }), - ) - const position = editor.getPosition() + queryOffsets: { startOffset: number, endOffset: number }[], +): Request[] => { + const model = editor.getModel() + if (!model) return [] - let row = 0 + const selection = editor.getSelection() + const selectedText = selection ? model.getValueInRange(selection) : undefined + if (!selection || !selectedText) { + const queryInCursor = getQueryFromCursor(editor) + if (queryInCursor) { + return [queryInCursor] + } + return [] + } + let selectionStartOffset = model.getOffsetAt(selection.getStartPosition()) + let selectionEndOffset = model.getOffsetAt(selection.getEndPosition()) + + const normalizedSelectedText = normalizeQueryText(selectedText) + + if (stripSQLComments(normalizedSelectedText).length > 0) { + selectionStartOffset += selectedText.indexOf(normalizedSelectedText) + selectionEndOffset = selectionStartOffset + normalizedSelectedText.length + } - let column = 0 + const firstQueryOffsets = queryOffsets.find(offset => offset.endOffset >= selectionStartOffset) + const lastQueryOffsets = queryOffsets.filter(offset => offset.startOffset <= selectionEndOffset).pop() + if (!firstQueryOffsets || !lastQueryOffsets) { + return [] + } + + const queries = getQueriesInRange(editor, model.getPositionAt(firstQueryOffsets.startOffset), model.getPositionAt(lastQueryOffsets.endOffset)) + const requests = queries.map(query => { + const clampedSelection = clampRange(model, selection, { + startOffset: model.getOffsetAt({ lineNumber: query.row + 1, column: query.column }), + endOffset: model.getOffsetAt({ lineNumber: query.endRow + 1, column: query.endColumn }), + }) + const clampedSelectionText = model.getValueInRange(clampedSelection) + return stripSQLComments(normalizeQueryText(clampedSelectionText)) + ? { + query: query.query, + row: query.row, + column: query.column, + endRow: query.endRow, + endColumn: query.endColumn, + selection: { + startOffset: model.getOffsetAt({ lineNumber: clampedSelection.startLineNumber, column: clampedSelection.startColumn }), + endOffset: model.getOffsetAt({ lineNumber: clampedSelection.endLineNumber, column: clampedSelection.endColumn }), + queryText: clampedSelectionText + } + } + : undefined + }) + return requests.filter(Boolean) as Request[] +} + +export const getQueriesFromPosition = ( + editor: IStandaloneCodeEditor, + editorPosition: IPosition, + startPosition?: IPosition +): { sqlTextStack: SqlTextItem[]; nextSql: SqlTextItem | null } => { + const text = editor.getValue({ preserveBOM: false, lineEnding: "\n" }) + + if (!text || !stripSQLComments(text)) { + return { sqlTextStack: [], nextSql: null } + } + + const position = { + row: editorPosition.lineNumber - 1, + column: editorPosition.column, + } + + // Calculate starting position - default to beginning if not provided + const start = startPosition ? { + row: startPosition.lineNumber - 1, + column: startPosition.column, + } : { row: 0, column: 1 } + + // Convert start position to character index + let startCharIndex = 0 + if (startPosition) { + const lines = text.split('\n') + const maxRow = Math.min(start.row, lines.length - 1) + for (let i = 0; i < maxRow; i++) { + if (lines[i] !== undefined) { + startCharIndex += lines[i].length + 1 // +1 for newline character + } + } + if (lines[maxRow] !== undefined) { + startCharIndex += Math.min(start.column - 1, lines[maxRow].length) + } + } + + let row = start.row + let column = start.column const sqlTextStack = [] - let startRow = 0 - let startCol = 0 - let startPos = -1 + let startRow = start.row + let startCol = start.column + let startPos = startCharIndex - 1 let nextSql = null let inQuote = false + let singleLineCommentStack: number[] = [] + let multiLineCommentStack: number[] = [] + let inSingleLineComment = false + let inMultiLineComment = false + + while ( + startCharIndex < text.length && + (text[startCharIndex] === "\n" || text[startCharIndex] === " ") + ) { + if (text[startCharIndex] === "\n") { + row++ + startRow++ + column = 1 + startCol = 1 + } else { + column++ + startCol++ + } + startCharIndex++ + } + startPos = startCharIndex - if (!position) return - - let i = 0; - + let i = startCharIndex for (; i < text.length; i++) { if (nextSql !== null) { break @@ -86,14 +208,14 @@ export const getQueryFromCursor = ( switch (char) { case ";": { - if (inQuote) { + if (inQuote || inSingleLineComment || inMultiLineComment) { column++ - continue + break } if ( - row < position.lineNumber - 1 || - (row === position.lineNumber - 1 && column < position.column - 1) + row < position.row || + (row === position.row && column < position.column) ) { sqlTextStack.push({ row: startRow, @@ -104,11 +226,10 @@ export const getQueryFromCursor = ( limit: i, }) startRow = row - startCol = column + startCol = column + 1 startPos = i + 1 column++ } else { - // empty queries, aka ;; , make sql.length === 0 nextSql = { row: startRow, col: startCol, @@ -122,10 +243,9 @@ export const getQueryFromCursor = ( } case " ": { - // ignore leading space if (startPos === i) { startRow = row - startCol = column + startCol = column + 1 startPos = i + 1 } @@ -134,9 +254,16 @@ export const getQueryFromCursor = ( } case "\n": { + if (inSingleLineComment) { + inSingleLineComment = false + if (startPos === i - 1) { + startPos = i + startRow = row + startCol = column + } + } row++ - column = 0 - + column = 1 if (startPos === i) { startRow = row startCol = column @@ -151,25 +278,92 @@ export const getQueryFromCursor = ( break } + case "-": { + if (!inMultiLineComment && !inQuote) { + singleLineCommentStack.push(i) + if (singleLineCommentStack.length === 2) { + if (singleLineCommentStack[0] + 1 === singleLineCommentStack[1]) { + if (startPos === i - 1) { + startPos = i + startRow = row + startCol = column + } + singleLineCommentStack = [] + inSingleLineComment = true + } else { + singleLineCommentStack.shift() + } + } + } + column++ + break + } + + case "/": { + if (!inMultiLineComment && !inSingleLineComment && !inQuote) { + if (multiLineCommentStack.length === 0) { + multiLineCommentStack.push(i) + } else { + multiLineCommentStack = [i] + } + } + if (inMultiLineComment) { + if (multiLineCommentStack.length === 1 && multiLineCommentStack[0] + 1 === i) { + if (startPos === i - 1) { + startPos = i + 1 + startRow = row + startCol = column + 1 + } + multiLineCommentStack = [] + inMultiLineComment = false + } + } + column++ + break + } + + case "*": { + if (!inMultiLineComment && !inSingleLineComment) { + if (multiLineCommentStack.length === 1 && multiLineCommentStack[0] + 1 === i) { + if (startPos === i - 1) { + startPos = i + startRow = row + startCol = column + } + multiLineCommentStack = [] + inMultiLineComment = true + } else if (multiLineCommentStack.length > 0) { + multiLineCommentStack = [] + } + } + if (inMultiLineComment) { + multiLineCommentStack = [i] + } + column++ + break + } + default: { column++ break } } + if ((inSingleLineComment || inMultiLineComment) && startPos === i - 1) { + startPos = i + startRow = row + startCol = column + } } // lastStackItem is the last query that is completed before the current cursor position. // nextSql is the next query that is not completed before the current cursor position, or started after the current cursor position. - const normalizedCurrentRow = position.lineNumber - 1 - const lastStackItem = sqlTextStack.length > 0 ? sqlTextStack[sqlTextStack.length - 1] : null - if (!nextSql) { - const sqlText = startPos === - 1 ? text : text.substring(startPos) + const sqlText = startPos === - 1 ? text.substring(startCharIndex) : text.substring(startPos) if (sqlText.length > 0) { nextSql = { row: startRow, col: startCol, - position: startPos === -1 ? 0 : startPos, + position: startPos === -1 ? startCharIndex : startPos, endRow: row, endCol: column, limit: i, @@ -177,6 +371,32 @@ export const getQueryFromCursor = ( } } + const filteredSqlTextStack = sqlTextStack.filter(item => { + return item.row !== item.endRow || item.col !== item.endCol + }) + + const filteredNextSql = nextSql && (nextSql.row !== nextSql.endRow || nextSql.col !== nextSql.endCol) + ? nextSql + : null + + return { sqlTextStack: filteredSqlTextStack, nextSql: filteredNextSql } +} + +export const getQueryFromCursor = ( + editor: IStandaloneCodeEditor, +): Request | undefined => { + const position = editor.getPosition() + const text = editor.getValue({ preserveBOM: false, lineEnding: "\n" }) + + if (!text || !stripSQLComments(text) || !position) { + return + } + + const { sqlTextStack, nextSql } = getQueriesFromPosition(editor, position) + + const normalizedCurrentRow = position.lineNumber - 1 + const lastStackItem = sqlTextStack.length > 0 ? sqlTextStack[sqlTextStack.length - 1] : null + const lastStackItemRowRange = lastStackItem ? { start: lastStackItem.row, end: lastStackItem.endRow, @@ -193,50 +413,161 @@ export const getQueryFromCursor = ( query: text.substring(lastStackItem!.position, lastStackItem!.limit), row: lastStackItem!.row, column: lastStackItem!.col, + endRow: lastStackItem!.endRow, + endColumn: lastStackItem!.endCol } } else if (isInNextSqlRowRange && !isInLastStackItemRowRange) { return { query: text.substring(nextSql!.position, nextSql!.limit), row: nextSql!.row, column: nextSql!.col, + endRow: nextSql!.endRow, + endColumn: nextSql!.endCol } } else if (isInLastStackItemRowRange && isInNextSqlRowRange) { const lastStackItemEndCol = lastStackItem!.endCol - const normalizedCurrentCol = position.column - 1 - if (normalizedCurrentCol > lastStackItemEndCol + 1) { + const normalizedCurrentCol = position.column + if (normalizedCurrentCol > lastStackItemEndCol) { return { query: text.substring(nextSql!.position, nextSql!.limit), row: nextSql!.row, column: nextSql!.col, + endRow: nextSql!.endRow, + endColumn: nextSql!.endCol } } return { query: text.substring(lastStackItem!.position, lastStackItem!.limit), row: lastStackItem!.row, column: lastStackItem!.col, + endRow: lastStackItem!.endRow, + endColumn: lastStackItem!.endCol + } + } +} + +export const getAllQueries = (editor: IStandaloneCodeEditor): Request[] => { + const position = getLastPosition(editor) + const text = editor.getValue({ preserveBOM: false, lineEnding: "\n" }) + + if (!text || !stripSQLComments(text) || !position) { + return [] + } + + const { sqlTextStack, nextSql } = getQueriesFromPosition(editor, position) + const stackQueries = sqlTextStack.map(item => ({ + query: text.substring(item.position, item.limit), + row: item.row, + column: item.col, + endRow: item.endRow, + endColumn: item.endCol + })) + const nextSqlQuery = nextSql ? { + query: text.substring(nextSql.position, nextSql.limit), + row: nextSql.row, + column: nextSql.col, + endRow: nextSql.endRow, + endColumn: nextSql.endCol + } : null + return [...stackQueries, ...(nextSqlQuery ? [nextSqlQuery] : [])] +} + +export const getQueriesInRange = ( + editor: IStandaloneCodeEditor, + startPosition: IPosition, + endPosition: IPosition +): Request[] => { + const text = editor.getValue({ preserveBOM: false, lineEnding: "\n" }) + if (!text || !stripSQLComments(text) || !startPosition || !endPosition) { + return [] + } + + const { sqlTextStack, nextSql } = getQueriesFromPosition(editor, endPosition, startPosition) + + const stackQueries = sqlTextStack.map(item => ({ + query: text.substring(item.position, item.limit), + row: item.row, + column: item.col, + endRow: item.endRow, + endColumn: item.endCol + })) + + const nextSqlQuery = nextSql ? { + query: text.substring(nextSql.position, nextSql.limit), + row: nextSql.row, + column: nextSql.col, + endRow: nextSql.endRow, + endColumn: nextSql.endCol + } : null + + return [...stackQueries, ...(nextSqlQuery ? [nextSqlQuery] : [])] +} + +export const getQueriesStartingFromLine = ( + editor: IStandaloneCodeEditor, + lineNumber: number, + queryOffsets: { startOffset: number, endOffset: number }[] +): Request[] => { + const model = editor.getModel() + if (!model || !queryOffsets) return [] + + const queries: Request[] = [] + + for (const offset of queryOffsets) { + const startPosition = model.getPositionAt(offset.startOffset) + if (startPosition.lineNumber === lineNumber) { + const endPosition = model.getPositionAt(offset.endOffset) + const queryText = model.getValueInRange({ + startLineNumber: startPosition.lineNumber, + startColumn: startPosition.column, + endLineNumber: endPosition.lineNumber, + endColumn: endPosition.column + }) + + queries.push({ + query: queryText, + row: startPosition.lineNumber - 1, + column: startPosition.column, + endRow: endPosition.lineNumber - 1, + endColumn: endPosition.column + }) } } + + return queries } export const getQueryFromSelection = ( editor: IStandaloneCodeEditor, ): Request | undefined => { + const model = editor.getModel() + if (!model) return + const selection = editor.getSelection() const selectedText = getSelectedText(editor) - if (selection && selectedText) { - let n = selectedText.length - let column = selectedText.charAt(n) - - while (n > 0 && (column === " " || column === "\n" || column === ";")) { - n-- - column = selectedText.charAt(n) - } - if (n > 0) { - return { - query: selectedText.substr(0, n + 1), - row: selection.startLineNumber - 1, - column: selection.startColumn, + if (selection && selectedText) { + let selectionStartOffset = model.getOffsetAt(selection.getStartPosition()) + let selectionEndOffset = model.getOffsetAt(selection.getEndPosition()) + + const normalizedSelectedText = normalizeQueryText(selectedText) + + if (stripSQLComments(normalizedSelectedText).length > 0) { + selectionStartOffset += selectedText.indexOf(normalizedSelectedText) + selectionEndOffset = selectionStartOffset + normalizedSelectedText.length + const startPos = model.getPositionAt(selectionStartOffset) + const endPos = model.getPositionAt(selectionEndOffset) + editor.setSelection({ startLineNumber: startPos.lineNumber, endLineNumber: endPos.lineNumber, startColumn: startPos.column, endColumn: endPos.column }) + const parentQuery = getQueryFromCursor(editor) + if (parentQuery) { + return { + ...parentQuery, + selection: { + startOffset: selectionStartOffset, + endOffset: selectionEndOffset, + queryText: normalizedSelectedText + } + } } } } @@ -245,11 +576,38 @@ export const getQueryFromSelection = ( export const getQueryRequestFromEditor = ( editor: IStandaloneCodeEditor, ): Request | undefined => { + let request: Request | undefined const selectedText = getSelectedText(editor) - if (selectedText) { - return getQueryFromSelection(editor) + const strippedNormalizedSelectedText = selectedText + ? stripSQLComments(normalizeQueryText(selectedText)) + : undefined + + if (strippedNormalizedSelectedText) { + request = getQueryFromSelection(editor) + } else { + request = getQueryFromCursor(editor) } - return getQueryFromCursor(editor) + + if (!request) return + + let normalizedRequest: Request | undefined + + if (request.selection) { + normalizedRequest = { + ...request, + selection: { + ...request.selection, + queryText: normalizeQueryText(request.selection.queryText), + } + } + } else { + normalizedRequest = { + ...request, + query: normalizeQueryText(request.query), + } + } + + return normalizedRequest } export const getQueryRequestFromLastExecutedQuery = ( @@ -258,7 +616,9 @@ export const getQueryRequestFromLastExecutedQuery = ( return { query, row: 0, - column: 0, + column: 1, + endRow: 0, + endColumn: 1 } } @@ -281,17 +641,7 @@ export const getErrorRange = ( const position = toTextPosition(request, errorPosition) const model = editor.getModel() if (model) { - const selection = editor.getSelection() - const selectedText = getSelectedText(editor) - let wordAtPosition - if (selection && selectedText) { - wordAtPosition = model.getWordAtPosition({ - column: selection.startColumn + position.column, - lineNumber: position.lineNumber, - }) - } else { - wordAtPosition = model.getWordAtPosition(position) - } + const wordAtPosition = model.getWordAtPosition(position) if (wordAtPosition) { return { startColumn: wordAtPosition.startColumn, @@ -304,6 +654,24 @@ export const getErrorRange = ( return null } +export const clampRange = (model: editor.ITextModel, range: IRange, selection: { startOffset: number, endOffset: number }) => { + const rangeStartOffset = model.getOffsetAt({ lineNumber: range.startLineNumber, column: range.startColumn }) + const rangeEndOffset = model.getOffsetAt({ lineNumber: range.endLineNumber, column: range.endColumn }) + + const clampedStartOffset = Math.max(rangeStartOffset, selection.startOffset) + const clampedEndOffset = Math.min(rangeEndOffset, selection.endOffset) + + const clampedStartPosition = model.getPositionAt(clampedStartOffset) + const clampedEndPosition = model.getPositionAt(clampedEndOffset) + + return { + startLineNumber: clampedStartPosition.lineNumber, + endLineNumber: clampedEndPosition.lineNumber, + startColumn: clampedStartPosition.column, + endColumn: clampedEndPosition.column, + } +} + export const insertTextAtCursor = ( editor: IStandaloneCodeEditor, text: string, @@ -481,7 +849,7 @@ const getInsertPosition = ({ return { lineStart: position.lineNumber + lineStartOffset, lineEnd: position.lineNumber + newQueryLines.length, - columnStart: 0, + columnStart: 1, columnEnd: newQueryLines[newQueryLines.length - 1].length + 1, } } @@ -491,7 +859,7 @@ const getInsertPosition = ({ return { lineStart, lineEnd: lineStart + newQueryLines.length, - columnStart: 0, + columnStart: 1, columnEnd: newQueryLines[newQueryLines.length - 1].length + 1, } } @@ -534,7 +902,7 @@ export const appendQuery = ( positionInsert.lineStart + selectStartOffset + (newQueryLines.length - 1), - columnStart: 0, + columnStart: 1, columnEnd: positionInsert.columnEnd, } @@ -572,40 +940,18 @@ export const clearModelMarkers = ( } } -export const setErrorMarker = ( - monaco: Monaco, - editor: IStandaloneCodeEditor, - errorRange: IRange, - message: string, -) => { - const model = editor.getModel() - - if (model) { - monaco.editor.setModelMarkers(model, QuestDBLanguageName, [ - { - message, - severity: monaco.MarkerSeverity.Error, - startLineNumber: errorRange.startLineNumber, - endLineNumber: errorRange.endLineNumber, - startColumn: errorRange.startColumn, - endColumn: errorRange.endColumn, - }, - ]) - } -} - export const toTextPosition = ( request: Request, position: number, ): IPosition => { const end = Math.min(position, request.query.length) let row = 0 - let column = 0 + let column = 1 for (let i = 0; i < end; i++) { if (request.query.charAt(i) === "\n") { row++ - column = 0 + column = 1 } else { column++ } @@ -613,8 +959,16 @@ export const toTextPosition = ( return { lineNumber: row + 1 + request.row, - column: (row === 0 ? column + request.column : column) + 1, + column: (row === 0 ? column + request.column : column), + } +} + +export const normalizeQueryText = (query: string) => { + let result = query.trim() + if (result.endsWith(";")) { + result = result.slice(0, -1) } + return result.trim() } export const findMatches = (model: editor.ITextModel, needle: string) => @@ -626,3 +980,126 @@ export const findMatches = (model: editor.ITextModel, needle: string) => null /* wordSeparators */, true /* captureMatches */, ) ?? null + +export const getLastPosition = (editor: IStandaloneCodeEditor): IPosition | undefined => { + const model = editor.getModel() + if (!model) return undefined + + const lastLineNumber = model.getLineCount() + const lastLineContent = model.getLineContent(lastLineNumber) + + return { + lineNumber: lastLineNumber, + column: lastLineContent.length + 1, + } +} + +export const getQueryStartOffset = ( + editor: IStandaloneCodeEditor, + request: Request +): number => { + const model = editor.getModel() + if (!model) return 0 + + return model.getOffsetAt({ + lineNumber: request.row + 1, + column: request.column, + }) +} + +export const createQueryKey = (queryText: string, startOffset: number): QueryKey => { + const normalizedText = normalizeQueryText(queryText) + return `${normalizedText}@${startOffset}-${startOffset + normalizedText.length}` as QueryKey +} + +export const parseQueryKey = (queryKey: QueryKey): { queryText: string, startOffset: number, endOffset: number } => { + const [queryText, offsets] = queryKey.split('@') + const [startOffset, endOffset] = offsets.split('-') + return { queryText, startOffset: parseInt(startOffset, 10), endOffset: parseInt(endOffset, 10) } +} + +export const shiftOffset = (offset: number, changeOffset: number, delta: number): number => { + return offset >= changeOffset ? offset + delta : offset +} + +export const validateQueryAtOffset = ( + editor: IStandaloneCodeEditor, + queryText: string, + offset: number +): boolean => { + const model = editor.getModel() + if (!model) return false + + const totalLength = model.getValueLength() + if (offset < 0 || offset >= totalLength) return false + + const offsetPosition = model.getPositionAt(offset) + + const queryInEditor = getQueriesInRange(editor, offsetPosition, offsetPosition)[0] + if (!queryInEditor) return false + + return normalizeQueryText(queryInEditor.query) === normalizeQueryText(queryText) +} + +export const createQueryKeyFromRequest = ( + editor: IStandaloneCodeEditor, + request: Request +): QueryKey => { + const startOffset = getQueryStartOffset(editor, request) + return createQueryKey(request.query, startOffset) +} + +export const setErrorMarkerForQuery = ( + monaco: any, + editor: IStandaloneCodeEditor, + bufferExecutions: Record, + query?: Request +) => { + const model = editor.getModel() + if (!model) return + + const markers: any[] = [] + + if (query) { + const queryKey = createQueryKeyFromRequest(editor, query) + const executionData = bufferExecutions[queryKey] + + if (executionData && executionData.error) { + const { error, selection } = executionData + + const errorRange = getErrorRange(editor, query, error.position) + + if (errorRange) { + const clampedErrorRange = selection ? clampRange(model, errorRange, selection) : errorRange + + markers.push({ + message: error.error, + severity: monaco.MarkerSeverity.Error, + startLineNumber: clampedErrorRange.startLineNumber, + endLineNumber: clampedErrorRange.endLineNumber, + startColumn: clampedErrorRange.startColumn, + endColumn: clampedErrorRange.endColumn, + }) + } else { + const errorPos = toTextPosition(query, error.position) + markers.push({ + message: error.error, + severity: monaco.MarkerSeverity.Error, + startLineNumber: errorPos.lineNumber, + endLineNumber: errorPos.lineNumber, + startColumn: errorPos.column, + endColumn: errorPos.column, + }) + } + } + } + + monaco.editor.setModelMarkers(model, QuestDBLanguageName, markers) +} \ No newline at end of file diff --git a/packages/web-console/src/scenes/Editor/QueryResult/index.tsx b/packages/web-console/src/scenes/Editor/QueryResult/index.tsx index 4d416bb0c..201acae89 100644 --- a/packages/web-console/src/scenes/Editor/QueryResult/index.tsx +++ b/packages/web-console/src/scenes/Editor/QueryResult/index.tsx @@ -73,7 +73,7 @@ const addColor = (timing: string) => { return {timing} } -const formatTiming = (nanos: number) => { +export const formatTiming = (nanos: number) => { if (nanos === 0) { return "0" } diff --git a/packages/web-console/src/scenes/Editor/Shortcuts/index.tsx b/packages/web-console/src/scenes/Editor/Shortcuts/index.tsx index 80aa6be89..1f96663b9 100644 --- a/packages/web-console/src/scenes/Editor/Shortcuts/index.tsx +++ b/packages/web-console/src/scenes/Editor/Shortcuts/index.tsx @@ -70,6 +70,14 @@ const ctrlCmd = platform.isMacintosh || platform.isIOS ? "⌘" : "Ctrl" const altOption = platform.isMacintosh || platform.isIOS ? "⌥" : "Alt" const editorList: ShortcutsList = [ + { + keys: [["F9"], [ctrlCmd, "Enter"]], + title: "Run query", + }, + { + keys: [[ctrlCmd, "⇧", "Enter"]], + title: "Run all queries in a tab", + }, { keys: [[altOption, "T"]], title: "Add new tab", @@ -109,17 +117,9 @@ const editorList: ShortcutsList = [ ] const globalList: ShortcutsList = [ - { - keys: [["F9"], [ctrlCmd, "Enter"]], - title: "Run query", - }, - { - keys: [["F2"]], - title: "Focus results grid", - }, { keys: [[ctrlCmd, "K"]], - title: "Clear all notifications", + title: "Search docs", }, ] diff --git a/packages/web-console/src/scenes/Editor/index.tsx b/packages/web-console/src/scenes/Editor/index.tsx index 23ab52171..e370857fb 100644 --- a/packages/web-console/src/scenes/Editor/index.tsx +++ b/packages/web-console/src/scenes/Editor/index.tsx @@ -22,7 +22,7 @@ * ******************************************************************************/ -import React, { CSSProperties, forwardRef, Ref, useEffect } from "react" +import React, { CSSProperties, forwardRef, Ref, useEffect, useRef } from "react" import styled from "styled-components" import { PaneWrapper } from "../../components" @@ -32,11 +32,25 @@ import { Tabs } from "./Monaco/tabs" import { useEditor } from "../../providers/EditorProvider" import { Metrics } from "./Metrics" import Notifications from "../../scenes/Notifications" +import type { QueryKey } from "../../store/Query/types" +import type { ErrorResult } from "../../utils" +import { useDispatch } from "react-redux" +import { actions } from "../../store" type Props = Readonly<{ style?: CSSProperties }> +// Buffer ID -> QueryKey -> Execution State +export type ExecutionRefs = Record> + const EditorPaneWrapper = styled(PaneWrapper)` height: 100%; overflow: hidden; @@ -46,7 +60,14 @@ const Editor = ({ innerRef, ...rest }: Props & { innerRef: Ref }) => { + const dispatch = useDispatch() const { activeBuffer, addBuffer, setActiveBuffer } = useEditor() + const executionRefs = useRef({}) + + const handleClearNotifications = (bufferId: number) => { + dispatch(actions.query.cleanupBufferNotifications(bufferId)) + delete executionRefs.current[bufferId] + } useEffect(() => { const params = new URLSearchParams(window.location.search) @@ -59,9 +80,9 @@ const Editor = ({ return ( - {activeBuffer.editorViewState && } + {activeBuffer.editorViewState && } {activeBuffer.metricsViewState && } - {activeBuffer.editorViewState && } + {activeBuffer.editorViewState && } ) } diff --git a/packages/web-console/src/scenes/Layout/help.tsx b/packages/web-console/src/scenes/Layout/help.tsx index 6ddae8c3b..c3a9cbcc9 100644 --- a/packages/web-console/src/scenes/Layout/help.tsx +++ b/packages/web-console/src/scenes/Layout/help.tsx @@ -18,8 +18,8 @@ import { Link, PopperToggle, } from "../../components" +import { DropdownMenu } from "../../components/DropdownMenu" import { - DropdownMenu, FeedbackDialog, ForwardRef, Box, @@ -39,6 +39,7 @@ const HelpButton = styled(PrimaryToggleButton)` const DropdownMenuContent = styled(DropdownMenu.Content)` background: ${({ theme }) => theme.color.backgroundDarker}; + z-index: 1000; ` const DropdownMenuItem = styled(DropdownMenu.Item)<{ withlink?: string }>` diff --git a/packages/web-console/src/scenes/Notifications/Notification/LoadingNotification/index.tsx b/packages/web-console/src/scenes/Notifications/Notification/LoadingNotification/index.tsx new file mode 100644 index 000000000..291130bf7 --- /dev/null +++ b/packages/web-console/src/scenes/Notifications/Notification/LoadingNotification/index.tsx @@ -0,0 +1,41 @@ +/******************************************************************************* + * ___ _ ____ ____ + * / _ \ _ _ ___ ___| |_| _ \| __ ) + * | | | | | | |/ _ \/ __| __| | | | _ \ + * | |_| | |_| | __/\__ \ |_| |_| | |_) | + * \__\_\\__,_|\___||___/\__|____/|____/ + * + * Copyright (c) 2014-2019 Appsicle + * Copyright (c) 2019-2022 QuestDB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + ******************************************************************************/ + +import React from "react" +import { Loader as LoaderIcon } from "@questdb/react-components" +import { Wrapper, SideContent, Content } from "../styles" +import { NotificationShape } from "types" +import { Timestamp } from "../Timestamp" + +export const LoadingNotification = (props: NotificationShape) => { + const { createdAt, content, sideContent, isMinimized } = props + return ( + + + + {content} + {sideContent} + + ) +} diff --git a/packages/web-console/src/scenes/Notifications/Notification/index.tsx b/packages/web-console/src/scenes/Notifications/Notification/index.tsx index 5dfdaeef6..ca7a85e98 100644 --- a/packages/web-console/src/scenes/Notifications/Notification/index.tsx +++ b/packages/web-console/src/scenes/Notifications/Notification/index.tsx @@ -27,7 +27,8 @@ import { NotificationShape, NotificationType } from "../../../types" import { SuccessNotification } from "./SuccessNotification" import { ErrorNotification } from "./ErrorNotification" import { InfoNotification } from "./InfoNotification" -import {NoticeNotification} from "./NoticeNotification" +import { NoticeNotification } from "./NoticeNotification" +import { LoadingNotification } from "./LoadingNotification" const Notification = (props: NotificationShape) => { const { type } = props @@ -37,6 +38,8 @@ const Notification = (props: NotificationShape) => { ) : type == NotificationType.NOTICE ? ( + ) : type === NotificationType.LOADING ? ( + ) : ( ) diff --git a/packages/web-console/src/scenes/Notifications/index.tsx b/packages/web-console/src/scenes/Notifications/index.tsx index b4bde58ed..e17be2ca1 100644 --- a/packages/web-console/src/scenes/Notifications/index.tsx +++ b/packages/web-console/src/scenes/Notifications/index.tsx @@ -43,6 +43,7 @@ import { TerminalBox, Subtract, ArrowUpS } from "@styled-icons/remix-line" import { Button } from "@questdb/react-components" import Notification from "./Notification" import { NotificationType } from "../../store/Query/types" +import { useEditor } from "../../providers" const Wrapper = styled(PaneWrapper)<{ minimized: boolean }>` flex: ${(props) => (props.minimized ? "initial" : "1")}; @@ -55,6 +56,7 @@ const Menu = styled(PaneMenu)` justify-content: space-between; overflow: hidden; border: 0; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); ::before { content: ""; @@ -80,7 +82,7 @@ const Header = styled(Text)` ` const LatestNotification = styled.div` - margin-left: 1rem; + padding: 0 1rem; flex: 1; height: 100%; display: flex; @@ -104,13 +106,18 @@ const ClearAllNotifications = styled.div` flex-shrink: 0; ` -const Notifications = () => { +const Notifications = ({ onClearNotifications }: { onClearNotifications: (bufferId: number) => void }) => { + const { activeBuffer } = useEditor() const notifications = useSelector(selectors.query.getNotifications) + const queryNotifications = useSelector(selectors.query.getQueryNotificationsForBuffer(activeBuffer.id as number)) || {} + const activeNotification = useSelector(selectors.query.getActiveNotification) const { sm } = useScreenSize() const [isMinimized, setIsMinimized] = useState(true) const contentRef = useRef(null) const dispatch = useDispatch() + const bufferNotifications = notifications.filter(notification => queryNotifications[notification.query]) + const scrollToBottom = () => { contentRef.current?.scrollTo({ top: contentRef.current?.scrollHeight, @@ -121,15 +128,11 @@ const Notifications = () => { setIsMinimized(!isMinimized) }, [isMinimized]) - const cleanupNotifications = useCallback(() => { - dispatch(actions.query.cleanupNotifications()) - }, [dispatch]) - useLayoutEffect(() => { - if (notifications.length > 0) { + if (bufferNotifications.length > 0) { scrollToBottom() } - }, [notifications]) + }, [bufferNotifications]) useLayoutEffect(() => { scrollToBottom() @@ -141,8 +144,6 @@ const Notifications = () => { } }, [sm]) - const lastNotification = notifications[notifications.length - 1] - return ( @@ -151,13 +152,19 @@ const Notifications = () => { Log - {isMinimized && lastNotification && ( - + {isMinimized && activeNotification && ( + )} - + {(bufferNotifications.length > 0 || !isMinimized) &&( + + )} {!isMinimized && ( { ref={contentRef} data-hook="notifications-expanded" > - {notifications + {bufferNotifications .filter( (notification) => notification.type !== NotificationType.LOADING, ) @@ -182,10 +189,10 @@ const Notifications = () => { )} diff --git a/packages/web-console/src/scenes/Result/index.tsx b/packages/web-console/src/scenes/Result/index.tsx index 758489751..09dc656fa 100644 --- a/packages/web-console/src/scenes/Result/index.tsx +++ b/packages/web-console/src/scenes/Result/index.tsx @@ -51,6 +51,7 @@ import type { IQuestDBGrid } from "../../js/console/grid.js" import { eventBus } from "../../modules/EventBus" import { EventType } from "../../modules/EventBus/types" import { QuestContext } from "../../providers" +import { LINE_NUMBER_HARD_LIMIT } from "../Editor/Monaco" import { QueryInNotification } from "../Editor/Monaco/query-in-notification" import { NotificationType } from "../../store/Query/types" import { copyToClipboard } from "../../utils/copyToClipboard" @@ -119,14 +120,17 @@ const Result = ({ viewMode }: { viewMode: ResultViewMode }) => { rendererFn(result) } } catch (err) { - dispatch(actions.query.stopRunning()) + // Order of actions is important dispatch( actions.query.addNotification({ + query: `${sql}@${LINE_NUMBER_HARD_LIMIT + 1}-${LINE_NUMBER_HARD_LIMIT + 1}`, content: {(err as ErrorResult).error}, sideContent: , type: NotificationType.ERROR, + updateActiveNotification: true, }), ) + dispatch(actions.query.stopRunning()) } }, ) diff --git a/packages/web-console/src/scenes/Schema/VirtualTables/index.tsx b/packages/web-console/src/scenes/Schema/VirtualTables/index.tsx index 7fd94f9be..abb24f74c 100644 --- a/packages/web-console/src/scenes/Schema/VirtualTables/index.tsx +++ b/packages/web-console/src/scenes/Schema/VirtualTables/index.tsx @@ -2,8 +2,7 @@ import React, { FC, useCallback, useEffect, useMemo, useState, useContext, useRe import { Virtuoso, VirtuosoHandle, ListRange } from 'react-virtuoso'; import styled from 'styled-components'; import { Loader3, FileCopy, Restart } from '@styled-icons/remix-line'; -import { Text } from "../../../components" -import { spinAnimation } from '../../../components'; +import { spinAnimation, toast } from '../../../components'; import { color, ErrorResult } from '../../../utils'; import * as QuestDB from "../../../utils/questdb"; import { State, View } from "../../Schema"; @@ -16,8 +15,6 @@ import { useSchema } from "../SchemaContext"; import { QuestContext } from "../../../providers"; import { PartitionBy } from "../../../utils/questdb/types"; import { useDispatch } from 'react-redux'; -import { actions } from "../../../store" -import { NotificationType } from "../../../types" import { ContextMenu, ContextMenuTrigger, ContextMenuContent, MenuItem } from "../../../components/ContextMenu" import { copyToClipboard } from "../../../utils/copyToClipboard" import { SuspensionDialog } from '../SuspensionDialog' @@ -169,22 +166,10 @@ const VirtualTables: FC = ({ if (response?.type === QuestDB.Type.DQL && response.data?.[0]?.ddl) { copyToClipboard(response.data[0].ddl) - dispatch( - actions.query.addNotification({ - content: Schema copied to clipboard, - type: NotificationType.SUCCESS, - }) - ) + toast.success("Schema copied to clipboard") } } catch (error: any) { - dispatch( - actions.query.addNotification({ - content: ( - Cannot copy schema for {isMatView ? 'materialized view' : 'table'} '{tableName}' - ), - type: NotificationType.ERROR, - }) - ) + toast.error(`Cannot copy schema for ${isMatView ? 'materialized view' : 'table'} '${tableName}'`) } } @@ -195,14 +180,7 @@ const VirtualTables: FC = ({ return response.data } } catch (error: any) { - dispatch( - actions.query.addNotification({ - content: ( - Cannot show columns from table '{name}' - ), - type: NotificationType.ERROR, - }), - ) + toast.error(`Cannot show columns from table '${name}'`) } return [] } @@ -400,19 +378,18 @@ const VirtualTables: FC = ({ await handleCopyQuery(item.name, item.kind === 'matview')} - icon={} + icon={} > Copy schema - {item.walTableData?.suspended && ( - setTimeout(() => setOpenedSuspensionDialog(item.id))} - icon={} - > - Resume WAL - - )} + item.walTableData?.suspended && setTimeout(() => setOpenedSuspensionDialog(item.id))} + icon={} + disabled={!item.walTableData?.suspended} + > + Resume WAL + diff --git a/packages/web-console/src/scenes/Schema/index.tsx b/packages/web-console/src/scenes/Schema/index.tsx index da9db2fe1..db3aa791d 100644 --- a/packages/web-console/src/scenes/Schema/index.tsx +++ b/packages/web-console/src/scenes/Schema/index.tsx @@ -63,7 +63,6 @@ import { Toolbar } from "./Toolbar/toolbar" import VirtualTables from "./VirtualTables" import { useLocalStorage } from "../../providers/LocalStorageProvider" import { StoreKey } from "../../utils/localStorage/types" -import { NotificationType } from "../../types" import { Checkbox } from "./checkbox" import { AddChart } from "@styled-icons/material" import { useEditor } from "../../providers/EditorProvider" @@ -76,6 +75,7 @@ import type { Duration } from "../../scenes/Editor/Metrics/types" import { useSchema } from "./SchemaContext" import { SchemaProvider } from "./SchemaContext" import { TreeNodeKind } from "./Row" +import { toast } from '../../components/Toast' type Props = Readonly<{ hideMenu?: boolean @@ -229,23 +229,10 @@ const Schema = ({ ) if (tablesWithError.length === 0) { copyToClipboard(ddls.join("\n\n")) - dispatch( - actions.query.addNotification({ - content: Schemas copied to clipboard, - }), - ) + toast.success("Schemas copied to clipboard") + } else { - dispatch( - actions.query.addNotification({ - content: ( - - Cannot copy schemas from tables:{" "} - {tablesWithError.sort().join(", ")} - - ), - type: NotificationType.ERROR, - }), - ) + toast.error("Cannot copy schemas from tables: " + tablesWithError.sort().join(", ")) } setSelectOpen(false) } diff --git a/packages/web-console/src/store/Query/actions.ts b/packages/web-console/src/store/Query/actions.ts index 56f8d04a0..0e75f71fe 100644 --- a/packages/web-console/src/store/Query/actions.ts +++ b/packages/web-console/src/store/Query/actions.ts @@ -35,6 +35,9 @@ import { NotificationType, QueryAction, QueryAT, + QueryKey, + RunningType, + QueriesToRun, } from "../../types" const setTables = (payload: Table[]): QueryAction => ({ @@ -52,12 +55,14 @@ const setColumns = (payload: InformationSchemaColumn[]): QueryAction => ({ }) const addNotification = ( - payload: Partial & { content: ReactNode }, + payload: Partial & { content: ReactNode, query: QueryKey }, + bufferId?: number, ): QueryAction => ({ payload: { createdAt: new Date(), type: NotificationType.SUCCESS, ...payload, + bufferId, }, type: QueryAT.ADD_NOTIFICATION, }) @@ -66,12 +71,20 @@ const cleanupNotifications = (): QueryAction => ({ type: QueryAT.CLEANUP_NOTIFICATIONS, }) -const removeNotification = (payload: Date): QueryAction => ({ +const cleanupBufferNotifications = (bufferId: number): QueryAction => ({ + type: QueryAT.CLEANUP_BUFFER_NOTIFICATIONS, + payload: { + bufferId, + }, +}) + +const removeNotification = (payload: QueryKey, bufferId?: number): QueryAction => ({ payload, + bufferId, type: QueryAT.REMOVE_NOTIFICATION, }) -const setResult = (payload: QueryRawResult): QueryAction => ({ +const setResult = (payload: QueryRawResult | undefined): QueryAction => ({ payload, type: QueryAT.SET_RESULT, }) @@ -80,20 +93,43 @@ const stopRunning = (): QueryAction => ({ type: QueryAT.STOP_RUNNING, }) -const toggleRunning = (isRefresh = false): QueryAction => ({ +const toggleRunning = ( + payload?: RunningType +): QueryAction => ({ type: QueryAT.TOGGLE_RUNNING, + payload, +}) + +const setActiveNotification = (payload: NotificationShape | null): QueryAction => ({ + type: QueryAT.SET_ACTIVE_NOTIFICATION, + payload, +}) + +const updateNotificationKey = (oldKey: QueryKey, newKey: QueryKey, bufferId?: number): QueryAction => ({ + type: QueryAT.UPDATE_NOTIFICATION_KEY, payload: { - isRefresh, + oldKey, + newKey, + bufferId, }, }) +const setQueriesToRun = (payload: QueriesToRun): QueryAction => ({ + type: QueryAT.SET_QUERIES_TO_RUN, + payload, +}) + export default { addNotification, cleanupNotifications, + cleanupBufferNotifications, removeNotification, + updateNotificationKey, setResult, stopRunning, toggleRunning, setTables, setColumns, + setActiveNotification, + setQueriesToRun, } diff --git a/packages/web-console/src/store/Query/reducers.ts b/packages/web-console/src/store/Query/reducers.ts index 14edcf793..864e6e938 100644 --- a/packages/web-console/src/store/Query/reducers.ts +++ b/packages/web-console/src/store/Query/reducers.ts @@ -22,31 +22,56 @@ * ******************************************************************************/ -import { QueryAction, QueryAT, QueryStateShape } from "../../types" +import { QueryAction, QueryAT, QueryStateShape, RunningType } from "../../types" export const initialState: QueryStateShape = { notifications: [], tables: [], columns: [], - running: { - value: false, - isRefresh: false, - }, - maxNotifications: 20, + running: RunningType.NONE, + queryNotifications: {}, + activeNotification: null, + queriesToRun: [], } const query = (state = initialState, action: QueryAction): QueryStateShape => { switch (action.type) { case QueryAT.ADD_NOTIFICATION: { - const notifications = [...state.notifications, action.payload] - - while (notifications.length === state.maxNotifications) { - notifications.shift() + const { query: queryText, isExplain = false, updateActiveNotification = true, bufferId } = action.payload + + const notificationWithTimestamp = { + ...action.payload, + createdAt: action.payload.createdAt || new Date() } + const { bufferId: _, ...cleanNotification } = notificationWithTimestamp + + if (bufferId !== undefined) { + const bufferNotifications = state.queryNotifications[bufferId] || {} + const existingQueryNotifications = bufferNotifications[queryText] || {} + const updatedQueryNotifications = { + ...existingQueryNotifications, + latest: cleanNotification, + [isExplain ? 'explain' : 'regular']: cleanNotification + } + return { + ...state, + notifications: [...state.notifications, cleanNotification], + queryNotifications: { + ...state.queryNotifications, + [bufferId]: { + ...bufferNotifications, + [queryText]: updatedQueryNotifications + } + }, + ...(updateActiveNotification ? { activeNotification: cleanNotification } : {}), + } + } + return { ...state, - notifications, + notifications: [...state.notifications, cleanNotification], + ...(updateActiveNotification ? { activeNotification: cleanNotification } : {}), } } @@ -54,15 +79,118 @@ const query = (state = initialState, action: QueryAction): QueryStateShape => { return { ...state, notifications: [], + queryNotifications: {}, + activeNotification: null, + } + } + + case QueryAT.CLEANUP_BUFFER_NOTIFICATIONS: { + const { bufferId } = action.payload + const bufferNotifications = state.queryNotifications[bufferId] || {} + + const filteredNotifications = state.notifications.filter(notification => + !bufferNotifications[notification.query] + ) + + const updatedQueryNotifications = { ...state.queryNotifications } + delete updatedQueryNotifications[bufferId] + + const updatedActiveNotification = state.activeNotification && bufferNotifications[state.activeNotification.query] + ? null + : state.activeNotification + + return { + ...state, + notifications: filteredNotifications, + queryNotifications: updatedQueryNotifications, + activeNotification: updatedActiveNotification, + } + } + + case QueryAT.SET_ACTIVE_NOTIFICATION: { + return { + ...state, + activeNotification: action.payload, } } case QueryAT.REMOVE_NOTIFICATION: { + const { bufferId } = action + const filteredNotifications = state.notifications.filter( + (notification) => notification.query !== action.payload, + ) + + if (bufferId !== undefined) { + const bufferNotifications = state.queryNotifications[bufferId] || {} + const updatedBufferNotifications = { ...bufferNotifications } + delete updatedBufferNotifications[action.payload] + + return { + ...state, + notifications: filteredNotifications, + queryNotifications: { + ...state.queryNotifications, + [bufferId]: updatedBufferNotifications + }, + } + } + return { ...state, - notifications: state.notifications.filter( - ({ createdAt }) => createdAt !== action.payload, - ), + notifications: filteredNotifications, + } + } + + case QueryAT.UPDATE_NOTIFICATION_KEY: { + const { oldKey, newKey, bufferId } = action.payload + if (newKey === oldKey) { + return { ...state } + } + + if (bufferId !== undefined) { + const bufferNotifications = state.queryNotifications[bufferId] || {} + const updatedBufferNotifications = { ...bufferNotifications } + + if (updatedBufferNotifications[oldKey]) { + updatedBufferNotifications[newKey] = updatedBufferNotifications[oldKey] + delete updatedBufferNotifications[oldKey] + } + + const updatedNotifications = state.notifications.map(notification => + notification.query === oldKey + ? { ...notification, query: newKey } + : notification + ) + + const updatedActiveNotification = state.activeNotification?.query === oldKey + ? { ...state.activeNotification, query: newKey } + : state.activeNotification + + return { + ...state, + notifications: updatedNotifications, + queryNotifications: { + ...state.queryNotifications, + [bufferId]: updatedBufferNotifications + }, + activeNotification: updatedActiveNotification, + } + } + + const updatedNotifications = state.notifications.map(notification => + notification.query === oldKey + ? { ...notification, query: newKey } + : notification + ) + + const updatedActiveNotification = state.activeNotification?.query === oldKey + ? { ...state.activeNotification, query: newKey } + : state.activeNotification + + return { + ...state, + notifications: updatedNotifications, + activeNotification: updatedActiveNotification, } } @@ -76,20 +204,21 @@ const query = (state = initialState, action: QueryAction): QueryStateShape => { case QueryAT.STOP_RUNNING: { return { ...state, - running: { - value: false, - isRefresh: false, - }, + running: RunningType.NONE, } } case QueryAT.TOGGLE_RUNNING: { + const currentRunning = state.running + if (currentRunning !== RunningType.NONE) { + return { + ...state, + running: RunningType.NONE, + } + } return { ...state, - running: { - value: !state.running.value, - isRefresh: action.payload.isRefresh, - }, + running: action.payload ?? RunningType.QUERY, } } @@ -107,6 +236,12 @@ const query = (state = initialState, action: QueryAction): QueryStateShape => { } } + case QueryAT.SET_QUERIES_TO_RUN: { + return { + ...state, + queriesToRun: action.payload, + } + } default: return state } diff --git a/packages/web-console/src/store/Query/selectors.ts b/packages/web-console/src/store/Query/selectors.ts index 7ec1e4563..3b5439114 100644 --- a/packages/web-console/src/store/Query/selectors.ts +++ b/packages/web-console/src/store/Query/selectors.ts @@ -22,20 +22,33 @@ * ******************************************************************************/ -import { NotificationShape, RunningShape, StoreShape } from "types" +import { NotificationShape, RunningType, StoreShape, QueryNotifications, QueriesToRun } from "types" import type { QueryRawResult, Table, InformationSchemaColumn, } from "utils/questdb" +import { QueryKey } from "./types" const getNotifications: (store: StoreShape) => NotificationShape[] = (store) => store.query.notifications +const getQueryNotifications: (store: StoreShape) => Record> = (store) => + store.query.queryNotifications + +const getQueryNotificationsForBuffer = (bufferId: number) => (store: StoreShape): Record | undefined => + store.query.queryNotifications[bufferId] + +const getActiveNotification: (store: StoreShape) => NotificationShape | null = (store) => + store.query.activeNotification + +const getQueriesToRun: (store: StoreShape) => QueriesToRun = (store) => + store.query.queriesToRun + const getResult: (store: StoreShape) => undefined | QueryRawResult = (store) => store.query.result -const getRunning: (store: StoreShape) => RunningShape = (store) => +const getRunning: (store: StoreShape) => RunningType = (store) => store.query.running const getTables: (store: StoreShape) => Table[] = (store) => store.query.tables @@ -45,6 +58,10 @@ const getColumns: (store: StoreShape) => InformationSchemaColumn[] = (store) => export default { getNotifications, + getQueryNotifications, + getQueryNotificationsForBuffer, + getActiveNotification, + getQueriesToRun, getResult, getRunning, getTables, diff --git a/packages/web-console/src/store/Query/types.ts b/packages/web-console/src/store/Query/types.ts index c27cea8e9..27453e13d 100644 --- a/packages/web-console/src/store/Query/types.ts +++ b/packages/web-console/src/store/Query/types.ts @@ -29,6 +29,9 @@ import type { Table, InformationSchemaColumn, } from "utils/questdb" +import type { Request } from "../../scenes/Editor/Monaco/utils" + +export type QueryKey = `${string}@${number}-${number}` export enum NotificationType { ERROR = "error", @@ -38,7 +41,18 @@ export enum NotificationType { LOADING = "loading", } +export enum RunningType { + SCRIPT = "script", + EXPLAIN = "explain", + REFRESH = "refresh", + QUERY = "query", + NONE = "none", +} + +export type QueriesToRun = Readonly + export type NotificationShape = Readonly<{ + query: QueryKey createdAt: Date content: ReactNode sideContent?: ReactNode @@ -46,11 +60,14 @@ export type NotificationShape = Readonly<{ type: NotificationType jitCompiled?: boolean isMinimized?: boolean + isExplain?: boolean + updateActiveNotification?: boolean }> -export type RunningShape = Readonly<{ - value: boolean - isRefresh: boolean +export type QueryNotifications = Readonly<{ + latest: NotificationShape + regular?: NotificationShape + explain?: NotificationShape }> export type QueryStateShape = Readonly<{ @@ -58,23 +75,29 @@ export type QueryStateShape = Readonly<{ tables: Table[] columns: InformationSchemaColumn[] result?: QueryRawResult - running: RunningShape - maxNotifications: number + running: RunningType + queryNotifications: Record> + activeNotification: NotificationShape | null + queriesToRun: QueriesToRun }> export enum QueryAT { ADD_NOTIFICATION = "QUERY/ADD_NOTIFICATION", CLEANUP_NOTIFICATIONS = "QUERY/CLEANUP_NOTIFICATIONS", + CLEANUP_BUFFER_NOTIFICATIONS = "QUERY/CLEANUP_BUFFER_NOTIFICATIONS", REMOVE_NOTIFICATION = "QUERY/REMOVE_NOTIFICATION", + UPDATE_NOTIFICATION_KEY = "QUERY/UPDATE_NOTIFICATION_KEY", SET_RESULT = "QUERY/SET_RESULT", STOP_RUNNING = "QUERY/STOP_RUNNING", TOGGLE_RUNNING = "QUERY/TOGGLE_RUNNING", SET_TABLES = "QUERY/SET_TABLES", SET_COLUMNS = "QUERY/SET_COLUMNS", + SET_ACTIVE_NOTIFICATION = "QUERY/SET_ACTIVE_NOTIFICATION", + SET_QUERIES_TO_RUN = "QUERY/SET_QUERIES_TO_RUN", } type AddNotificationAction = Readonly<{ - payload: NotificationShape + payload: NotificationShape & { bufferId?: number } type: QueryAT.ADD_NOTIFICATION }> @@ -82,13 +105,21 @@ type CleanupNotificationsAction = Readonly<{ type: QueryAT.CLEANUP_NOTIFICATIONS }> +type CleanupBufferNotificationsAction = Readonly<{ + type: QueryAT.CLEANUP_BUFFER_NOTIFICATIONS + payload: { + bufferId: number + } +}> + type RemoveNotificationAction = Readonly<{ - payload: Date + payload: QueryKey + bufferId?: number type: QueryAT.REMOVE_NOTIFICATION }> type SetResultAction = Readonly<{ - payload: QueryRawResult + payload: QueryRawResult | undefined type: QueryAT.SET_RESULT }> @@ -98,9 +129,7 @@ type StopRunningAction = Readonly<{ type ToggleRunningAction = Readonly<{ type: QueryAT.TOGGLE_RUNNING - payload: Readonly<{ - isRefresh: boolean - }> + payload?: RunningType }> type SetTablesAction = Readonly<{ @@ -117,12 +146,35 @@ type SetColumnsActions = Readonly<{ }> }> +type SetActiveNotificationAction = Readonly<{ + type: QueryAT.SET_ACTIVE_NOTIFICATION + payload: NotificationShape | null +}> + +type UpdateNotificationKeyAction = Readonly<{ + type: QueryAT.UPDATE_NOTIFICATION_KEY + payload: { + oldKey: QueryKey + newKey: QueryKey + bufferId?: number + } +}> + +type SetQueriesToRunAction = Readonly<{ + type: QueryAT.SET_QUERIES_TO_RUN + payload: QueriesToRun +}> + export type QueryAction = | AddNotificationAction | CleanupNotificationsAction + | CleanupBufferNotificationsAction | RemoveNotificationAction + | UpdateNotificationKeyAction | SetResultAction | StopRunningAction | ToggleRunningAction | SetTablesAction | SetColumnsActions + | SetActiveNotificationAction + | SetQueriesToRunAction diff --git a/packages/web-console/src/styles/_editor.scss b/packages/web-console/src/styles/_editor.scss index 788dc9f4d..2f79de92b 100644 --- a/packages/web-console/src/styles/_editor.scss +++ b/packages/web-console/src/styles/_editor.scss @@ -288,3 +288,33 @@ display: flex; flex: 1; } + + +.monaco-menu { + background: #343746; + color: #f8f8f2; +} + +.monaco-menu .action-menu-item { + border: 1px solid transparent; + padding: 1rem 0; + height: 3rem !important; + + &:hover { + background: #043c5c !important; + border: 1px solid #8be9fd; + } + + &:not(.disabled) { + cursor: pointer; + } +} + +.monaco-menu-container .monaco-scrollable-element { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.36) !important; +} + +.action-label.separator { + border-bottom: 1px solid #606060 !important; + margin: 5px 0 !important; +} diff --git a/packages/web-console/src/theme/index.ts b/packages/web-console/src/theme/index.ts index 1590b46fc..0a4d1852e 100644 --- a/packages/web-console/src/theme/index.ts +++ b/packages/web-console/src/theme/index.ts @@ -44,6 +44,7 @@ const color: ColorShape = { orange: "#ffb86c", yellow: "#f1fa8c", green: "#50fa7b", + greenDarker: "#00aa3b", purple: "#bd93f9", cyan: "#8be9fd", pink: "#d14671", diff --git a/packages/web-console/src/types/styled.d.ts b/packages/web-console/src/types/styled.d.ts index 73754572f..f203b9464 100644 --- a/packages/web-console/src/types/styled.d.ts +++ b/packages/web-console/src/types/styled.d.ts @@ -42,6 +42,7 @@ export type ColorShape = { orange: string yellow: string green: string + greenDarker: string purple: string cyan: string pink: string diff --git a/yarn.lock b/yarn.lock index c33ed3ae3..646445f55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3244,6 +3244,7 @@ __metadata: "@questdb/sql-grammar": "npm:1.2.4" "@radix-ui/react-context-menu": "npm:2.1.5" "@radix-ui/react-dialog": "npm:^1.0.3" + "@radix-ui/react-dropdown-menu": "npm:^2.1.15" "@styled-icons/bootstrap": "npm:10.47.0" "@styled-icons/boxicons-logos": "npm:10.47.0" "@styled-icons/boxicons-regular": "npm:10.47.0" @@ -3396,6 +3397,13 @@ __metadata: languageName: node linkType: hard +"@radix-ui/primitive@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/primitive@npm:1.1.2" + checksum: 10/6cb2ac097faf77b7288bdfd87d92e983e357252d00ee0d2b51ad8e7897bf9f51ec53eafd7dd64c613671a2b02cb8166177bc3de444a6560ec60835c363321c18 + languageName: node + linkType: hard + "@radix-ui/react-alert-dialog@npm:^1.0.3": version: 1.0.5 resolution: "@radix-ui/react-alert-dialog@npm:1.0.5" @@ -3453,6 +3461,25 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-arrow@npm:1.1.7": + version: 1.1.7 + resolution: "@radix-ui/react-arrow@npm:1.1.7" + dependencies: + "@radix-ui/react-primitive": "npm:2.1.3" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/6cdf74f06090f8994cdf6d3935a44ea3ac309163a4f59c476482c4907e8e0775f224045030abf10fa4f9e1cb7743db034429249b9e59354988e247eeb0f4fdcf + languageName: node + linkType: hard + "@radix-ui/react-collection@npm:1.0.3": version: 1.0.3 resolution: "@radix-ui/react-collection@npm:1.0.3" @@ -3476,6 +3503,28 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-collection@npm:1.1.7": + version: 1.1.7 + resolution: "@radix-ui/react-collection@npm:1.1.7" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-slot": "npm:1.2.3" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/cd53e2a2be82be7bc4014164cac0b42948401a203e5d0294d3947a5193f1d56bd23eb60e878a98dba50d08283254e79c3b873de5f935276b849686a868d51dd5 + languageName: node + linkType: hard + "@radix-ui/react-compose-refs@npm:0.1.0": version: 0.1.0 resolution: "@radix-ui/react-compose-refs@npm:0.1.0" @@ -3502,6 +3551,19 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-compose-refs@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-compose-refs@npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/9a91f0213014ffa40c5b8aae4debb993be5654217e504e35aa7422887eb2d114486d37e53c482d0fffb00cd44f51b5269fcdf397b280c71666fa11b7f32f165d + languageName: node + linkType: hard + "@radix-ui/react-context-menu@npm:2.1.5": version: 2.1.5 resolution: "@radix-ui/react-context-menu@npm:2.1.5" @@ -3553,6 +3615,19 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-context@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-context@npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/156088367de42afa3c7e3acf5f0ba7cad6b359f3d17485585e80c2418434a6ed7cac2602eb73bca265d0091a1ad380f9405c069f103983e53497097ff35ba8f2 + languageName: node + linkType: hard + "@radix-ui/react-dialog@npm:1.0.5, @radix-ui/react-dialog@npm:^1.0.3": version: 1.0.5 resolution: "@radix-ui/react-dialog@npm:1.0.5" @@ -3601,6 +3676,19 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-direction@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-direction@npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/8cc330285f1d06829568042ca9aabd3295be4690ae93683033fc8632b5c4dfc60f5c1312f6e2cae27c196189c719de3cfbcf792ff74800f9ccae0ab4abc1bc92 + languageName: node + linkType: hard + "@radix-ui/react-dismissable-layer@npm:0.1.5": version: 0.1.5 resolution: "@radix-ui/react-dismissable-layer@npm:0.1.5" @@ -3642,6 +3730,29 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-dismissable-layer@npm:1.1.10": + version: 1.1.10 + resolution: "@radix-ui/react-dismissable-layer@npm:1.1.10" + dependencies: + "@radix-ui/primitive": "npm:1.1.2" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + "@radix-ui/react-use-escape-keydown": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/e08733ee345521a09100f922191302960d87a723d3bebb804f5659ff37f2fae57e335b2debad5ac17cb929be6b1fa8bc092c016723665ea8c90f7cf396c92d3b + languageName: node + linkType: hard + "@radix-ui/react-dropdown-menu@npm:^2.0.5": version: 2.0.6 resolution: "@radix-ui/react-dropdown-menu@npm:2.0.6" @@ -3668,6 +3779,31 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-dropdown-menu@npm:^2.1.15": + version: 2.1.15 + resolution: "@radix-ui/react-dropdown-menu@npm:2.1.15" + dependencies: + "@radix-ui/primitive": "npm:1.1.2" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-menu": "npm:2.1.15" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-controllable-state": "npm:1.2.2" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/1300aa871dec358ddf42dfba48b59f25963d9aa7584726e71ba8b676b08d443d2933ec95c6112e1bb0cc60e79c06700cc83e1dad3f2762f8779e8e89ce1eddad + languageName: node + linkType: hard + "@radix-ui/react-focus-guards@npm:0.1.0": version: 0.1.0 resolution: "@radix-ui/react-focus-guards@npm:0.1.0" @@ -3694,6 +3830,19 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-focus-guards@npm:1.1.2": + version: 1.1.2 + resolution: "@radix-ui/react-focus-guards@npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/618658e2b98575198b94ccfdd27f41beb37f83721c9a04617e848afbc47461124ae008d703d713b9644771d96d4852e49de322cf4be3b5f10a4f94d200db5248 + languageName: node + linkType: hard + "@radix-ui/react-focus-scope@npm:0.1.4": version: 0.1.4 resolution: "@radix-ui/react-focus-scope@npm:0.1.4" @@ -3730,6 +3879,27 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-focus-scope@npm:1.1.7": + version: 1.1.7 + resolution: "@radix-ui/react-focus-scope@npm:1.1.7" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/2a7cd00e39e01756999ebf0bdb3401d6a8efa489a7b19e6b629b40bad3022b7b1f616555ccb4b0505bc0ba53e13a1fb51be905db138b16ec39c4fe319fe701d3 + languageName: node + linkType: hard + "@radix-ui/react-id@npm:0.1.5": version: 0.1.5 resolution: "@radix-ui/react-id@npm:0.1.5" @@ -3758,6 +3928,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-id@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-id@npm:1.1.1" + dependencies: + "@radix-ui/react-use-layout-effect": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/8d68e200778eb3038906870fc869b3d881f4a46715fb20cddd9c76cba42fdaaa4810a3365b6ec2daf0f185b9201fc99d009167f59c7921bc3a139722c2e976db + languageName: node + linkType: hard + "@radix-ui/react-menu@npm:2.0.6": version: 2.0.6 resolution: "@radix-ui/react-menu@npm:2.0.6" @@ -3795,6 +3980,42 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-menu@npm:2.1.15": + version: 2.1.15 + resolution: "@radix-ui/react-menu@npm:2.1.15" + dependencies: + "@radix-ui/primitive": "npm:1.1.2" + "@radix-ui/react-collection": "npm:1.1.7" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-direction": "npm:1.1.1" + "@radix-ui/react-dismissable-layer": "npm:1.1.10" + "@radix-ui/react-focus-guards": "npm:1.1.2" + "@radix-ui/react-focus-scope": "npm:1.1.7" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-popper": "npm:1.2.7" + "@radix-ui/react-portal": "npm:1.1.9" + "@radix-ui/react-presence": "npm:1.1.4" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-roving-focus": "npm:1.1.10" + "@radix-ui/react-slot": "npm:1.2.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + aria-hidden: "npm:^1.2.4" + react-remove-scroll: "npm:^2.6.3" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/7876c0892b3327f03685b564ae13e058627546346d60b5ee10559dea6a1c514660f7b6b4cffc9253b59de97dffda0512a99c5181446e0f4f5ef33b67dda03072 + languageName: node + linkType: hard + "@radix-ui/react-popover@npm:0.1.6": version: 0.1.6 resolution: "@radix-ui/react-popover@npm:0.1.6" @@ -3869,6 +4090,34 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-popper@npm:1.2.7": + version: 1.2.7 + resolution: "@radix-ui/react-popper@npm:1.2.7" + dependencies: + "@floating-ui/react-dom": "npm:^2.0.0" + "@radix-ui/react-arrow": "npm:1.1.7" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" + "@radix-ui/react-use-rect": "npm:1.1.1" + "@radix-ui/react-use-size": "npm:1.1.1" + "@radix-ui/rect": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/1d1bcb679f914cd01649fdd5106b3a845d9f68cf0660c209e29baf95e92f9ac8a97e5bb8b56911b11b0291db8d4b6c8adc4040af23e0149e4235e669f5f4703b + languageName: node + linkType: hard + "@radix-ui/react-portal@npm:0.1.4": version: 0.1.4 resolution: "@radix-ui/react-portal@npm:0.1.4" @@ -3903,6 +4152,26 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-portal@npm:1.1.9": + version: 1.1.9 + resolution: "@radix-ui/react-portal@npm:1.1.9" + dependencies: + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/bd6be39bf021d5c917e2474ecba411e2625171f7ef96862b9af04bbd68833bb3662a7f1fbdeb5a7a237111b10e811e76d2cd03e957dadd6e668ef16541bfbd68 + languageName: node + linkType: hard + "@radix-ui/react-presence@npm:0.1.2": version: 0.1.2 resolution: "@radix-ui/react-presence@npm:0.1.2" @@ -3937,6 +4206,26 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-presence@npm:1.1.4": + version: 1.1.4 + resolution: "@radix-ui/react-presence@npm:1.1.4" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/ba01f385f6beedba7bf50ffd4aca8091554a67aee2b7252605876136155ceb2fcf1b627dccaf2e49032231eda271fe0e8915040729da9d1f95d08b854d815305 + languageName: node + linkType: hard + "@radix-ui/react-primitive@npm:0.1.4": version: 0.1.4 resolution: "@radix-ui/react-primitive@npm:0.1.4" @@ -3969,6 +4258,25 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-primitive@npm:2.1.3": + version: 2.1.3 + resolution: "@radix-ui/react-primitive@npm:2.1.3" + dependencies: + "@radix-ui/react-slot": "npm:1.2.3" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/1dbbf932a3527f4e62f210bb72944eff605c3e38c8d3275ed5a5c570c02820ab156169756a65ad9a638d2089a828a04a7903795377384e98c87d0ca456303253 + languageName: node + linkType: hard + "@radix-ui/react-roving-focus@npm:1.0.4": version: 1.0.4 resolution: "@radix-ui/react-roving-focus@npm:1.0.4" @@ -3997,6 +4305,33 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-roving-focus@npm:1.1.10": + version: 1.1.10 + resolution: "@radix-ui/react-roving-focus@npm:1.1.10" + dependencies: + "@radix-ui/primitive": "npm:1.1.2" + "@radix-ui/react-collection": "npm:1.1.7" + "@radix-ui/react-compose-refs": "npm:1.1.2" + "@radix-ui/react-context": "npm:1.1.2" + "@radix-ui/react-direction": "npm:1.1.1" + "@radix-ui/react-id": "npm:1.1.1" + "@radix-ui/react-primitive": "npm:2.1.3" + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + "@radix-ui/react-use-controllable-state": "npm:1.2.2" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/65241d84cb62d79115185df02f292a682dda08d920152ed5881a24cc656c60bbf97530af012b422619862dc0956ee301e4d0d65b0cb82e72a689135ac8afe5a8 + languageName: node + linkType: hard + "@radix-ui/react-slot@npm:0.1.2": version: 0.1.2 resolution: "@radix-ui/react-slot@npm:0.1.2" @@ -4025,6 +4360,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-slot@npm:1.2.3": + version: 1.2.3 + resolution: "@radix-ui/react-slot@npm:1.2.3" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.2" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/fe484c2741e31d9c20a8fb53c5790a73c0664e2bea35e27f4d484a90c42135fcfffe11a08abfcacb7a8ee2faf013471f0e856818f3ddac8ac51ceb8869e0fd08 + languageName: node + linkType: hard + "@radix-ui/react-switch@npm:^1.0.2": version: 1.0.3 resolution: "@radix-ui/react-switch@npm:1.0.3" @@ -4089,6 +4439,19 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-callback-ref@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-callback-ref@npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/cde8c40f1d4e79e6e71470218163a746858304bad03758ac84dc1f94247a046478e8e397518350c8d6609c84b7e78565441d7505bb3ed573afce82cfdcd19faf + languageName: node + linkType: hard + "@radix-ui/react-use-controllable-state@npm:0.1.0": version: 0.1.0 resolution: "@radix-ui/react-use-controllable-state@npm:0.1.0" @@ -4117,6 +4480,37 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-controllable-state@npm:1.2.2": + version: 1.2.2 + resolution: "@radix-ui/react-use-controllable-state@npm:1.2.2" + dependencies: + "@radix-ui/react-use-effect-event": "npm:0.0.2" + "@radix-ui/react-use-layout-effect": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/a100bff3ddecb753dab17444147273c9f70046c5949712c52174b259622eaef12acbf7ebcf289bae4e714eb84d0a7317c1aa44064cd997f327d77b62bc732a7c + languageName: node + linkType: hard + +"@radix-ui/react-use-effect-event@npm:0.0.2": + version: 0.0.2 + resolution: "@radix-ui/react-use-effect-event@npm:0.0.2" + dependencies: + "@radix-ui/react-use-layout-effect": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/5a1950a30a399ea7e4b98154da9f536737a610de80189b7aacd4f064a89a3cd0d2a48571d527435227252e72e872bdb544ff6ffcfbdd02de2efd011be4aaa902 + languageName: node + linkType: hard + "@radix-ui/react-use-escape-keydown@npm:0.1.0": version: 0.1.0 resolution: "@radix-ui/react-use-escape-keydown@npm:0.1.0" @@ -4145,6 +4539,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-escape-keydown@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-escape-keydown@npm:1.1.1" + dependencies: + "@radix-ui/react-use-callback-ref": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/0eb0756c2c55ddcde9ff01446ab01c085ab2bf799173e97db7ef5f85126f9e8600225570801a1f64740e6d14c39ffe8eed7c14d29737345a5797f4622ac96f6f + languageName: node + linkType: hard + "@radix-ui/react-use-layout-effect@npm:0.1.0": version: 0.1.0 resolution: "@radix-ui/react-use-layout-effect@npm:0.1.0" @@ -4171,6 +4580,19 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-layout-effect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-layout-effect@npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/bad2ba4f206e6255263582bedfb7868773c400836f9a1b423c0b464ffe4a17e13d3f306d1ce19cf7a19a492e9d0e49747464f2656451bb7c6a99f5a57bd34de2 + languageName: node + linkType: hard + "@radix-ui/react-use-previous@npm:1.0.1": version: 1.0.1 resolution: "@radix-ui/react-use-previous@npm:1.0.1" @@ -4214,6 +4636,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-rect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-rect@npm:1.1.1" + dependencies: + "@radix-ui/rect": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/116461bebc49472f7497e66a9bd413541181b3d00c5e0aaeef45d790dc1fbd7c8dcea80b169ea273306228b9a3c2b70067e902d1fd5004b3057e3bbe35b9d55d + languageName: node + linkType: hard + "@radix-ui/react-use-size@npm:0.1.1": version: 0.1.1 resolution: "@radix-ui/react-use-size@npm:0.1.1" @@ -4241,6 +4678,21 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-use-size@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-use-size@npm:1.1.1" + dependencies: + "@radix-ui/react-use-layout-effect": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/64e61f65feb67ffc80e1fc4a8d5e32480fb6d68475e2640377e021178dead101568cba5f936c9c33e6c142c7cf2fb5d76ad7b23ef80e556ba142d56cf306147b + languageName: node + linkType: hard + "@radix-ui/rect@npm:0.1.1": version: 0.1.1 resolution: "@radix-ui/rect@npm:0.1.1" @@ -4259,6 +4711,13 @@ __metadata: languageName: node linkType: hard +"@radix-ui/rect@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/rect@npm:1.1.1" + checksum: 10/b6c5eb787640775b53dd52fa47218a089f0a0d8220d3ebff079c0b754e1fb82d89b6bdf08a82fd0d59549bdeb52678c0cca091c302da49dcf74c3c989cb55678 + languageName: node + linkType: hard + "@sideway/address@npm:^4.1.3": version: 4.1.4 resolution: "@sideway/address@npm:4.1.4" @@ -7495,6 +7954,15 @@ __metadata: languageName: node linkType: hard +"aria-hidden@npm:^1.2.4": + version: 1.2.6 + resolution: "aria-hidden@npm:1.2.6" + dependencies: + tslib: "npm:^2.0.0" + checksum: 10/1914e5a36225dccdb29f0b88cc891eeca736cdc5b0c905ab1437b90b28b5286263ed3a221c75b7dc788f25b942367be0044b2ac8ccf073a72e07a50b1d964202 + languageName: node + linkType: hard + "aria-query@npm:5.1.3": version: 5.1.3 resolution: "aria-query@npm:5.1.3" @@ -18359,6 +18827,22 @@ __metadata: languageName: node linkType: hard +"react-remove-scroll-bar@npm:^2.3.7": + version: 2.3.8 + resolution: "react-remove-scroll-bar@npm:2.3.8" + dependencies: + react-style-singleton: "npm:^2.2.2" + tslib: "npm:^2.0.0" + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/6c0f8cff98b9f49a4ee2263f1eedf12926dced5ce220fbe83bd93544460e2a7ec8ec39b35d1b2a75d2fced0b2d64afeb8e66f830431ca896e05a20585f9fc350 + languageName: node + linkType: hard + "react-remove-scroll@npm:2.5.5": version: 2.5.5 resolution: "react-remove-scroll@npm:2.5.5" @@ -18397,6 +18881,25 @@ __metadata: languageName: node linkType: hard +"react-remove-scroll@npm:^2.6.3": + version: 2.7.1 + resolution: "react-remove-scroll@npm:2.7.1" + dependencies: + react-remove-scroll-bar: "npm:^2.3.7" + react-style-singleton: "npm:^2.2.3" + tslib: "npm:^2.1.0" + use-callback-ref: "npm:^1.3.3" + use-sidecar: "npm:^1.1.3" + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/5e571ba35ba527047c54c9c4a271363167770556fb85ee45ead8310673197719425cc8f7a2b7f672abf530294c41c8c34bdae325a571994cc1e694b664b52734 + languageName: node + linkType: hard + "react-style-singleton@npm:^2.2.1": version: 2.2.1 resolution: "react-style-singleton@npm:2.2.1" @@ -18414,6 +18917,22 @@ __metadata: languageName: node linkType: hard +"react-style-singleton@npm:^2.2.2, react-style-singleton@npm:^2.2.3": + version: 2.2.3 + resolution: "react-style-singleton@npm:2.2.3" + dependencies: + get-nonce: "npm:^1.0.0" + tslib: "npm:^2.0.0" + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/62498094ff3877a37f351b29e6cad9e38b2eb1ac3c0cb27ebf80aee96554f80b35e17bdb552bcd7ac8b7cb9904fea93ea5668f2057c73d38f90b5d46bb9b27ab + languageName: node + linkType: hard + "react-toastify@npm:^9.1.3": version: 9.1.3 resolution: "react-toastify@npm:9.1.3" @@ -21936,6 +22455,21 @@ __metadata: languageName: node linkType: hard +"use-callback-ref@npm:^1.3.3": + version: 1.3.3 + resolution: "use-callback-ref@npm:1.3.3" + dependencies: + tslib: "npm:^2.0.0" + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/adf06a7b6a27d3651c325ac9b66d2b82ccacaed7450b85b211d123e91d9a23cb5a587fcc6db5b4fd07ac7233e5abf024d30cf02ddc2ec46bca712151c0836151 + languageName: node + linkType: hard + "use-resize-observer@npm:^9.0.0": version: 9.1.0 resolution: "use-resize-observer@npm:9.1.0" @@ -21964,6 +22498,22 @@ __metadata: languageName: node linkType: hard +"use-sidecar@npm:^1.1.3": + version: 1.1.3 + resolution: "use-sidecar@npm:1.1.3" + dependencies: + detect-node-es: "npm:^1.1.0" + tslib: "npm:^2.0.0" + peerDependencies: + "@types/react": "*" + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 10/2fec05eb851cdfc4a4657b1dfb434e686f346c3265ffc9db8a974bb58f8128bd4a708a3cc00e8f51655fccf81822ed4419ebed42f41610589e3aab0cf2492edb + languageName: node + linkType: hard + "use@npm:^3.1.0": version: 3.1.1 resolution: "use@npm:3.1.1"