From b19f2e71ba9b72727b9e99b804ecc97c511c9ad5 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Tue, 10 Dec 2024 13:48:44 +0100 Subject: [PATCH] fix(connect): can not select files on the connection form VSCODE-658 (#898) --- package-lock.json | 231 +++++++++--------- package.json | 6 +- .../views/webview-app/overview-page.test.tsx | 4 +- .../suite/views/webviewController.test.ts | 12 +- src/views/webview-app/connection-form.tsx | 2 +- .../extension-app-message-constants.ts | 35 ++- src/views/webview-app/overview-page.tsx | 106 ++++++-- src/views/webview-app/use-connection-form.ts | 39 ++- .../webview-app/use-connection-status.ts | 11 +- .../use-detect-vscode-dark-mode.tsx | 8 +- src/views/webview-app/vscode-api.ts | 39 ++- src/views/webviewController.ts | 97 +++++++- 12 files changed, 397 insertions(+), 193 deletions(-) diff --git a/package-lock.json b/package-lock.json index 370471ccd..bdd2b5b41 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,9 @@ "@babel/core": "^7.25.8", "@babel/parser": "^7.25.8", "@babel/traverse": "^7.25.7", - "@mongodb-js/compass-components": "^1.30.1", - "@mongodb-js/connection-form": "^1.42.0", - "@mongodb-js/connection-info": "^0.9.1", + "@mongodb-js/compass-components": "^1.32.1", + "@mongodb-js/connection-form": "^1.45.1", + "@mongodb-js/connection-info": "^0.9.5", "@mongodb-js/mongodb-constants": "^0.10.3", "@mongosh/browser-runtime-electron": "^2.3.3", "@mongosh/i18n": "^2.3.3", @@ -1748,9 +1748,9 @@ } }, "node_modules/@codemirror/autocomplete": { - "version": "6.18.1", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.1.tgz", - "integrity": "sha512-iWHdj/B1ethnHRTwZj+C1obmmuCzquH29EbcKr0qIjA9NfDeBDJ7vs+WOHsFeLeflE4o+dHfYndJloMKHUkWUA==", + "version": "6.18.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.3.tgz", + "integrity": "sha512-1dNIOmiM0z4BIBwxmxEfA1yoxh1MF/6KPBbh20a5vphGV0ictKlgQsbJs6D6SkR6iJpGbpwRsa6PFMNlg9T9pQ==", "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", @@ -1765,9 +1765,9 @@ } }, "node_modules/@codemirror/commands": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.0.tgz", - "integrity": "sha512-+cduIZ2KbesDhbykV02K25A5xIVrquSPz4UxxYBemRlAT2aW8dhwUgLDwej7q/RJUHKk4nALYcR1puecDvbdqw==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.1.tgz", + "integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==", "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", @@ -1799,9 +1799,9 @@ } }, "node_modules/@codemirror/language": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.3.tgz", - "integrity": "sha512-kDqEU5sCP55Oabl6E7m5N+vZRoc0iWqgDVhEKifcHzPzjqCegcO4amfrYVL9PmPZpl4G0yjkpTpUO/Ui8CzO8A==", + "version": "6.10.6", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.6.tgz", + "integrity": "sha512-KrsbdCnxEztLVbB5PycWXFxas4EOyk/fPAfruSOnDDppevQgid2XZ+KbJ9u+fDikP/e7MW7HPBTvTb8JlZK9vA==", "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", @@ -1812,12 +1812,12 @@ } }, "node_modules/@codemirror/lint": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.2.tgz", - "integrity": "sha512-PDFG5DjHxSEjOXk9TQYYVjZDqlZTFaDBfhQixHnQOEVDDNHUbEh/hstAjcQJaA6FQdZTD1hquXTK0rVBLADR1g==", + "version": "6.8.4", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.4.tgz", + "integrity": "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==", "dependencies": { "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", + "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, @@ -1827,9 +1827,9 @@ "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==" }, "node_modules/@codemirror/view": { - "version": "6.34.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.34.1.tgz", - "integrity": "sha512-t1zK/l9UiRqwUNPm+pdIT0qzJlzuVckbTEMVNFhfWkGiBQClstzg+78vedCvLSX0xJEZ6lwZbPpnljL7L6iwMQ==", + "version": "6.35.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.35.0.tgz", + "integrity": "sha512-I0tYy63q5XkaWsJ8QRv5h6ves7kvtrBWjBcnf/bzohFJQc5c14a1AQRdE8QpPF9eMp5Mq2FMm59TCj1gDfE7kw==", "dependencies": { "@codemirror/state": "^6.4.0", "style-mod": "^4.1.0", @@ -2829,18 +2829,18 @@ } }, "node_modules/@leafygreen-ui/hooks": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/hooks/-/hooks-8.1.3.tgz", - "integrity": "sha512-UAHii7T+g8h8sSzogqUgIid64bbKPHGihAAoBpNzbNsjqFllYVC0FpF59jQeL6tCYd32C2KatWOvhYheBf1hsA==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/hooks/-/hooks-8.2.1.tgz", + "integrity": "sha512-yozp+WfMo1aNzQJG4WOa4eoxEEMK3T7Q7D2AObRWEPR+jPeeocsBKSHoAkUsfJ/8AklQ+LIWM1SvtUm4iuLXtQ==", "dependencies": { "@leafygreen-ui/lib": "^13.3.0", "lodash": "^4.17.21" } }, "node_modules/@leafygreen-ui/icon": { - "version": "12.5.4", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-12.5.4.tgz", - "integrity": "sha512-RsoIN4hfBtJDGuR5ClElCYvpX5+YqjB381EJDZQGC12iQGhhJwCuD4p4NW4O+jWXpt7KGISDKg0Ieao5R/vmpw==", + "version": "12.8.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/icon/-/icon-12.8.0.tgz", + "integrity": "sha512-LDYSFtdn+dX3/hyBJJw722grz98To+X9Nw/97F6MUk+D9eNdufzPFYQCd8iDsgUbfeSVJ/uw1PVr20QEJ7Xtcw==", "dependencies": { "@leafygreen-ui/emotion": "^4.0.8", "lodash": "^4.17.21" @@ -2946,9 +2946,9 @@ } }, "node_modules/@leafygreen-ui/lib": { - "version": "13.6.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-13.6.0.tgz", - "integrity": "sha512-4TglZhImmJ5G13nEoBsNkwBEDZLS0Qo4b3hfPnJsXQ0+BYguxExevan6S7i7hQ4iwvZekCVKGd/yrp0UonrOHQ==", + "version": "13.8.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/lib/-/lib-13.8.0.tgz", + "integrity": "sha512-WHbs0ZKzMR/UU0XmlI7+PKDzayuynTXrEQ2GwL5WpPjRElxlajkDyjkzmxXkQLIKce+T+q8ucIVlRVInDG+Qlg==", "dependencies": { "@storybook/csf": "^0.1.0", "lodash": "^4.17.21", @@ -3285,9 +3285,9 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/@leafygreen-ui/table": { - "version": "12.6.4", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/table/-/table-12.6.4.tgz", - "integrity": "sha512-bXnYUrn+SWdLcaUIjfr3CwLilpdjpLJrW8GY/co4SOqlA57/ih8pi1dei3ltmsjtCxOkupU0jZeMUoF45JNikg==", + "version": "12.7.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/table/-/table-12.7.0.tgz", + "integrity": "sha512-zsenGdk7yXu7aFELSDlGa1yjv4Q6C4KfL4o8MEqAZYXlZqzsB6z/QiZRJfVjti4vIYWVENC2GwDSLRAAUDGuuQ==", "dependencies": { "@leafygreen-ui/checkbox": "^13.1.0", "@leafygreen-ui/emotion": "^4.0.8", @@ -3297,8 +3297,9 @@ "@leafygreen-ui/lib": "^13.6.0", "@leafygreen-ui/palette": "^4.0.9", "@leafygreen-ui/polymorphic": "^2.0.0", - "@leafygreen-ui/tokens": "^2.9.0", + "@leafygreen-ui/tokens": "^2.11.0", "@leafygreen-ui/typography": "^19.2.0", + "@lg-tools/test-harnesses": "^0.1.2", "@tanstack/react-table": "^8.13.2", "lodash": "^4.17.21", "polished": "^4.2.2", @@ -3311,13 +3312,13 @@ } }, "node_modules/@leafygreen-ui/table/node_modules/@leafygreen-ui/checkbox": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/checkbox/-/checkbox-13.1.0.tgz", - "integrity": "sha512-Cu+Jqunde+yTYqdUvRe/o2gBZE/WI8nyhvS+Ozy2PB1KMN95PCQ/oCU1oMI3n5/gQTbFEXi4ia54eln0TRgh7w==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/checkbox/-/checkbox-13.1.2.tgz", + "integrity": "sha512-rdn55oDiywyk/t3wKnJKbzDn6CUtCCSm4PQF6t4svZWVaHvNzDgTDjHy5D1s8MYpFQbqhsWbJhf17tpRrzY/Mw==", "dependencies": { "@leafygreen-ui/a11y": "^1.4.13", "@leafygreen-ui/emotion": "^4.0.8", - "@leafygreen-ui/hooks": "^8.1.3", + "@leafygreen-ui/hooks": "^8.1.4", "@leafygreen-ui/lib": "^13.4.0", "@leafygreen-ui/palette": "^4.0.10", "@leafygreen-ui/tokens": "^2.5.2", @@ -3330,22 +3331,22 @@ } }, "node_modules/@leafygreen-ui/table/node_modules/@leafygreen-ui/polymorphic": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/polymorphic/-/polymorphic-2.0.1.tgz", - "integrity": "sha512-rzNoS0Q50NI6e+qkc0ytLwnCbiBEfDdLTta2WBPT7noi1yshCcPDlM7+g2R6gTRdTNVUSOLVNyt4VeLZOERc6g==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/polymorphic/-/polymorphic-2.0.2.tgz", + "integrity": "sha512-OjP+hPG/cwADShcGa1SZdm51G2wVpbNqpU0B3GonEAvGLcAvG4LDMXa7BWo3GDliNkPtVMS86w0eZzEDmLfKmQ==", "dependencies": { "@leafygreen-ui/lib": "^13.6.0", "lodash": "^4.17.21" } }, "node_modules/@leafygreen-ui/table/node_modules/@leafygreen-ui/typography": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/typography/-/typography-19.2.0.tgz", - "integrity": "sha512-57O0eplpV3nYQMVtSYuOGafPhGC26ShPDTK46HF9I9xCgLRul4YHFM3jwXQEvdWcZO5JSMHzx5iH7ec2+pHBrA==", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/typography/-/typography-19.3.0.tgz", + "integrity": "sha512-pgTRcc4usW/S9nDDzkf5Ac/JPEybhWtOnDpmrp99mAJHM6tH48Pd1HjRNHWjn6bnh0nXWjwANXX1ZEe+8ggCNg==", "dependencies": { "@leafygreen-ui/emotion": "^4.0.8", - "@leafygreen-ui/icon": "^12.5.4", - "@leafygreen-ui/lib": "^13.6.0", + "@leafygreen-ui/icon": "^12.6.0", + "@leafygreen-ui/lib": "^13.6.1", "@leafygreen-ui/palette": "^4.0.10", "@leafygreen-ui/polymorphic": "^2.0.0", "@leafygreen-ui/tokens": "^2.9.0" @@ -3472,12 +3473,13 @@ } }, "node_modules/@leafygreen-ui/tokens": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@leafygreen-ui/tokens/-/tokens-2.9.0.tgz", - "integrity": "sha512-Ogn250aFFHylmkKZAtdyS6qhA3JiHra+Zx8tMK500kkWTo8lwh7bSiK6nVwKWzkkeReEr8Iq41a08RjaRaf4HQ==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@leafygreen-ui/tokens/-/tokens-2.11.0.tgz", + "integrity": "sha512-/0G+UaDpLBLLtEP1mjGjiDnqReHufUTUkteqNUsyTOz1bpfejoo1anu3f6dZDqNlxoKhHZEEngQ+HvP1l1RJVw==", "dependencies": { - "@leafygreen-ui/lib": "^13.6.0", - "@leafygreen-ui/palette": "^4.0.9" + "@leafygreen-ui/lib": "^13.7.0", + "@leafygreen-ui/palette": "^4.0.9", + "polished": "^4.2.2" } }, "node_modules/@leafygreen-ui/tooltip": { @@ -3560,9 +3562,9 @@ } }, "node_modules/@lezer/javascript": { - "version": "1.4.19", - "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.19.tgz", - "integrity": "sha512-j44kbR1QL26l6dMunZ1uhKBFteVGLVCBGNUD2sUaMnic+rbTviVuoK0CD1l9FTW31EueWvFFswCKMH7Z+M3JRA==", + "version": "1.4.20", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.4.20.tgz", + "integrity": "sha512-Qhl3x+hVPnZkylv+BS//zx77KR4GLxM4PiL02r/D1Zoa4WLQI1A0cHuOr6k0FOTTSCPNNfeNANax0I5DWcXBYw==", "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.1.3", @@ -3657,9 +3659,9 @@ } }, "node_modules/@mongodb-js/compass-components": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@mongodb-js/compass-components/-/compass-components-1.30.1.tgz", - "integrity": "sha512-XTlQsmKagGmb2oQifkzsw7ssmYBgqm9/vKXgGKgGKTdC4Jr6k3INm+lvRp3gU54ejNfaPgV269MQWATVMGwF4w==", + "version": "1.32.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/compass-components/-/compass-components-1.32.1.tgz", + "integrity": "sha512-fP1zdixe3zTjtxAYSj4N44K0LKlz6Keqiyn1uqiPu/XXGqWYuztdmAKmwS1Ud4w0o9Q3mXOaVG7tOgeBvowgVA==", "dependencies": { "@dnd-kit/core": "^6.0.7", "@dnd-kit/sortable": "^7.0.2", @@ -3694,7 +3696,7 @@ "@leafygreen-ui/search-input": "^2.1.5", "@leafygreen-ui/segmented-control": "^8.2.10", "@leafygreen-ui/select": "^11.2.2", - "@leafygreen-ui/table": "^12.6.1", + "@leafygreen-ui/table": "^12.7.0", "@leafygreen-ui/tabs": "^11.1.13", "@leafygreen-ui/text-area": "^8.1.2", "@leafygreen-ui/text-input": "^12.1.26", @@ -3706,10 +3708,11 @@ "@react-aria/interactions": "^3.9.1", "@react-aria/utils": "^3.13.1", "@react-aria/visually-hidden": "^3.3.1", + "@tanstack/table-core": "^8.14.0", "bson": "^6.8.0", "focus-trap-react": "^9.0.2", - "hadron-document": "^8.6.4", - "hadron-type-checker": "^7.2.3", + "hadron-document": "^8.6.6", + "hadron-type-checker": "^7.2.4", "is-electron-renderer": "^2.0.1", "lodash": "^4.17.21", "polished": "^4.2.2", @@ -3722,9 +3725,9 @@ } }, "node_modules/@mongodb-js/compass-editor": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/@mongodb-js/compass-editor/-/compass-editor-0.31.1.tgz", - "integrity": "sha512-BNsgrOpJG/m1s4IKJD2HclfHR/5SJOfxay77HTrSdZR8kqlN43k9jzomVKv3LaZ4NFNHVFqpPoxyVDcswOAc/g==", + "version": "0.34.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/compass-editor/-/compass-editor-0.34.1.tgz", + "integrity": "sha512-0AclLRvldtJ48bn0soFKFw1YRDQhL43Wbom5DfbJ9xn075UITkX8fvKntkpQMYHb/gl7tDGbs2XMIZiApxLo2Q==", "dependencies": { "@codemirror/autocomplete": "^6.17.0", "@codemirror/commands": "^6.1.2", @@ -3735,7 +3738,7 @@ "@codemirror/state": "^6.1.4", "@codemirror/view": "^6.7.1", "@lezer/highlight": "^1.2.0", - "@mongodb-js/compass-components": "^1.30.1", + "@mongodb-js/compass-components": "^1.32.1", "@mongodb-js/mongodb-constants": "^0.10.0", "mongodb-query-parser": "^4.2.3", "polished": "^4.2.2", @@ -3744,54 +3747,54 @@ } }, "node_modules/@mongodb-js/compass-logging": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/@mongodb-js/compass-logging/-/compass-logging-1.4.8.tgz", - "integrity": "sha512-+mCyiKM24u6k+380/Gr59DksbW/1ago0g3BGZqR8KVFG4WVQ1UQWyan28xI81ofOuF9/Sz3qGmpG5T7lMxJOcQ==", + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@mongodb-js/compass-logging/-/compass-logging-1.4.12.tgz", + "integrity": "sha512-XVUdc/76R3yUI4vxnE1GadyuJA7+TKLT8yfynmi77wcGd1/0g0uX2yrJtGja1UsqPRVejz5mrVTjHw22yyxPYw==", "dependencies": { "debug": "^4.3.4", - "hadron-app-registry": "^9.2.7", - "hadron-ipc": "^3.2.23", + "hadron-app-registry": "^9.2.8", + "hadron-ipc": "^3.2.27", "is-electron-renderer": "^2.0.1", "mongodb-log-writer": "^1.4.2", "react": "^17.0.2" } }, "node_modules/@mongodb-js/compass-utils": { - "version": "0.6.12", - "resolved": "https://registry.npmjs.org/@mongodb-js/compass-utils/-/compass-utils-0.6.12.tgz", - "integrity": "sha512-TIeNeta2aGmkIWlzqKwkPfXUMFAxpCG9ml68wYRrKHXvWmnnCot0/lJZdXyD9PQ3U41DnF6BfmguTeo3A/Us6Q==", + "version": "0.6.16", + "resolved": "https://registry.npmjs.org/@mongodb-js/compass-utils/-/compass-utils-0.6.16.tgz", + "integrity": "sha512-BjR5Tw2AXtAf3GdZWiYmsXXjB1kWJ2Y5U7k0yLAsA8tPueaPeJAmSTc9OZM8eHCmcWrQZOSoIfW5ZNUkVoaTaw==", "dependencies": { "@electron/remote": "^2.1.2", - "electron": "^30.5.1" + "electron": "^32.2.5" } }, "node_modules/@mongodb-js/connection-form": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/connection-form/-/connection-form-1.42.0.tgz", - "integrity": "sha512-kGJRzVvYIeDHYjUEAXSEWz0ycxd3uFY+i+6ol3EA0MSY7Qd+1T8wMT78rB8EDoqDtpSUke4yBhEMlrcDHz7WLQ==", + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/connection-form/-/connection-form-1.45.1.tgz", + "integrity": "sha512-7b2BOAJFiVRRcDmzMvdmIYqgWqJuFTcdcaaLhc6oTugwfYt1Dy3/EXKp2A2R4TuU9ZX0gim8pTpHVzbH1saGfg==", "dependencies": { - "@mongodb-js/compass-components": "^1.30.1", - "@mongodb-js/compass-editor": "^0.31.1", - "@mongodb-js/connection-info": "^0.9.1", + "@mongodb-js/compass-components": "^1.32.1", + "@mongodb-js/compass-editor": "^0.34.1", + "@mongodb-js/connection-info": "^0.9.5", "@mongodb-js/shell-bson-parser": "^1.1.2", "lodash": "^4.17.21", "mongodb": "^6.9.0", "mongodb-build-info": "^1.7.2", "mongodb-connection-string-url": "^3.0.1", - "mongodb-data-service": "^22.23.5", + "mongodb-data-service": "^22.23.9", "mongodb-query-parser": "^4.2.3", "react": "^17.0.2" } }, "node_modules/@mongodb-js/connection-info": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@mongodb-js/connection-info/-/connection-info-0.9.1.tgz", - "integrity": "sha512-DrM7v/QP+8LUrYNwS8zF4a6FFXW4wI/7KSjkO6nis9ivA/n5uQwpr6JkRP5wMe5j1m1AzsEcb3sVK9ZrEvVo+w==", + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@mongodb-js/connection-info/-/connection-info-0.9.5.tgz", + "integrity": "sha512-CeNjUmt8hEIoBmzEnLoNhjQjBqAaP9W/nNurxGNpbvkE/8Pw+GUR5OkiXDN/7f2mWm+g1P/RTIlWyK1wsqyRyQ==", "dependencies": { "lodash": "^4.17.21", "mongodb": "^6.9.0", "mongodb-connection-string-url": "^3.0.1", - "mongodb-data-service": "^22.23.5" + "mongodb-data-service": "^22.23.9" } }, "node_modules/@mongodb-js/devtools-proxy-support": { @@ -5280,11 +5283,11 @@ } }, "node_modules/@tanstack/react-table": { - "version": "8.17.3", - "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.17.3.tgz", - "integrity": "sha512-5gwg5SvPD3lNAXPuJJz1fOCEZYk9/GeBFH3w/hCgnfyszOIzwkwgp5I7Q4MJtn0WECp84b5STQUDdmvGi8m3nA==", + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.5.tgz", + "integrity": "sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==", "dependencies": { - "@tanstack/table-core": "8.17.3" + "@tanstack/table-core": "8.20.5" }, "engines": { "node": ">=12" @@ -5299,9 +5302,9 @@ } }, "node_modules/@tanstack/table-core": { - "version": "8.17.3", - "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.17.3.tgz", - "integrity": "sha512-mPBodDGVL+fl6d90wUREepHa/7lhsghg2A3vFpakEhrhtbIlgNAZiMr7ccTgak5qbHqF14Fwy+W1yFWQt+WmYQ==", + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", "engines": { "node": ">=12" }, @@ -9561,9 +9564,9 @@ } }, "node_modules/electron": { - "version": "30.5.1", - "resolved": "https://registry.npmjs.org/electron/-/electron-30.5.1.tgz", - "integrity": "sha512-AhL7+mZ8Lg14iaNfoYTkXQ2qee8mmsQyllKdqxlpv/zrKgfxz6jNVtcRRbQtLxtF8yzcImWdfTQROpYiPumdbw==", + "version": "32.2.6", + "resolved": "https://registry.npmjs.org/electron/-/electron-32.2.6.tgz", + "integrity": "sha512-aGG1MLvWCf+ECUFBCmaCF52F8312OPAJfph2D0FSsFmlbfnJuNevZCbty2lFzsiIMtU7/QRo6d0ksbgR4s7y3w==", "hasInstallScript": true, "dependencies": { "@electron/get": "^2.0.0", @@ -9583,9 +9586,9 @@ "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==" }, "node_modules/electron/node_modules/@types/node": { - "version": "20.16.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.13.tgz", - "integrity": "sha512-GjQ7im10B0labo8ZGXDGROUl9k0BNyDgzfGpb4g/cl+4yYDWVKcozANF4FGr4/p0O/rAkQClM6Wiwkije++1Tg==", + "version": "20.17.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.9.tgz", + "integrity": "sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==", "dependencies": { "undici-types": "~6.19.2" } @@ -12027,9 +12030,9 @@ } }, "node_modules/hadron-app-registry": { - "version": "9.2.7", - "resolved": "https://registry.npmjs.org/hadron-app-registry/-/hadron-app-registry-9.2.7.tgz", - "integrity": "sha512-j9gzTlzyuRxis1ywcoDRyOUJtDRktc/ZFXjbxtxTQMuDnSJYtshbzF1D7N38fvs61gDQNQ20egRTDg9oPAhQCA==", + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/hadron-app-registry/-/hadron-app-registry-9.2.8.tgz", + "integrity": "sha512-amnO2cicQ1VLrw8N122fv/fBsFHDG0Uy36FFwegpc6/FvIroS1JvRuOy/i9fN8h3NrwdIKbGilVhdU5W/xU96Q==", "dependencies": { "eventemitter3": "^4.0.0", "react": "^17.0.2", @@ -12082,30 +12085,30 @@ } }, "node_modules/hadron-document": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/hadron-document/-/hadron-document-8.6.4.tgz", - "integrity": "sha512-BJdtCE34krvjlUOx+OeNNNSlf29vrnB0V2RuodfXhBB/Sast7wOb9Ig5NjCCHlfzKiGmw+5MIilqIit2Titewg==", + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/hadron-document/-/hadron-document-8.6.6.tgz", + "integrity": "sha512-RXkcQYQC5qoxf0ohNX4Hp19bVVb57IGWF+WUGquTxLL2T5Lq80oYk/79xkrzJJNnnyXLxUWyGcF2/e2QSMI6zA==", "dependencies": { "bson": "^6.8.0", "eventemitter3": "^4.0.0", - "hadron-type-checker": "^7.2.3", + "hadron-type-checker": "^7.2.4", "lodash": "^4.17.21" } }, "node_modules/hadron-ipc": { - "version": "3.2.23", - "resolved": "https://registry.npmjs.org/hadron-ipc/-/hadron-ipc-3.2.23.tgz", - "integrity": "sha512-KQLfJFVxPJtMrF378V/M8hZimrgUT8wM6//3WGF8Lx1+G5r3CV07QNyxkb9NbEii0FA9Xn3A9Yyg/mDBXNP2iQ==", + "version": "3.2.27", + "resolved": "https://registry.npmjs.org/hadron-ipc/-/hadron-ipc-3.2.27.tgz", + "integrity": "sha512-ZnVbuoXJ9zLfy+abmLwdnEU23VQ0ZcIzlLUovEKW9d5uMtE8j7q+t3/igdxacPFPywBbR4alhOUml4NgVr0cxA==", "dependencies": { "debug": "^4.3.4", - "electron": "^30.5.1", + "electron": "^32.2.5", "is-electron-renderer": "^2.0.1" } }, "node_modules/hadron-type-checker": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/hadron-type-checker/-/hadron-type-checker-7.2.3.tgz", - "integrity": "sha512-IeqnXS2r3874S8ZByEKwXrQDFdHmYpkEB1G40zIU0toW/UrIrLhD4HokNWVhTWck5GSvlEMEAG924sBQdRlnRQ==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/hadron-type-checker/-/hadron-type-checker-7.2.4.tgz", + "integrity": "sha512-I6mht/w0/HLIu/AE4na8WYDrjyo9tvVC7j+NMs+8icW/P8xSP4WgDk9KyvgfgBhMOtEMY2kQI5AuKKCncsdF+A==", "dependencies": { "bson": "^6.8.0", "lodash": "^4.17.21" @@ -14885,12 +14888,12 @@ } }, "node_modules/mongodb-data-service": { - "version": "22.23.5", - "resolved": "https://registry.npmjs.org/mongodb-data-service/-/mongodb-data-service-22.23.5.tgz", - "integrity": "sha512-US4zCIkJ44pVMROine5mvykg374M60WQ4fu1K92+u/4vBP/Z5ZqlxIytBj1OH1x04S0yvyN/Y6BCVzug4EWAKw==", + "version": "22.23.9", + "resolved": "https://registry.npmjs.org/mongodb-data-service/-/mongodb-data-service-22.23.9.tgz", + "integrity": "sha512-aaKNTHWD+lOdrbFdH4zJK/9OlsSFaAxkAcy0fIbXOtbUy26ShX8018QfoRbwyqGbmJr0iDZd4Wd1Gpqc3bPaLw==", "dependencies": { - "@mongodb-js/compass-logging": "^1.4.8", - "@mongodb-js/compass-utils": "^0.6.12", + "@mongodb-js/compass-logging": "^1.4.12", + "@mongodb-js/compass-utils": "^0.6.16", "@mongodb-js/devtools-connect": "^3.3.1", "@mongodb-js/devtools-proxy-support": "^0.4.1", "bson": "^6.8.0", diff --git a/package.json b/package.json index f4676cdbe..4e0fac3db 100644 --- a/package.json +++ b/package.json @@ -1209,9 +1209,9 @@ "@babel/core": "^7.25.8", "@babel/parser": "^7.25.8", "@babel/traverse": "^7.25.7", - "@mongodb-js/compass-components": "^1.30.1", - "@mongodb-js/connection-form": "^1.42.0", - "@mongodb-js/connection-info": "^0.9.1", + "@mongodb-js/compass-components": "^1.32.1", + "@mongodb-js/connection-form": "^1.45.1", + "@mongodb-js/connection-info": "^0.9.5", "@mongodb-js/mongodb-constants": "^0.10.3", "@mongosh/browser-runtime-electron": "^2.3.3", "@mongosh/i18n": "^2.3.3", diff --git a/src/test/suite/views/webview-app/overview-page.test.tsx b/src/test/suite/views/webview-app/overview-page.test.tsx index 459e2d75b..650264a57 100644 --- a/src/test/suite/views/webview-app/overview-page.test.tsx +++ b/src/test/suite/views/webview-app/overview-page.test.tsx @@ -156,7 +156,7 @@ describe('OverviewPage test suite', function () { .getCalls() .filter( (call) => - call.args[0].command === MESSAGE_TYPES.EDIT_AND_CONNECT_CONNECTION + call.args[0].command === MESSAGE_TYPES.EDIT_CONNECTION_AND_CONNECT ); }; expect(getConnectMessages()).to.have.length(0); @@ -165,7 +165,7 @@ describe('OverviewPage test suite', function () { expect(connectMessages).to.have.length(1); expect(connectMessages[0].args[0]).to.deep.equal({ - command: 'EDIT_AND_CONNECT_CONNECTION', + command: 'EDIT_CONNECTION_AND_CONNECT', connectionInfo: { id: 'pear', connectionOptions: { diff --git a/src/test/suite/views/webviewController.test.ts b/src/test/suite/views/webviewController.test.ts index 675a484bf..f0cb964dd 100644 --- a/src/test/suite/views/webviewController.test.ts +++ b/src/test/suite/views/webviewController.test.ts @@ -124,7 +124,7 @@ suite('Webview Test Suite', () => { const extensionPath = mdbTestExtension.extensionContextStub.extensionPath; const htmlString = getWebviewContent({ extensionPath, - telemetryUserId: 'MOCK_ANONYMOU_ID', + telemetryUserId: 'MOCK_ANONYMOUS_ID', webview: { asWebviewUri: (jsUri) => { return jsUri; @@ -133,7 +133,7 @@ suite('Webview Test Suite', () => { }); expect(htmlString).to.include( - ">window['VSCODE_EXTENSION_SEGMENT_ANONYMOUS_ID'] = 'MOCK_ANONYMOU_ID';" + ">window['VSCODE_EXTENSION_SEGMENT_ANONYMOUS_ID'] = 'MOCK_ANONYMOUS_ID';" ); }); @@ -143,7 +143,7 @@ suite('Webview Test Suite', () => { extensionPath, telemetryUserId: 'test', webview: { - asWebviewUri: (jsUri) => { + asWebviewUri: (jsUri: vscode.Uri) => { return jsUri; }, } as unknown as vscode.Webview, @@ -177,7 +177,7 @@ suite('Webview Test Suite', () => { extensionPath, telemetryUserId: 'test', webview: { - asWebviewUri: (jsUri) => { + asWebviewUri: (jsUri: vscode.Uri) => { return jsUri; }, } as unknown as vscode.Webview, @@ -368,7 +368,7 @@ suite('Webview Test Suite', () => { onDidReceiveMessage: (callback): void => { messageReceived = callback; }, - asWebviewUri: () => '', + asWebviewUri: (): string => '', }; const fakeVSCodeExecuteCommand = sandbox .stub(vscode.commands, 'executeCommand') @@ -538,7 +538,7 @@ suite('Webview Test Suite', () => { // Mock a connection status request call. messageReceived({ - command: MESSAGE_TYPES.EDIT_AND_CONNECT_CONNECTION, + command: MESSAGE_TYPES.EDIT_CONNECTION_AND_CONNECT, connectionInfo: { id: 'pineapple', connectionOptions: { diff --git a/src/views/webview-app/connection-form.tsx b/src/views/webview-app/connection-form.tsx index 638e1deb8..111979820 100644 --- a/src/views/webview-app/connection-form.tsx +++ b/src/views/webview-app/connection-form.tsx @@ -67,7 +67,7 @@ const ConnectionForm: React.FunctionComponent< // Warning: This property may be removed in future // modal releases. contentClassName={modalContentStyles} - setOpen={() => onClose()} + setOpen={(): void => onClose()} open={open} size="large" > diff --git a/src/views/webview-app/extension-app-message-constants.ts b/src/views/webview-app/extension-app-message-constants.ts index 6407587b6..80a197347 100644 --- a/src/views/webview-app/extension-app-message-constants.ts +++ b/src/views/webview-app/extension-app-message-constants.ts @@ -1,4 +1,5 @@ import type { ConnectionOptions } from 'mongodb-data-service'; +import type { FileChooserOptions } from './use-connection-form'; export enum CONNECTION_STATUS { LOADING = 'LOADING', // When the connection status has not yet been shared from the extension. @@ -19,9 +20,11 @@ export enum MESSAGE_TYPES { CANCEL_CONNECT = 'CANCEL_CONNECT', CONNECT_RESULT = 'CONNECT_RESULT', CONNECTION_FORM_OPENED = 'CONNECTION_FORM_OPENED', + OPEN_FILE_CHOOSER = 'OPEN_FILE_CHOOSER', + OPEN_FILE_CHOOSER_RESULT = 'OPEN_FILE_CHOOSER_RESULT', CONNECTION_STATUS_MESSAGE = 'CONNECTION_STATUS_MESSAGE', OPEN_EDIT_CONNECTION = 'OPEN_EDIT_CONNECTION', - EDIT_AND_CONNECT_CONNECTION = 'EDIT_AND_CONNECT_CONNECTION', + EDIT_CONNECTION_AND_CONNECT = 'EDIT_CONNECTION_AND_CONNECT', EXTENSION_LINK_CLICKED = 'EXTENSION_LINK_CLICKED', CREATE_NEW_PLAYGROUND = 'CREATE_NEW_PLAYGROUND', GET_CONNECTION_STATUS = 'GET_CONNECTION_STATUS', @@ -58,14 +61,20 @@ export interface OpenEditConnectionMessage extends BasicWebviewMessage { }; } -export interface EditAndConnectConnection extends BasicWebviewMessage { - command: MESSAGE_TYPES.EDIT_AND_CONNECT_CONNECTION; +export interface EditConnectionAndConnectMessage extends BasicWebviewMessage { + command: MESSAGE_TYPES.EDIT_CONNECTION_AND_CONNECT; connectionInfo: { id: string; connectionOptions: ConnectionOptions; }; } +export interface OpenFileChooserMessage extends BasicWebviewMessage { + command: MESSAGE_TYPES.OPEN_FILE_CHOOSER; + fileChooserOptions: FileChooserOptions; + requestId: string; +} + export interface ConnectMessage extends BasicWebviewMessage { command: MESSAGE_TYPES.CONNECT; connectionInfo: { @@ -85,6 +94,16 @@ export interface ConnectResultsMessage extends BasicWebviewMessage { connectionId: string; } +export type FileChooserResult = + | { canceled: false; filePaths: string[] } + | { canceled: false; filePath?: string }; + +export interface OpenFileChooserResultMessage extends BasicWebviewMessage { + command: MESSAGE_TYPES.OPEN_FILE_CHOOSER_RESULT; + fileChooserResult: FileChooserResult; + requestId: string; +} + export interface GetConnectionStatusMessage extends BasicWebviewMessage { command: MESSAGE_TYPES.GET_CONNECTION_STATUS; } @@ -113,7 +132,7 @@ export interface ThemeChangedMessage extends BasicWebviewMessage { darkMode: boolean; } -export type MESSAGE_FROM_WEBVIEW_TO_EXTENSION = +export type MessageFromWebviewToExtension = | ConnectMessage | CancelConnectMessage | ConnectionFormOpenedMessage @@ -123,10 +142,12 @@ export type MESSAGE_FROM_WEBVIEW_TO_EXTENSION = | OpenConnectionStringInputMessage | OpenTrustedLinkMessage | RenameConnectionMessage - | EditAndConnectConnection; + | EditConnectionAndConnectMessage + | OpenFileChooserMessage; -export type MESSAGE_FROM_EXTENSION_TO_WEBVIEW = +export type MessageFromExtensionToWebview = | ConnectResultsMessage | ConnectionStatusMessage | ThemeChangedMessage - | OpenEditConnectionMessage; + | OpenEditConnectionMessage + | OpenFileChooserResultMessage; diff --git a/src/views/webview-app/overview-page.tsx b/src/views/webview-app/overview-page.tsx index 1231e4b8f..ea5bfe387 100644 --- a/src/views/webview-app/overview-page.tsx +++ b/src/views/webview-app/overview-page.tsx @@ -1,10 +1,15 @@ import React, { useCallback, useLayoutEffect, useState } from 'react'; +import type { ElectronShowFileDialogProvider } from '@mongodb-js/compass-components'; import { HorizontalRule, css, resetGlobalCSS, spacing, + FileInputBackendProvider, + createElectronFileInputBackend, + type ElectronFileDialogOptions, } from '@mongodb-js/compass-components'; +import type { ConnectionOptions } from 'mongodb-data-service'; import OverviewHeader from './overview-header'; import ConnectionStatus from './connection-status'; @@ -12,7 +17,12 @@ import ConnectHelper from './connect-helper'; import AtlasCta from './atlas-cta'; import ResourcesPanel from './resources-panel/panel'; import { ConnectionForm } from './connection-form'; -import useConnectionForm from './use-connection-form'; +import useConnectionForm, { + FILE_CHOOSER_MODE, + type FileChooserOptions, +} from './use-connection-form'; +import type { MessageFromExtensionToWebview } from './extension-app-message-constants'; +import { MESSAGE_TYPES } from './extension-app-message-constants'; const pageStyles = css({ width: '90%', @@ -39,6 +49,7 @@ const OverviewPage: React.FC = () => { handleCancelConnectClicked, handleSaveConnectionClicked, handleConnectClicked, + handleOpenFileChooser, } = useConnectionForm(); const handleResourcesPanelClose = useCallback( () => setShowResourcesPanel(false), @@ -55,30 +66,91 @@ const OverviewPage: React.FC = () => { resetGlobalCSS(); }, []); + function handleOpenFileChooserResult( + options: FileChooserOptions + ): Promise { + const requestId = handleOpenFileChooser(options); + return new Promise((resolve) => { + const messageHandler = ( + event: MessageEvent + ): void => { + const message = event.data; + if ( + message.command === MESSAGE_TYPES.OPEN_FILE_CHOOSER_RESULT && + message.requestId === requestId + ) { + window.removeEventListener('message', messageHandler); + resolve(message.fileChooserResult as T); + } + }; + window.addEventListener('message', messageHandler); + }); + } + + // Electron 32.0 removed support for the `path` property of the Web File object in favor of the webUtils.getPathForFile method. + // https://github.com/electron/electron/blob/83d704009687956fb4b69cb13ab03664d7950118/docs/breaking-changes.md%23removed-filepath + // We can not import `dialog` and `webUtils` from 'electron' in the sandboxed webview. + // To work around this, we use a custom dialog provider that uses webview APIs + // to send a message to the extension process to open the electron file dialog + // and listen for the response to get the file path and send them to the electron file input backend. + const dialogProvider: ElectronShowFileDialogProvider = { + getCurrentWindow(): void {}, + dialog: { + async showSaveDialog( + window: void, + electronFileDialogOptions: Partial + ): Promise<{ canceled: boolean; filePath?: string }> { + return handleOpenFileChooserResult({ + electronFileDialogOptions, + mode: FILE_CHOOSER_MODE.SAVE, + }); + }, + async showOpenDialog( + window: void, + electronFileDialogOptions: Partial + ): Promise<{ canceled: boolean; filePaths: string[] }> { + return handleOpenFileChooserResult({ + electronFileDialogOptions, + mode: FILE_CHOOSER_MODE.OPEN, + }); + }, + }, + }; + return (
{showResourcesPanel && ( )} {isConnectionFormOpen && ( - { - void handleSaveConnectionClicked({ - id, - connectionOptions, - }); - handleConnectClicked({ + + + }: { + id: string; + connectionOptions: ConnectionOptions; + }): void => { + void handleSaveConnectionClicked(); + handleConnectClicked({ + id, + connectionOptions, + }); + }} + onCancelConnectClicked={handleCancelConnectClicked} + onClose={closeConnectionForm} + open={isConnectionFormOpen} + connectionErrorMessage={connectionErrorMessage} + /> + )} diff --git a/src/views/webview-app/use-connection-form.ts b/src/views/webview-app/use-connection-form.ts index c0edd7989..e21f2723d 100644 --- a/src/views/webview-app/use-connection-form.ts +++ b/src/views/webview-app/use-connection-form.ts @@ -7,9 +7,21 @@ import { sendCancelConnectToExtension, sendFormOpenedToExtension, sendEditConnectionToExtension, + sendOpenFileChooserToExtension, } from './vscode-api'; import { MESSAGE_TYPES } from './extension-app-message-constants'; -import type { MESSAGE_FROM_EXTENSION_TO_WEBVIEW } from './extension-app-message-constants'; +import type { MessageFromExtensionToWebview } from './extension-app-message-constants'; +import type { ElectronFileDialogOptions } from '@mongodb-js/compass-components'; + +export enum FILE_CHOOSER_MODE { + OPEN = 'open', + SAVE = 'save', +} + +export type FileChooserOptions = { + electronFileDialogOptions?: Partial; + mode: FILE_CHOOSER_MODE; +}; type ConnectionInfo = { id: string; @@ -132,7 +144,7 @@ export default function useConnectionForm() { useEffect(() => { const handleConnectResultResponse = (event) => { - const message: MESSAGE_FROM_EXTENSION_TO_WEBVIEW = event.data; + const message: MessageFromExtensionToWebview = event.data; if ( message.command === MESSAGE_TYPES.CONNECT_RESULT && message.connectionId === initialConnectionInfo.id @@ -152,7 +164,7 @@ export default function useConnectionForm() { useEffect(() => { const handleConnectResultResponse = (event) => { - const message: MESSAGE_FROM_EXTENSION_TO_WEBVIEW = event.data; + const message: MessageFromExtensionToWebview = event.data; if (message.command === MESSAGE_TYPES.OPEN_EDIT_CONNECTION) { dispatch({ type: 'open-edit-connection', @@ -167,7 +179,7 @@ export default function useConnectionForm() { } }; window.addEventListener('message', handleConnectResultResponse); - return () => { + return (): void => { window.removeEventListener('message', handleConnectResultResponse); }; }, []); @@ -177,33 +189,34 @@ export default function useConnectionForm() { isConnecting, initialConnectionInfo, connectionErrorMessage, - openConnectionForm: () => { + openConnectionForm: (): void => { dispatch({ type: 'open-connection-form', }); sendFormOpenedToExtension(); }, - closeConnectionForm: () => { + closeConnectionForm: (): void => { dispatch({ type: 'close-connection-form', }); }, - handleCancelConnectClicked: () => { + handleOpenFileChooser: (options: FileChooserOptions): string => { + const requestId = uuidv4(); + sendOpenFileChooserToExtension(options, requestId); + return requestId; + }, + handleCancelConnectClicked: (): void => { sendCancelConnectToExtension(); }, // eslint-disable-next-line @typescript-eslint/no-unused-vars - handleSaveConnectionClicked: (connectionAttempt: { - id: string; - connectionOptions: ConnectionOptions; - }) => { + handleSaveConnectionClicked: (): Promise => { // no-op, this cannot be called as don't set the `showFavoriteActions` setting. - return Promise.resolve(); }, handleConnectClicked: (connectionAttempt: { id: string; connectionOptions: ConnectionOptions; - }) => { + }): void => { dispatch({ type: 'attempt-connect', }); diff --git a/src/views/webview-app/use-connection-status.ts b/src/views/webview-app/use-connection-status.ts index c570661bc..7c5944e2d 100644 --- a/src/views/webview-app/use-connection-status.ts +++ b/src/views/webview-app/use-connection-status.ts @@ -1,21 +1,24 @@ import { useState, useEffect } from 'react'; import { CONNECTION_STATUS, - type MESSAGE_FROM_EXTENSION_TO_WEBVIEW, + type MessageFromExtensionToWebview, MESSAGE_TYPES, } from './extension-app-message-constants'; import vscode from './vscode-api'; const CONNECTION_STATUS_POLLING_FREQ_MS = 1000; -const useConnectionStatus = () => { +const useConnectionStatus = (): { + connectionStatus: CONNECTION_STATUS; + connectionName: string; +} => { const [connectionStatus, setConnectionStatus] = useState( CONNECTION_STATUS.LOADING ); const [connectionName, setConnectionName] = useState(''); useEffect(() => { const handleConnectionStatusResponse = (event) => { - const message: MESSAGE_FROM_EXTENSION_TO_WEBVIEW = event.data; + const message: MessageFromExtensionToWebview = event.data; if (message.command === MESSAGE_TYPES.CONNECTION_STATUS_MESSAGE) { setConnectionStatus(message.connectionStatus); setConnectionName(message.activeConnectionName); @@ -23,7 +26,7 @@ const useConnectionStatus = () => { }; window.addEventListener('message', handleConnectionStatusResponse); - const requestConnectionStatus = () => + const requestConnectionStatus = (): void => vscode.postMessage({ command: MESSAGE_TYPES.GET_CONNECTION_STATUS, }); diff --git a/src/views/webview-app/use-detect-vscode-dark-mode.tsx b/src/views/webview-app/use-detect-vscode-dark-mode.tsx index 1e62eedfc..640c231aa 100644 --- a/src/views/webview-app/use-detect-vscode-dark-mode.tsx +++ b/src/views/webview-app/use-detect-vscode-dark-mode.tsx @@ -1,23 +1,23 @@ import { useEffect, useState } from 'react'; import { - type MESSAGE_FROM_EXTENSION_TO_WEBVIEW, + type MessageFromExtensionToWebview, MESSAGE_TYPES, } from './extension-app-message-constants'; -export const useDetectVsCodeDarkMode = () => { +export const useDetectVsCodeDarkMode = (): boolean => { const [darkModeDetected, setDarkModeDetected] = useState( globalThis.document.body.classList.contains('vscode-dark') || globalThis.document.body.classList.contains('vscode-high-contrast') ); useEffect(() => { const onThemeChanged = (event) => { - const message: MESSAGE_FROM_EXTENSION_TO_WEBVIEW = event.data; + const message: MessageFromExtensionToWebview = event.data; if (message.command === MESSAGE_TYPES.THEME_CHANGED) { setDarkModeDetected(message.darkMode); } }; window.addEventListener('message', onThemeChanged); - return () => window.removeEventListener('message', onThemeChanged); + return (): void => window.removeEventListener('message', onThemeChanged); }, []); return darkModeDetected; diff --git a/src/views/webview-app/vscode-api.ts b/src/views/webview-app/vscode-api.ts index 90585bf9f..29bb13102 100644 --- a/src/views/webview-app/vscode-api.ts +++ b/src/views/webview-app/vscode-api.ts @@ -1,11 +1,12 @@ import { MESSAGE_TYPES, - type MESSAGE_FROM_WEBVIEW_TO_EXTENSION, + type MessageFromWebviewToExtension, type ConnectMessage, } from './extension-app-message-constants'; +import type { FileChooserOptions } from './use-connection-form'; interface VSCodeApi { - postMessage: (message: MESSAGE_FROM_WEBVIEW_TO_EXTENSION) => void; + postMessage: (message: MessageFromWebviewToExtension) => void; } declare const acquireVsCodeApi: () => VSCodeApi; @@ -13,23 +14,34 @@ const vscode = acquireVsCodeApi(); export const sendEditConnectionToExtension = ( connectionInfo: ConnectMessage['connectionInfo'] -) => { +): void => { vscode.postMessage({ - command: MESSAGE_TYPES.EDIT_AND_CONNECT_CONNECTION, + command: MESSAGE_TYPES.EDIT_CONNECTION_AND_CONNECT, connectionInfo, }); }; export const sendConnectToExtension = ( connectionInfo: ConnectMessage['connectionInfo'] -) => { +): void => { vscode.postMessage({ command: MESSAGE_TYPES.CONNECT, connectionInfo, }); }; -export const sendCancelConnectToExtension = () => { +export const sendOpenFileChooserToExtension = ( + fileChooserOptions: FileChooserOptions, + requestId: string +): void => { + vscode.postMessage({ + command: MESSAGE_TYPES.OPEN_FILE_CHOOSER, + fileChooserOptions, + requestId, + }); +}; + +export const sendCancelConnectToExtension = (): void => { vscode.postMessage({ command: MESSAGE_TYPES.CANCEL_CONNECT, }); @@ -37,31 +49,34 @@ export const sendCancelConnectToExtension = () => { // When the form is opened we want to close the connection string // input if it's open, so we message the extension. -export const sendFormOpenedToExtension = () => { +export const sendFormOpenedToExtension = (): void => { vscode.postMessage({ command: MESSAGE_TYPES.CONNECTION_FORM_OPENED, }); }; -export const renameActiveConnection = () => { +export const renameActiveConnection = (): void => { vscode.postMessage({ command: MESSAGE_TYPES.RENAME_ACTIVE_CONNECTION, }); }; -export const createNewPlayground = () => { +export const createNewPlayground = (): void => { vscode.postMessage({ command: MESSAGE_TYPES.CREATE_NEW_PLAYGROUND, }); }; -export const connectWithConnectionString = () => { +export const connectWithConnectionString = (): void => { vscode.postMessage({ command: MESSAGE_TYPES.OPEN_CONNECTION_STRING_INPUT, }); }; -export const trackExtensionLinkClicked = (screen: string, linkId: string) => { +export const trackExtensionLinkClicked = ( + screen: string, + linkId: string +): void => { vscode.postMessage({ command: MESSAGE_TYPES.EXTENSION_LINK_CLICKED, screen, @@ -69,7 +84,7 @@ export const trackExtensionLinkClicked = (screen: string, linkId: string) => { }); }; -export const openTrustedLink = (linkTo: string) => { +export const openTrustedLink = (linkTo: string): void => { vscode.postMessage({ command: MESSAGE_TYPES.OPEN_TRUSTED_LINK, linkTo, diff --git a/src/views/webviewController.ts b/src/views/webviewController.ts index fac9a83d6..fbf0c81cb 100644 --- a/src/views/webviewController.ts +++ b/src/views/webviewController.ts @@ -7,7 +7,7 @@ import type ConnectionController from '../connectionController'; import { ConnectionTypes } from '../connectionController'; import { createLogger } from '../logging'; import EXTENSION_COMMANDS from '../commands'; -import type { MESSAGE_FROM_WEBVIEW_TO_EXTENSION } from './webview-app/extension-app-message-constants'; +import type { MessageFromWebviewToExtension } from './webview-app/extension-app-message-constants'; import { MESSAGE_TYPES, VSCODE_EXTENSION_OIDC_DEVICE_AUTH_ID, @@ -18,10 +18,11 @@ import type { StorageController } from '../storage'; import type TelemetryService from '../telemetry/telemetryService'; import { getFeatureFlagsScript } from '../featureFlags'; import { TelemetryEventTypes } from '../telemetry/telemetryService'; +import type { FileChooserOptions } from './webview-app/use-connection-form'; const log = createLogger('webview controller'); -const getNonce = () => { +const getNonce = (): string => { return crypto.randomBytes(16).toString('base64'); }; @@ -105,6 +106,75 @@ export default class WebviewController { this._themeChangedSubscription?.dispose(); } + handleWebviewOpenFileChooserAttempt = async ({ + panel, + fileChooserOptions, + requestId, + }: { + panel: vscode.WebviewPanel; + fileChooserOptions: FileChooserOptions; + requestId: string; + }): Promise => { + let files: vscode.Uri[] | vscode.Uri | undefined; + + try { + if (fileChooserOptions.mode === 'open') { + files = await vscode.window.showOpenDialog({ + defaultUri: vscode.Uri.from({ + path: fileChooserOptions.electronFileDialogOptions?.defaultPath, + scheme: 'file', + }), + openLabel: fileChooserOptions.electronFileDialogOptions?.buttonLabel, + filters: + fileChooserOptions.electronFileDialogOptions?.filters?.reduce( + (acc, filter) => { + acc[filter.name] = filter.extensions; + return acc; + }, + {} + ), + title: fileChooserOptions.electronFileDialogOptions?.title, + }); + } else if (fileChooserOptions.mode === 'save') { + files = await vscode.window.showSaveDialog({ + defaultUri: vscode.Uri.from({ + path: fileChooserOptions.electronFileDialogOptions?.defaultPath, + scheme: 'file', + }), + saveLabel: fileChooserOptions.electronFileDialogOptions?.buttonLabel, + filters: + fileChooserOptions.electronFileDialogOptions?.filters?.reduce( + (acc, filter) => { + acc[filter.name] = filter.extensions; + return acc; + }, + {} + ), + title: fileChooserOptions.electronFileDialogOptions?.title, + }); + } + } catch (error) { + void vscode.window.showErrorMessage( + `Unable to open file chooser dialog: ${error}` + ); + } + + void panel.webview.postMessage({ + command: MESSAGE_TYPES.OPEN_FILE_CHOOSER_RESULT, + fileChooserResult: { + canceled: false, + ...(Array.isArray(files) + ? { + filePaths: files + ? files?.map((file: vscode.Uri) => file.fsPath) + : [], + } + : { filePath: files?.fsPath }), + }, + requestId, + }); + }; + handleWebviewConnectAttempt = async ({ panel, connection, @@ -116,7 +186,7 @@ export default class WebviewController { id: string; }; isEditingConnection?: boolean; - }) => { + }): Promise => { try { const { successfullyConnected, connectionErrorMessage } = isEditingConnection @@ -159,7 +229,7 @@ export default class WebviewController { // eslint-disable-next-line complexity handleWebviewMessage = async ( - message: MESSAGE_FROM_WEBVIEW_TO_EXTENSION, + message: MessageFromWebviewToExtension, panel: vscode.WebviewPanel ): Promise => { switch (message.command) { @@ -172,7 +242,7 @@ export default class WebviewController { case MESSAGE_TYPES.CANCEL_CONNECT: this._connectionController.cancelConnectionAttempt(); return; - case MESSAGE_TYPES.EDIT_AND_CONNECT_CONNECTION: + case MESSAGE_TYPES.EDIT_CONNECTION_AND_CONNECT: await this.handleWebviewConnectAttempt({ panel, connection: message.connectionInfo, @@ -180,6 +250,13 @@ export default class WebviewController { }); this._telemetryService.track(TelemetryEventTypes.CONNECTION_EDITED); return; + case MESSAGE_TYPES.OPEN_FILE_CHOOSER: + await this.handleWebviewOpenFileChooserAttempt({ + panel, + fileChooserOptions: message.fileChooserOptions, + requestId: message.requestId, + }); + return; case MESSAGE_TYPES.CREATE_NEW_PLAYGROUND: void vscode.commands.executeCommand( EXTENSION_COMMANDS.MDB_CREATE_PLAYGROUND_FROM_OVERVIEW_PAGE @@ -231,7 +308,7 @@ export default class WebviewController { }; onReceivedWebviewMessage = async ( - message: MESSAGE_FROM_WEBVIEW_TO_EXTENSION, + message: MessageFromWebviewToExtension, panel: vscode.WebviewPanel ): Promise => { // Ensure handling message from the webview can't crash the extension. @@ -243,13 +320,13 @@ export default class WebviewController { } }; - onWebviewPanelClosed = (disposedPanel: vscode.WebviewPanel) => { + onWebviewPanelClosed = (disposedPanel: vscode.WebviewPanel): void => { this._activeWebviewPanels = this._activeWebviewPanels.filter( (panel) => panel !== disposedPanel ); }; - onThemeChanged = (theme: vscode.ColorTheme) => { + onThemeChanged = (theme: vscode.ColorTheme): void => { const darkModeDetected = theme.kind === vscode.ColorThemeKind.Dark || theme.kind === vscode.ColorThemeKind.HighContrast; @@ -278,7 +355,7 @@ export default class WebviewController { connectionOptions: ConnectionOptions; }; context: vscode.ExtensionContext; - }) => { + }): Promise => { const webviewPanel = this.openWebview(context); // Wait for the panel to open. @@ -334,7 +411,7 @@ export default class WebviewController { // Handle messages from the webview. panel.webview.onDidReceiveMessage( - (message: MESSAGE_FROM_WEBVIEW_TO_EXTENSION) => + (message: MessageFromWebviewToExtension) => this.onReceivedWebviewMessage(message, panel), undefined, context.subscriptions