From 7b81767ded7031167d57f1ae7f2075d8bd9eaad0 Mon Sep 17 00:00:00 2001 From: jawelton74 <103591340+jawelton74@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:34:20 -0700 Subject: [PATCH 01/15] Enable new backend for Settings API in Prod. (#1791) --- src/Utils/EndpointUtils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Utils/EndpointUtils.ts b/src/Utils/EndpointUtils.ts index f8398568c..e4c036d75 100644 --- a/src/Utils/EndpointUtils.ts +++ b/src/Utils/EndpointUtils.ts @@ -155,7 +155,11 @@ export function useNewPortalBackendEndpoint(backendApi: string): boolean { // This maps backend APIs to the environments supported by the new backend. const newBackendApiEnvironmentMap: { [key: string]: string[] } = { [BackendApi.GenerateToken]: [PortalBackendEndpoints.Development], - [BackendApi.PortalSettings]: [PortalBackendEndpoints.Development, PortalBackendEndpoints.Mpac], + [BackendApi.PortalSettings]: [ + PortalBackendEndpoints.Development, + PortalBackendEndpoints.Mpac, + PortalBackendEndpoints.Prod, + ], }; if (!newBackendApiEnvironmentMap[backendApi] || !configContext.PORTAL_BACKEND_ENDPOINT) { From acf5acfdb45c0609b8dbc83cddfb730ec3a25b66 Mon Sep 17 00:00:00 2001 From: Asier Isayas Date: Tue, 23 Apr 2024 08:20:27 -0400 Subject: [PATCH 02/15] Remove Legacy Mongo Shell feature flag (#1810) * LMS Mongo Proxy support * change stirng to url for get mongo shell url * fix tests * enable feature flag * fixed unit test * add mongoshell to path * remove LMS feature flag --------- Co-authored-by: Asier Isayas --- src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx | 4 ++-- src/Platform/Hosted/extractFeatures.ts | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx b/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx index a89ca5fc7..d3720cf41 100644 --- a/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx +++ b/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx @@ -1,3 +1,4 @@ +import { useMongoProxyEndpoint } from "Common/MongoProxyClient"; import React, { Component } from "react"; import * as Constants from "../../../Common/Constants"; import { configContext } from "../../../ConfigContext"; @@ -54,8 +55,7 @@ export default class MongoShellTabComponent extends Component< constructor(props: IMongoShellTabComponentProps) { super(props); this._logTraces = new Map(); - this._useMongoProxyEndpoint = userContext.features.enableLegacyMongoShell; - // this._useMongoProxyEndpoint = useMongoProxyEndpoint("legacyMongoShell"); + this._useMongoProxyEndpoint = useMongoProxyEndpoint("legacyMongoShell"); this.state = { url: getMongoShellUrl(this._useMongoProxyEndpoint), diff --git a/src/Platform/Hosted/extractFeatures.ts b/src/Platform/Hosted/extractFeatures.ts index 626b855bb..5bd84516e 100644 --- a/src/Platform/Hosted/extractFeatures.ts +++ b/src/Platform/Hosted/extractFeatures.ts @@ -38,7 +38,6 @@ export type Features = { readonly copilotChatFixedMonacoEditorHeight: boolean; readonly enablePriorityBasedExecution: boolean; readonly disableConnectionStringLogin: boolean; - readonly enableLegacyMongoShell: boolean; // can be set via both flight and feature flag autoscaleDefault: boolean; @@ -109,7 +108,6 @@ export function extractFeatures(given = new URLSearchParams(window.location.sear copilotChatFixedMonacoEditorHeight: "true" === get("copilotchatfixedmonacoeditorheight"), enablePriorityBasedExecution: "true" === get("enableprioritybasedexecution"), disableConnectionStringLogin: "true" === get("disableconnectionstringlogin"), - enableLegacyMongoShell: "true" === get("enablelegacymongoshell"), }; } From c1a28793ba04c000b29af923051dff3435118bad Mon Sep 17 00:00:00 2001 From: Ashley Stanton-Nurse Date: Tue, 23 Apr 2024 09:08:29 -0700 Subject: [PATCH 03/15] bind F5 to execute query (#1813) --- src/KeyboardShortcuts.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/KeyboardShortcuts.tsx b/src/KeyboardShortcuts.tsx index 66efd68b4..023b7078e 100644 --- a/src/KeyboardShortcuts.tsx +++ b/src/KeyboardShortcuts.tsx @@ -54,7 +54,7 @@ const bindings: Record = { // See https://www.npmjs.com/package/tinykeys#commonly-used-keys-and-codes for more information on the expected values for keyboard shortcuts. [KeyboardAction.NEW_QUERY]: ["$mod+J", "Alt+N Q"], - [KeyboardAction.EXECUTE_ITEM]: ["Shift+Enter"], + [KeyboardAction.EXECUTE_ITEM]: ["Shift+Enter", "F5"], [KeyboardAction.CANCEL_OR_DISCARD]: ["Escape"], [KeyboardAction.SAVE_ITEM]: ["$mod+S"], [KeyboardAction.OPEN_QUERY]: ["$mod+O"], From d36e511b1873403a742bcdae1fd96f6963df9bdf Mon Sep 17 00:00:00 2001 From: jawelton74 <103591340+jawelton74@users.noreply.github.com> Date: Tue, 23 Apr 2024 10:15:48 -0700 Subject: [PATCH 04/15] Update d3, webpack-dev-server, typedoc dependencies. (#1812) * Update d3, webpack-dev-server, typedoc dependencies. * Fix unit test failures. * Revert change to snapshot as it doesn't seem required when running in github. --- jest.config.js | 5 +- package-lock.json | 582 +++++++++++++++++++++++++++++++--------------- package.json | 6 +- 3 files changed, 405 insertions(+), 188 deletions(-) diff --git a/jest.config.js b/jest.config.js index c00efdac6..b4f660063 100644 --- a/jest.config.js +++ b/jest.config.js @@ -76,6 +76,10 @@ module.exports = { "^dnd-core$": "dnd-core/dist/cjs", "^react-dnd$": "react-dnd/dist/cjs", "^react-dnd-html5-backend$": "react-dnd-html5-backend/dist/cjs", + "d3-force": "/node_modules/d3-force/dist/d3-force.min.js", + "d3-quadtree": "/node_modules/d3-quadtree/dist/d3-quadtree.min.js", + "d3-scale-chromatic": "/node_modules/d3-scale-chromatic/dist/d3-scale-chromatic.min.js", + "d3-zoom": "/node_modules/d3-zoom/dist/d3-zoom.min.js", }, // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader @@ -130,7 +134,6 @@ module.exports = { // The test environment that will be used for testing // testEnvironment: "jest-environment-jsdom", - modulePaths: ["node_modules", "/src"], // Options that will be passed to the testEnvironment diff --git a/package-lock.json b/package-lock.json index 590a1c049..8dcfe5d50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,7 +60,7 @@ "copy-webpack-plugin": "11.0.0", "crossroads": "0.12.2", "css-element-queries": "1.1.1", - "d3": "6.1.1", + "d3": "7.8.5", "datatables.net-colreorder-dt": "1.7.0", "datatables.net-dt": "1.13.8", "date-fns": "1.29.0", @@ -191,14 +191,14 @@ "sinon": "3.2.1", "style-loader": "0.23.0", "ts-loader": "9.2.4", - "typedoc": "0.21.5", + "typedoc": "0.22.15", "typescript": "4.3.5", "url-loader": "4.1.1", "wait-on": "4.0.2", "webpack": "5.88.2", "webpack-bundle-analyzer": "4.9.1", "webpack-cli": "5.1.4", - "webpack-dev-server": "4.15.1" + "webpack-dev-server": "4.15.2" } }, "canvas": { @@ -17259,40 +17259,43 @@ } }, "node_modules/d3": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/d3/-/d3-6.1.1.tgz", - "integrity": "sha512-bJYW9wlS2uvP2EoMkcPptrUzLMHQKCbiSW+/la8iGSLZgs4KbI/f3Fch4RtnUA9PA+/nPlwyFYzTwDjX80Of8w==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.5.tgz", + "integrity": "sha512-JgoahDG51ncUfJu6wX/1vWQEqOflgXyl4MaHqlcSruTez7yhaRKR9i8VjjcQGeS2en/jnFivXuaIMnseMMt0XA==", "dependencies": { - "d3-array": "2", - "d3-axis": "2", - "d3-brush": "2", - "d3-chord": "2", - "d3-color": "2", - "d3-contour": "2", - "d3-delaunay": "5", - "d3-dispatch": "2", - "d3-drag": "2", - "d3-dsv": "2", - "d3-ease": "2", - "d3-fetch": "2", - "d3-force": "2", - "d3-format": "2", - "d3-geo": "2", - "d3-hierarchy": "2", - "d3-interpolate": "2", - "d3-path": "2", - "d3-polygon": "2", - "d3-quadtree": "2", - "d3-random": "2", - "d3-scale": "3", - "d3-scale-chromatic": "2", - "d3-selection": "2", - "d3-shape": "2", - "d3-time": "2", - "d3-time-format": "3", - "d3-timer": "2", - "d3-transition": "2", - "d3-zoom": "2" + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-array": { @@ -17304,9 +17307,12 @@ } }, "node_modules/d3-axis": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-2.1.0.tgz", - "integrity": "sha512-z/G2TQMyuf0X3qP+Mh+2PimoJD41VOCjViJzT0BHeL/+JQAofkiWZbWxlwFGb1N8EN+Cl/CW+MUKbVzr1689Cw==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-brush": { "version": "2.1.0", @@ -17321,11 +17327,14 @@ } }, "node_modules/d3-chord": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-2.0.0.tgz", - "integrity": "sha512-D5PZb7EDsRNdGU4SsjQyKhja8Zgu+SHZfUSO5Ls8Wsn+jsAKUUGkcshLxMg9HDFxG3KqavGWaWkJ8EpU8ojuig==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", "dependencies": { - "d3-path": "1 - 2" + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-collection": { @@ -17339,19 +17348,36 @@ "integrity": "sha512-SPXi0TSKPD4g9tw0NMZFnR95XVgUZiBH+uUTqQuDu1OsE2zomHU7ho0FISciaPvosimixwHFl3WHLGabv6dDgQ==" }, "node_modules/d3-contour": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-2.0.0.tgz", - "integrity": "sha512-9unAtvIaNk06UwqBmvsdHX7CZ+NPDZnn8TtNH1myW93pWJkhsV25JcgnYAu0Ck5Veb1DHiCv++Ic5uvJ+h50JA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", "dependencies": { - "d3-array": "2" + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour/node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-delaunay": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-5.3.0.tgz", - "integrity": "sha512-amALSrOllWVLaHTnDLHwMIiz0d1bBu9gZXd1FiLfXf8sHcX9jrcj81TVZOqD4UX7MgBZZ07c8GxzEgBpJqc74w==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", "dependencies": { - "delaunator": "4" + "delaunator": "5" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-dispatch": { @@ -17369,24 +17395,46 @@ } }, "node_modules/d3-dsv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-2.0.0.tgz", - "integrity": "sha512-E+Pn8UJYx9mViuIUkoc93gJGGYut6mSDKy2+XaPwccwkRGlR+LO97L2VCCRjQivTwLHkSnAJG7yo00BWY6QM+w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", "dependencies": { - "commander": "2", - "iconv-lite": "0.4", + "commander": "7", + "iconv-lite": "0.6", "rw": "1" }, "bin": { - "csv2json": "bin/dsv2json", - "csv2tsv": "bin/dsv2dsv", - "dsv2dsv": "bin/dsv2dsv", - "dsv2json": "bin/dsv2json", - "json2csv": "bin/json2dsv", - "json2dsv": "bin/json2dsv", - "json2tsv": "bin/json2dsv", - "tsv2csv": "bin/dsv2dsv", - "tsv2json": "bin/dsv2json" + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-dsv/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, "node_modules/d3-ease": { @@ -17395,21 +17443,27 @@ "integrity": "sha512-68/n9JWarxXkOWMshcT5IcjbB+agblQUaIsbnXmrzejn2O82n3p2A9R2zEB9HIEFWKFwPAEDDN8gR0VdSAyyAQ==" }, "node_modules/d3-fetch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-2.0.0.tgz", - "integrity": "sha512-TkYv/hjXgCryBeNKiclrwqZH7Nb+GaOwo3Neg24ZVWA3MKB+Rd+BY84Nh6tmNEMcjUik1CSUWjXYndmeO6F7sw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", "dependencies": { - "d3-dsv": "1 - 2" + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-force": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-2.1.1.tgz", - "integrity": "sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", "dependencies": { - "d3-dispatch": "1 - 2", - "d3-quadtree": "1 - 2", - "d3-timer": "1 - 2" + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-format": { @@ -17418,11 +17472,14 @@ "integrity": "sha512-Ab3S6XuE/Q+flY96HXT0jOXcM4EAClYFnRGY5zsjRGNy6qCYrQsMffs7cV5Q9xejb35zxW5hf/guKw34kvIKsA==" }, "node_modules/d3-geo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-2.0.2.tgz", - "integrity": "sha512-8pM1WGMLGFuhq9S+FpPURxic+gKzjluCD/CHTuUF3mXMeiCo0i6R0tO1s4+GArRFde96SLcW/kOFRjoAosPsFA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", "dependencies": { - "d3-array": "^2.5.0" + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-glyphedge": { @@ -17436,9 +17493,12 @@ "integrity": "sha512-KS3fUT2ReD4RlGCjvCEm1RgMtp2NFZumdMu4DBzQK8AZv3fXRM6Xm8I4fSU07UXvH4xxg03NwWKWdvxfS/yc4w==" }, "node_modules/d3-hierarchy": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-2.0.0.tgz", - "integrity": "sha512-SwIdqM3HxQX2214EG9GTjgmCc/mbSx4mQBn+DuEETubhOw6/U3fmnji4uCVrmzOydMHSO1nZle5gh6HB/wdOzw==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-interpolate": { "version": "2.0.1", @@ -17449,9 +17509,12 @@ } }, "node_modules/d3-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-2.0.0.tgz", - "integrity": "sha512-ZwZQxKhBnv9yHaiWd6ZU4x5BtCQ7pXszEV9CU6kRgwIQVQGLMv1oiL4M+MK/n79sYzsj+gcgpPQSctJUsLN7fA==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-path-arrows": { "version": "0.4.0", @@ -17473,19 +17536,28 @@ "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" }, "node_modules/d3-polygon": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-2.0.0.tgz", - "integrity": "sha512-MsexrCK38cTGermELs0cO1d79DcTsQRN7IWMJKczD/2kBjzNXxLUWP33qRF6VDpiLV/4EI4r6Gs0DAWQkE8pSQ==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-quadtree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-2.0.0.tgz", - "integrity": "sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-random": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-2.2.2.tgz", - "integrity": "sha512-0D9P8TRj6qDAtHhRQn6EfdOtHMfsUWanl3yb/84C4DqpZ+VsgfI5iTVRNRbELCfNvRfpMr8OrqqUTQ6ANGCijw==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } }, "node_modules/d3-sankey-circular": { "version": "0.25.0", @@ -17515,12 +17587,15 @@ } }, "node_modules/d3-scale-chromatic": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-2.0.0.tgz", - "integrity": "sha512-LLqy7dJSL8yDy7NRmf6xSlsFZ6zYvJ4BcWFE4zBrOPnQERv9zj24ohnXKRbyi9YHnYV+HN1oEO3iFK971/gkzA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", "dependencies": { - "d3-color": "1 - 2", - "d3-interpolate": "1 - 2" + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" } }, "node_modules/d3-selection": { @@ -17583,23 +17658,181 @@ "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" }, "node_modules/d3-zoom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-2.0.0.tgz", - "integrity": "sha512-fFg7aoaEm9/jf+qfstak0IYpnesZLiMX6GZvXtUSdv8RH2o4E2qeelgdU09eKS6wGuiGMfcnMI0nTIqWzRHGpw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", "dependencies": { - "d3-dispatch": "1 - 2", - "d3-drag": "2", - "d3-interpolate": "1 - 2", - "d3-selection": "2", - "d3-transition": "2" + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" } }, "node_modules/d3/node_modules/d3-shape": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-2.1.0.tgz", - "integrity": "sha512-PnjUqfM2PpskbSLTJvAzp2Wv4CZsnAgTfcVRTwW03QR3MkXF8Uo7B1y/lWkAsmbKwuecto++4NlsYcvYpXpTHA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", "dependencies": { - "d3-path": "1 - 2" + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3/node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" } }, "node_modules/dashdash": { @@ -18021,9 +18254,12 @@ } }, "node_modules/delaunator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", - "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dependencies": { + "robust-predicates": "^3.0.2" + } }, "node_modules/delayed-stream": { "version": "1.0.0", @@ -21452,36 +21688,6 @@ "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -28937,9 +29143,9 @@ } }, "node_modules/jsonc-parser": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, "node_modules/jsonfile": { @@ -29740,15 +29946,15 @@ } }, "node_modules/marked": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz", - "integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "dev": true, "bin": { - "marked": "bin/marked" + "marked": "bin/marked.js" }, "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/martinez-polygon-clipping": { @@ -35900,6 +36106,11 @@ "rimraf": "bin.js" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, "node_modules/rst-selector-parser": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz", @@ -36645,9 +36856,9 @@ "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" }, "node_modules/shiki": { - "version": "0.9.15", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.9.15.tgz", - "integrity": "sha512-/Y0z9IzhJ8nD9nbceORCqu6NgT9X6I8Fk8c3SICHI5NbZRLdZYFaB233gwct9sU0vvSypyaL/qaKvzyQGJBZSw==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.10.1.tgz", + "integrity": "sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==", "dev": true, "dependencies": { "jsonc-parser": "^3.0.0", @@ -38572,19 +38783,16 @@ } }, "node_modules/typedoc": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.21.5.tgz", - "integrity": "sha512-uRDRmYheE5Iju9Zz0X50pTASTpBorIHFt02F5Y8Dt4eBt55h3mwk1CBSY2+EfwBxY16N4Xm7f8KXhnfFZ0AmBw==", + "version": "0.22.15", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.15.tgz", + "integrity": "sha512-CMd1lrqQbFvbx6S9G6fL4HKp3GoIuhujJReWqlIvSb2T26vGai+8Os3Mde7Pn832pXYemd9BMuuYWhFpL5st0Q==", "dev": true, "dependencies": { - "glob": "^7.1.7", - "handlebars": "^4.7.7", + "glob": "^7.2.0", "lunr": "^2.3.9", - "marked": "^2.1.1", - "minimatch": "^3.0.0", - "progress": "^2.0.3", - "shiki": "^0.9.3", - "typedoc-default-themes": "^0.12.10" + "marked": "^4.0.12", + "minimatch": "^5.0.1", + "shiki": "^0.10.1" }, "bin": { "typedoc": "bin/typedoc" @@ -38593,16 +38801,28 @@ "node": ">= 12.10.0" }, "peerDependencies": { - "typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x" + "typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x" } }, - "node_modules/typedoc-default-themes": { - "version": "0.12.10", - "resolved": "https://registry.npmjs.org/typedoc-default-themes/-/typedoc-default-themes-0.12.10.tgz", - "integrity": "sha512-fIS001cAYHkyQPidWXmHuhs8usjP5XVJjWB8oZGqkTowZaz3v7g3KDZeeqE82FBrmkAnIBOY3jgy7lnPnqATbA==", + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">= 8" + "node": ">=10" } }, "node_modules/typescript": { @@ -39615,9 +39835,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dev": true, "dependencies": { "colorette": "^2.0.10", @@ -39691,9 +39911,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", - "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", + "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", "dev": true, "dependencies": { "@types/bonjour": "^3.5.9", @@ -39724,7 +39944,7 @@ "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", + "webpack-dev-middleware": "^5.3.4", "ws": "^8.13.0" }, "bin": { @@ -40201,12 +40421,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index 9ac61d243..3a2f8b742 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "copy-webpack-plugin": "11.0.0", "crossroads": "0.12.2", "css-element-queries": "1.1.1", - "d3": "6.1.1", + "d3": "7.8.5", "datatables.net-colreorder-dt": "1.7.0", "datatables.net-dt": "1.13.8", "date-fns": "1.29.0", @@ -186,14 +186,14 @@ "sinon": "3.2.1", "style-loader": "0.23.0", "ts-loader": "9.2.4", - "typedoc": "0.21.5", + "typedoc": "0.22.15", "typescript": "4.3.5", "url-loader": "4.1.1", "wait-on": "4.0.2", "webpack": "5.88.2", "webpack-bundle-analyzer": "4.9.1", "webpack-cli": "5.1.4", - "webpack-dev-server": "4.15.1" + "webpack-dev-server": "4.15.2" }, "scripts": { "postinstall": "patch-package", From 17207624a9e20d1d5d77ff0916f8176d9eca65f5 Mon Sep 17 00:00:00 2001 From: Ashley Stanton-Nurse Date: Tue, 23 Apr 2024 15:46:41 -0700 Subject: [PATCH 05/15] add more intl-friendly tab nav shortcuts (#1814) --- src/KeyboardShortcuts.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/KeyboardShortcuts.tsx b/src/KeyboardShortcuts.tsx index 023b7078e..5ce91b8f2 100644 --- a/src/KeyboardShortcuts.tsx +++ b/src/KeyboardShortcuts.tsx @@ -67,8 +67,8 @@ const bindings: Record = { [KeyboardAction.NEW_ITEM]: ["Alt+N I"], [KeyboardAction.DELETE_ITEM]: ["Alt+D"], [KeyboardAction.TOGGLE_COPILOT]: ["$mod+P"], - [KeyboardAction.SELECT_LEFT_TAB]: ["$mod+Alt+["], - [KeyboardAction.SELECT_RIGHT_TAB]: ["$mod+Alt+]"], + [KeyboardAction.SELECT_LEFT_TAB]: ["$mod+Alt+[", "$mod+Shift+F6"], + [KeyboardAction.SELECT_RIGHT_TAB]: ["$mod+Alt+]", "$mod+F6"], [KeyboardAction.CLOSE_TAB]: ["$mod+Alt+W"], }; From f4bcee54612b4821176609fec170e135d03e4ec7 Mon Sep 17 00:00:00 2001 From: Ashley Stanton-Nurse Date: Tue, 23 Apr 2024 15:47:04 -0700 Subject: [PATCH 06/15] initialize new documents with their partition key (#1815) * initialize new documents with their partition key * refmt --- src/Explorer/Tabs/DocumentsTab.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Explorer/Tabs/DocumentsTab.ts b/src/Explorer/Tabs/DocumentsTab.ts index 1d13237d0..c79f7eb4f 100644 --- a/src/Explorer/Tabs/DocumentsTab.ts +++ b/src/Explorer/Tabs/DocumentsTab.ts @@ -463,7 +463,22 @@ export default class DocumentsTab extends TabsBase { private initializeNewDocument = (): void => { this.selectedDocumentId(null); - const defaultDocument: string = this.renderObjectForEditor({ id: "replace_with_new_document_id" }, null, 4); + const newDocument: any = { + id: "replace_with_new_document_id", + }; + this.partitionKeyProperties.forEach((partitionKeyProperty) => { + let target = newDocument; + const keySegments = partitionKeyProperty.split("."); + const finalSegment = keySegments.pop(); + + // Initialize nested objects as needed + keySegments.forEach((segment) => { + target = target[segment] = target[segment] || {}; + }); + + target[finalSegment] = "replace_with_new_partition_key_value"; + }); + const defaultDocument: string = this.renderObjectForEditor(newDocument, null, 4); this.initialDocumentContent(defaultDocument); this.selectedDocumentContent.setBaseline(defaultDocument); this.editorState(ViewModels.DocumentExplorerState.newDocumentValid); From afc82845b5a5eb5fe4367400f6423f1a5a3c60d2 Mon Sep 17 00:00:00 2001 From: Asier Isayas Date: Wed, 24 Apr 2024 15:04:01 -0400 Subject: [PATCH 07/15] activate Token Controller (#1820) Co-authored-by: Asier Isayas --- src/Utils/EndpointUtils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Utils/EndpointUtils.ts b/src/Utils/EndpointUtils.ts index e4c036d75..97e733b98 100644 --- a/src/Utils/EndpointUtils.ts +++ b/src/Utils/EndpointUtils.ts @@ -154,7 +154,11 @@ export const allowedNotebookServerUrls: ReadonlyArray = []; export function useNewPortalBackendEndpoint(backendApi: string): boolean { // This maps backend APIs to the environments supported by the new backend. const newBackendApiEnvironmentMap: { [key: string]: string[] } = { - [BackendApi.GenerateToken]: [PortalBackendEndpoints.Development], + [BackendApi.GenerateToken]: [ + PortalBackendEndpoints.Development, + PortalBackendEndpoints.Mpac, + PortalBackendEndpoints.Prod, + ], [BackendApi.PortalSettings]: [ PortalBackendEndpoints.Development, PortalBackendEndpoints.Mpac, From 618c5ec0fee67545eb53bc471cb45449972b4ff2 Mon Sep 17 00:00:00 2001 From: Ashley Stanton-Nurse Date: Wed, 24 Apr 2024 15:11:51 -0700 Subject: [PATCH 08/15] Add button (and keyboard shortcut) to download query (#1817) --- .../Tabs/QueryTab/QueryTabComponent.tsx | 46 +++++++++++++++---- src/KeyboardShortcuts.tsx | 2 + 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx index 07f0a9eba..d5f4bf697 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx @@ -22,6 +22,7 @@ import "react-splitter-layout/lib/index.css"; import { format } from "react-string-format"; import QueryCommandIcon from "../../../../images/CopilotCommand.svg"; import LaunchCopilot from "../../../../images/CopilotTabIcon.svg"; +import DownloadQueryIcon from "../../../../images/DownloadQuery.svg"; import CancelQueryIcon from "../../../../images/Entity_cancel.svg"; import ExecuteQueryIcon from "../../../../images/ExecuteQuery.svg"; import SaveQueryIcon from "../../../../images/save-cosmos.svg"; @@ -225,6 +226,20 @@ export default class QueryTabComponent extends React.Component { + const text = this.getCurrentEditorQuery(); + const queryFile = new File([text], `SavedQuery.txt`, { type: "text/plain" }); + + // It appears the most consistent to download a file from a blob is to create an anchor element and simulate clicking it + const blobUrl = URL.createObjectURL(queryFile); + const anchor = document.createElement("a"); + anchor.href = blobUrl; + anchor.download = queryFile.name; + document.body.appendChild(anchor); // Must put the anchor in the document. + anchor.click(); + document.body.removeChild(anchor); // Clean up the anchor. + }; + public onSaveQueryClick = (): void => { useSidePanel.getState().openSidePanel("Save Query", ); }; @@ -405,15 +420,28 @@ export default class QueryTabComponent extends React.Component 0; + useCommandBar.getState().setContextButtons(this.getTabsButtons()); } diff --git a/src/KeyboardShortcuts.tsx b/src/KeyboardShortcuts.tsx index 5ce91b8f2..98f988038 100644 --- a/src/KeyboardShortcuts.tsx +++ b/src/KeyboardShortcuts.tsx @@ -29,6 +29,7 @@ export enum KeyboardAction { EXECUTE_ITEM = "EXECUTE_ITEM", CANCEL_OR_DISCARD = "CANCEL_OR_DISCARD", SAVE_ITEM = "SAVE_ITEM", + DOWNLOAD_ITEM = "DOWNLOAD_ITEM", OPEN_QUERY = "OPEN_QUERY", OPEN_QUERY_FROM_DISK = "OPEN_QUERY_FROM_DISK", NEW_SPROC = "NEW_SPROC", @@ -57,6 +58,7 @@ const bindings: Record = { [KeyboardAction.EXECUTE_ITEM]: ["Shift+Enter", "F5"], [KeyboardAction.CANCEL_OR_DISCARD]: ["Escape"], [KeyboardAction.SAVE_ITEM]: ["$mod+S"], + [KeyboardAction.DOWNLOAD_ITEM]: ["$mod+Shift+S"], [KeyboardAction.OPEN_QUERY]: ["$mod+O"], [KeyboardAction.OPEN_QUERY_FROM_DISK]: ["$mod+Shift+O"], [KeyboardAction.NEW_SPROC]: ["Alt+N P"], From cbd5e6bf761b5469c63712ec6a451122e3dddcc1 Mon Sep 17 00:00:00 2001 From: Asier Isayas Date: Fri, 26 Apr 2024 14:55:47 -0400 Subject: [PATCH 09/15] open Legacy Mongo SHell with correct base URL in sovereign clouds (#1823) Co-authored-by: Asier Isayas --- .../Tabs/MongoShellTab/MongoShellTabComponent.tsx | 4 ++-- src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.ts | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx b/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx index d3720cf41..052c81ce6 100644 --- a/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx +++ b/src/Explorer/Tabs/MongoShellTab/MongoShellTabComponent.tsx @@ -35,7 +35,7 @@ export interface IMongoShellTabAccessor { } export interface IMongoShellTabComponentStates { - url: URL; + url: string; } export interface IMongoShellTabComponentProps { @@ -221,7 +221,7 @@ export default class MongoShellTabComponent extends Component< name="explorer" className="iframe" style={{ width: "100%", height: "100%", border: 0, padding: 0, margin: 0, overflow: "hidden" }} - src={this.state.url.toString()} + src={this.state.url} id={this.props.tabsBaseInstance.tabId} onLoad={(event) => this.setContentFocus(event)} title="Mongo Shell" diff --git a/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.ts b/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.ts index 0ecbcb83e..a3b49b373 100644 --- a/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.ts +++ b/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.ts @@ -1,15 +1,11 @@ -import { configContext } from "ConfigContext"; import { userContext } from "../../../UserContext"; -export function getMongoShellUrl(useMongoProxyEndpoint?: boolean): URL { +export function getMongoShellUrl(useMongoProxyEndpoint?: boolean): string { const { databaseAccount: account } = userContext; const resourceId = account?.id; const accountName = account?.name; const mongoEndpoint = account?.properties?.mongoEndpoint || account?.properties?.documentEndpoint; const queryString = `resourceId=${resourceId}&accountName=${accountName}&mongoEndpoint=${mongoEndpoint}`; - const path: string = useMongoProxyEndpoint - ? `/mongoshell/index.html?${queryString}` - : `/mongoshell/indexv2.html?${queryString}`; - return new URL(path, configContext.hostedExplorerURL); + return useMongoProxyEndpoint ? `/mongoshell/index.html?${queryString}` : `/mongoshell/indexv2.html?${queryString}`; } From f8f7ea34bdd156c8e7fd04c9945ae93652942d74 Mon Sep 17 00:00:00 2001 From: sunghyunkang1111 <114709653+sunghyunkang1111@users.noreply.github.com> Date: Fri, 26 Apr 2024 14:09:55 -0500 Subject: [PATCH 10/15] Copilot rewording (#1824) * Copilot rebranding to query advisor * fix the subquery link --- src/Explorer/Panes/SettingsPane/SettingsPane.tsx | 4 ++-- src/Explorer/QueryCopilot/QueryCopilotPromptbar.tsx | 2 +- src/Explorer/QueryCopilot/QueryCopilotTab.tsx | 6 +++--- src/Explorer/SplashScreen/SplashScreen.tsx | 4 ++-- src/Explorer/Tabs/QueryTab/QueryResultSection.tsx | 10 +++++++--- src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx | 6 +++--- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx index 1f0eda4f7..9b2412c4b 100644 --- a/src/Explorer/Panes/SettingsPane/SettingsPane.tsx +++ b/src/Explorer/Panes/SettingsPane/SettingsPane.tsx @@ -630,7 +630,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ Enable sample database This is a sample database and collection with synthetic product data you can use to explore using - NoSQL queries and Copilot. This will appear as another database in the Data Explorer UI, and is + NoSQL queries and Query Advisor. This will appear as another database in the Data Explorer UI, and is created by, and maintained by Microsoft at no cost to you. @@ -640,7 +640,7 @@ export const SettingsPane: FunctionComponent<{ explorer: Explorer }> = ({ label: { padding: 0 }, }} className="padding" - ariaLabel="Enable sample db for Copilot" + ariaLabel="Enable sample db for Query Advisor" checked={copilotSampleDBEnabled} onChange={handleSampleDatabaseChange} /> diff --git a/src/Explorer/QueryCopilot/QueryCopilotPromptbar.tsx b/src/Explorer/QueryCopilot/QueryCopilotPromptbar.tsx index 6aaebc926..6c54cd506 100644 --- a/src/Explorer/QueryCopilot/QueryCopilotPromptbar.tsx +++ b/src/Explorer/QueryCopilot/QueryCopilotPromptbar.tsx @@ -385,7 +385,7 @@ export const QueryCopilotPromptbar: React.FC = ({ hasSmallHeadline={true} headline="Write a prompt" > - Write a prompt here and Copilot will generate the query for you. You can also choose from our{" "} + Write a prompt here and Query Advisor will generate the query for you. You can also choose from our{" "} { setShowSamplePrompts(true); diff --git a/src/Explorer/QueryCopilot/QueryCopilotTab.tsx b/src/Explorer/QueryCopilot/QueryCopilotTab.tsx index 240881310..150f7ec5b 100644 --- a/src/Explorer/QueryCopilot/QueryCopilotTab.tsx +++ b/src/Explorer/QueryCopilot/QueryCopilotTab.tsx @@ -57,12 +57,12 @@ export const QueryCopilotTab: React.FC = ({ explorer }: Query const toggleCopilotButton = { iconSrc: QueryCommandIcon, - iconAlt: "Copilot", + iconAlt: "Query Advisor", onCommandClick: () => { toggleCopilot(true); }, - commandButtonLabel: "Copilot", - ariaLabel: "Copilot", + commandButtonLabel: "Query Advisor", + ariaLabel: "Query Advisor", hasPopup: false, disabled: copilotActive, }; diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx index aa7e7dcd9..f4ebb9cd0 100644 --- a/src/Explorer/SplashScreen/SplashScreen.tsx +++ b/src/Explorer/SplashScreen/SplashScreen.tsx @@ -151,9 +151,9 @@ export class SplashScreen extends React.Component { {useQueryCopilot.getState().copilotEnabled && ( { const copilotVersion = userContext.features.copilotVersion; diff --git a/src/Explorer/Tabs/QueryTab/QueryResultSection.tsx b/src/Explorer/Tabs/QueryTab/QueryResultSection.tsx index fc93d98a1..30d2ed9c1 100644 --- a/src/Explorer/Tabs/QueryTab/QueryResultSection.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryResultSection.tsx @@ -381,9 +381,13 @@ export const QueryResultSection: React.FC = ({ Error - We have detected you may be using a subquery. Non-correlated subqueries are not currently supported. - - Please see Cosmos sub query documentation for further information + We detected you may be using a subquery. To learn more about subqueries effectively,{" "} + + visit the documentation diff --git a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx index d5f4bf697..532ce4662 100644 --- a/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx +++ b/src/Explorer/Tabs/QueryTab/QueryTabComponent.tsx @@ -483,13 +483,13 @@ export default class QueryTabComponent extends React.Component { this._toggleCopilot(!this.state.copilotActive); }, - commandButtonLabel: this.state.copilotActive ? "Disable Copilot" : "Enable Copilot", - ariaLabel: this.state.copilotActive ? "Disable Copilot" : "Enable Copilot", + commandButtonLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor", + ariaLabel: this.state.copilotActive ? "Disable Query Advisor" : "Enable Query Advisor", hasPopup: false, }; buttons.push(toggleCopilotButton); From b94ce28e96edd5b65896dec56a6baf833fba63cd Mon Sep 17 00:00:00 2001 From: SATYA SB <107645008+satya07sb@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:23:49 +0530 Subject: [PATCH 11/15] [accessibility-2724013]:[Screen reader - Cosmos DB - Data Explorer -> Entities -> Add entity]: Screen reader announces incorrect role when focus lands on the "Edit" and "Delete" buttons. (#1822) Co-authored-by: Satyapriya Bai --- src/Common/TableEntity.tsx | 4 ++-- .../GraphExplorerComponent/NodePropertiesComponent.tsx | 4 ++-- .../Graph/NewVertexComponent/NewVertexComponent.tsx | 8 +++++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Common/TableEntity.tsx b/src/Common/TableEntity.tsx index f3d0b5244..eece32ecb 100644 --- a/src/Common/TableEntity.tsx +++ b/src/Common/TableEntity.tsx @@ -142,7 +142,7 @@ export const TableEntity: FunctionComponent = ({ editEntity = ({ delete entity - Delete + Delete ); } else { @@ -406,7 +406,7 @@ export class NodePropertiesComponent extends React.Component< aria-label="Edit properties" onActivated={expandClickHandler} > - Edit + Edit )} diff --git a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx index de357989a..6b20cfcb0 100644 --- a/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx +++ b/src/Explorer/Graph/NewVertexComponent/NewVertexComponent.tsx @@ -184,12 +184,18 @@ export const NewVertexComponent: FunctionComponent = ( className="rightPaneTrashIcon rightPaneBtns" tabIndex={0} role="button" + aria-label={`Delete ${data.key}`} onClick={(event: React.MouseEvent) => removeNewVertexProperty(event, index)} onKeyPress={(event: React.KeyboardEvent) => removeNewVertexPropertyKeyPress(event, index) } > - Remove property + Remove property From a08415e7bcf9683cee3e37459005972a5693b49a Mon Sep 17 00:00:00 2001 From: SATYA SB <107645008+satya07sb@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:26:27 +0530 Subject: [PATCH 12/15] [3100018:[Programmatic Access - Azure Cosmos DB - Edit Property]: Text Area edit field does not have a label under 'Edit Property' pane. (#1819) Co-authored-by: Satyapriya Bai --- src/Explorer/Panes/Tables/AddTableEntityPanel.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx b/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx index 9ad999aa0..7d73ccc1f 100644 --- a/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx +++ b/src/Explorer/Panes/Tables/AddTableEntityPanel.tsx @@ -261,6 +261,7 @@ export const AddTableEntityPanel: FunctionComponent = { entityChange(newInput, selectedRow, "value"); From 92246144f710b2dcdc4e9b8613c54344aca7f753 Mon Sep 17 00:00:00 2001 From: Asier Isayas Date: Mon, 29 Apr 2024 16:25:58 -0400 Subject: [PATCH 13/15] Enable Legacy Mongo Shell in Fairfax (#1829) * enable Mongo Proxy and LMS in sovereign clouds * remove mooncake --------- Co-authored-by: Asier Isayas --- src/Common/MongoProxyClient.ts | 1 + src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Common/MongoProxyClient.ts b/src/Common/MongoProxyClient.ts index 907b0305e..d9aa0fb4c 100644 --- a/src/Common/MongoProxyClient.ts +++ b/src/Common/MongoProxyClient.ts @@ -677,6 +677,7 @@ export function useMongoProxyEndpoint(api: string): boolean { MongoProxyEndpoints.Local, MongoProxyEndpoints.Mpac, MongoProxyEndpoints.Prod, + MongoProxyEndpoints.Fairfax, ]; let canAccessMongoProxy: boolean = userContext.databaseAccount.properties.publicNetworkAccess === "Enabled"; if ( diff --git a/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts b/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts index 0c138ff61..8b16816ab 100644 --- a/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts +++ b/src/Explorer/Tabs/MongoShellTab/getMongoShellUrl.test.ts @@ -3,7 +3,6 @@ import { updateUserContext, userContext } from "../../../UserContext"; import { getMongoShellUrl } from "./getMongoShellUrl"; const mongoBackendEndpoint = "https://localhost:1234"; -const hostedExplorerURL = "https://cosmos.azure.com/"; describe("getMongoShellUrl", () => { let queryString = ""; @@ -13,7 +12,6 @@ describe("getMongoShellUrl", () => { updateConfigContext({ BACKEND_ENDPOINT: mongoBackendEndpoint, - hostedExplorerURL: hostedExplorerURL, platform: Platform.Hosted, }); From b023250e67e834ee8485670a1605973d0db88936 Mon Sep 17 00:00:00 2001 From: jawelton74 <103591340+jawelton74@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:46:24 -0700 Subject: [PATCH 14/15] First set of changes for Notebooks removal. (#1816) * First set of changes for Notebooks removal. * Fix unit test snapshots. --- src/Contracts/ActionContracts.ts | 5 - .../SettingsComponent.test.tsx.snap | 16 - src/Explorer/Explorer.tsx | 612 +----------------- src/Explorer/Notebook/NotebookManager.tsx | 27 +- src/Explorer/OpenActions/OpenActions.tsx | 12 - .../CopyNotebookPane/CopyNotebookPane.tsx | 154 ----- .../CopyNotebookPaneComponent.tsx | 120 ---- .../GitHubReposPanel.test.tsx.snap | 4 - .../StringInputPane.test.tsx.snap | 4 - .../QueryCopilotTab.test.tsx.snap | 4 - src/Explorer/SplashScreen/SplashScreen.tsx | 28 +- src/Explorer/Tabs/NotebookV2Tab.ts | 336 +--------- src/Explorer/Tree/ResourceTree.tsx | 432 +------------ src/Explorer/Tree/ResourceTreeAdapter.tsx | 497 +------------- src/Utils/GalleryUtils.ts | 2 - 15 files changed, 11 insertions(+), 2242 deletions(-) delete mode 100644 src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx delete mode 100644 src/Explorer/Panes/CopyNotebookPane/CopyNotebookPaneComponent.tsx diff --git a/src/Contracts/ActionContracts.ts b/src/Contracts/ActionContracts.ts index f8fc956e6..cf4b66ed6 100644 --- a/src/Contracts/ActionContracts.ts +++ b/src/Contracts/ActionContracts.ts @@ -68,10 +68,6 @@ export interface OpenPane extends DataExplorerAction { paneKind: PaneKind | string; } -export interface OpenSampleNotebook extends DataExplorerAction { - path: string; -} - /** * The types of actions that the DataExplorer supports performing upon opening. */ @@ -80,5 +76,4 @@ export enum ActionType { OpenCollectionTab, OpenPane, TransmitCachedData, - OpenSampleNotebook, } diff --git a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap index ab7abac11..b08834dd6 100644 --- a/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap +++ b/src/Explorer/Controls/Settings/__snapshots__/SettingsComponent.test.tsx.snap @@ -29,8 +29,6 @@ exports[`SettingsComponent renders 1`] = ` "computedProperties": [Function], "conflictResolutionPolicy": [Function], "container": Explorer { - "_isInitializingNotebooks": false, - "_resetNotebookWorkspace": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], @@ -47,10 +45,8 @@ exports[`SettingsComponent renders 1`] = ` "queriesClient": QueriesClient { "container": [Circular], }, - "refreshNotebookList": [Function], "resourceTree": ResourceTreeAdapter { "container": [Circular], - "copyNotebook": [Function], "parameters": [Function], }, }, @@ -107,8 +103,6 @@ exports[`SettingsComponent renders 1`] = ` "computedProperties": [Function], "conflictResolutionPolicy": [Function], "container": Explorer { - "_isInitializingNotebooks": false, - "_resetNotebookWorkspace": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], @@ -125,10 +119,8 @@ exports[`SettingsComponent renders 1`] = ` "queriesClient": QueriesClient { "container": [Circular], }, - "refreshNotebookList": [Function], "resourceTree": ResourceTreeAdapter { "container": [Circular], - "copyNotebook": [Function], "parameters": [Function], }, }, @@ -224,8 +216,6 @@ exports[`SettingsComponent renders 1`] = ` "computedProperties": [Function], "conflictResolutionPolicy": [Function], "container": Explorer { - "_isInitializingNotebooks": false, - "_resetNotebookWorkspace": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], @@ -242,10 +232,8 @@ exports[`SettingsComponent renders 1`] = ` "queriesClient": QueriesClient { "container": [Circular], }, - "refreshNotebookList": [Function], "resourceTree": ResourceTreeAdapter { "container": [Circular], - "copyNotebook": [Function], "parameters": [Function], }, }, @@ -271,8 +259,6 @@ exports[`SettingsComponent renders 1`] = ` } explorer={ Explorer { - "_isInitializingNotebooks": false, - "_resetNotebookWorkspace": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], @@ -289,10 +275,8 @@ exports[`SettingsComponent renders 1`] = ` "queriesClient": QueriesClient { "container": [Circular], }, - "refreshNotebookList": [Function], "resourceTree": ResourceTreeAdapter { "container": [Circular], - "copyNotebook": [Function], "parameters": [Function], }, } diff --git a/src/Explorer/Explorer.tsx b/src/Explorer/Explorer.tsx index 4af478475..368168a4a 100644 --- a/src/Explorer/Explorer.tsx +++ b/src/Explorer/Explorer.tsx @@ -1,4 +1,3 @@ -import { Link } from "@fluentui/react/lib/Link"; import { isPublicInternetAccessAllowed } from "Common/DatabaseAccountUtility"; import { sendMessage } from "Common/MessageHandler"; import { Platform, configContext } from "ConfigContext"; @@ -16,7 +15,7 @@ import shallow from "zustand/shallow"; import { AuthType } from "../AuthType"; import { BindingHandlersRegisterer } from "../Bindings/BindingHandlersRegisterer"; import * as Constants from "../Common/Constants"; -import { Areas, ConnectionStatusType, HttpStatusCodes, Notebook, PoolIdType } from "../Common/Constants"; +import { Areas, ConnectionStatusType, HttpStatusCodes, PoolIdType } from "../Common/Constants"; import { getErrorMessage, getErrorStack, handleError } from "../Common/ErrorHandlingUtils"; import * as Logger from "../Common/Logger"; import { QueriesClient } from "../Common/QueriesClient"; @@ -32,34 +31,23 @@ import { Action, ActionModifiers } from "../Shared/Telemetry/TelemetryConstants" import * as TelemetryProcessor from "../Shared/Telemetry/TelemetryProcessor"; import { isAccountNewerThanThresholdInMs, userContext } from "../UserContext"; import { getCollectionName, getUploadName } from "../Utils/APITypeUtils"; -import { stringToBlob } from "../Utils/BlobUtils"; import { isCapabilityEnabled } from "../Utils/CapabilityUtils"; -import { fromContentUri, toRawContentUri } from "../Utils/GitHubUtils"; -import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils"; import { logConsoleError, logConsoleInfo, logConsoleProgress } from "../Utils/NotificationConsoleUtils"; import { update } from "../Utils/arm/generatedClients/cosmos/databaseAccounts"; -import { listByDatabaseAccount } from "../Utils/arm/generatedClients/cosmosNotebooks/notebookWorkspaces"; import { useSidePanel } from "../hooks/useSidePanel"; import { useTabs } from "../hooks/useTabs"; import "./ComponentRegisterer"; import { DialogProps, useDialog } from "./Controls/Dialog"; import { GalleryTab as GalleryTabKind } from "./Controls/NotebookGallery/GalleryViewerComponent"; import { useCommandBar } from "./Menus/CommandBar/CommandBarComponentAdapter"; -import * as FileSystemUtil from "./Notebook/FileSystemUtil"; -import { SnapshotRequest } from "./Notebook/NotebookComponent/types"; -import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem"; +import { NotebookContentItem } from "./Notebook/NotebookContentItem"; import type NotebookManager from "./Notebook/NotebookManager"; -import { NotebookPaneContent } from "./Notebook/NotebookManager"; -import { NotebookUtil } from "./Notebook/NotebookUtil"; import { useNotebook } from "./Notebook/useNotebook"; import { AddCollectionPanel } from "./Panes/AddCollectionPanel"; import { CassandraAddCollectionPane } from "./Panes/CassandraAddCollectionPane/CassandraAddCollectionPane"; import { ExecuteSprocParamsPane } from "./Panes/ExecuteSprocParamsPane/ExecuteSprocParamsPane"; -import { StringInputPane } from "./Panes/StringInputPane/StringInputPane"; -import { UploadFilePane } from "./Panes/UploadFilePane/UploadFilePane"; import { UploadItemsPane } from "./Panes/UploadItemsPane/UploadItemsPane"; import { CassandraAPIDataClient, TableDataClient, TablesAPIDataClient } from "./Tables/TableDataClient"; -import NotebookV2Tab, { NotebookTabOptions } from "./Tabs/NotebookV2Tab"; import TabsBase from "./Tabs/TabsBase"; import TerminalTab from "./Tabs/TerminalTab"; import Database from "./Tree/Database"; @@ -87,7 +75,6 @@ export default class Explorer { // Notebooks public notebookManager?: NotebookManager; - private _isInitializingNotebooks: boolean; private notebookToImport: { name: string; content: string; @@ -99,7 +86,6 @@ export default class Explorer { const startKey: number = TelemetryProcessor.traceStart(Action.InitializeDataExplorer, { dataExplorerArea: Constants.Areas.ResourceTree, }); - this._isInitializingNotebooks = false; this.phoenixClient = new PhoenixClient(userContext?.databaseAccount?.id); useNotebook.subscribe( @@ -205,12 +191,10 @@ export default class Explorer { container: this, resourceTree: this.resourceTree, refreshCommandBarButtons: () => this.refreshCommandBarButtons(), - refreshNotebookList: () => this.refreshNotebookList(), }); } this.refreshCommandBarButtons(); - this.refreshNotebookList(); } public openEnableSynapseLinkDialog(): void { @@ -373,7 +357,6 @@ export default class Explorer { userContext.authType === AuthType.ResourceToken ? this.refreshDatabaseForResourceToken() : this.refreshAllDatabases(); - this.refreshNotebookList(); }; // Facade @@ -381,19 +364,6 @@ export default class Explorer { window.open(Constants.Urls.feedbackEmail, "_blank"); }; - public async initNotebooks(databaseAccount: DataModels.DatabaseAccount): Promise { - if (!databaseAccount) { - throw new Error("No database account specified"); - } - - if (this._isInitializingNotebooks) { - return; - } - this._isInitializingNotebooks = true; - this.refreshNotebookList(); - this._isInitializingNotebooks = false; - } - public async allocateContainer(poolId: PoolIdType, mode?: string): Promise { const shouldUseNotebookStates = poolId === PoolIdType.DefaultPoolId ? true : false; const notebookServerInfo = shouldUseNotebookStates @@ -472,8 +442,6 @@ export default class Explorer { ? useNotebook.getState().setIsAllocating(false) : useQueryCopilot.getState().setIsAllocatingContainer(false); this.refreshCommandBarButtons(); - this.refreshNotebookList(); - this._isInitializingNotebooks = false; } } } @@ -510,104 +478,6 @@ export default class Explorer { .then((memoryUsageInfo) => useNotebook.getState().setMemoryUsageInfo(memoryUsageInfo)); } - public resetNotebookWorkspace(): void { - if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookClient) { - handleError( - "Attempt to reset notebook workspace, but notebook is not enabled", - "Explorer/resetNotebookWorkspace", - ); - return; - } - const dialogContent = useNotebook.getState().isPhoenixNotebooks - ? "Notebooks saved in the temporary workspace will be deleted. Do you want to proceed?" - : "This lets you keep your notebook files and the workspace will be restored to default. Proceed anyway?"; - - const resetConfirmationDialogProps: DialogProps = { - isModal: true, - title: "Reset Workspace", - subText: dialogContent, - primaryButtonText: "OK", - secondaryButtonText: "Cancel", - onPrimaryButtonClick: this._resetNotebookWorkspace, - onSecondaryButtonClick: () => useDialog.getState().closeDialog(), - }; - useDialog.getState().openDialog(resetConfirmationDialogProps); - } - - private async _containsDefaultNotebookWorkspace(databaseAccount: DataModels.DatabaseAccount): Promise { - if (!databaseAccount) { - return false; - } - try { - const { value: workspaces } = await listByDatabaseAccount( - userContext.subscriptionId, - userContext.resourceGroup, - userContext.databaseAccount.name, - ); - return workspaces && workspaces.length > 0 && workspaces.some((workspace) => workspace.name === "default"); - } catch (error) { - Logger.logError(getErrorMessage(error), "Explorer/_containsDefaultNotebookWorkspace"); - return false; - } - } - - private _resetNotebookWorkspace = async () => { - useDialog.getState().closeDialog(); - const clearInProgressMessage = logConsoleProgress("Resetting notebook workspace"); - let connectionStatus: ContainerConnectionInfo; - try { - const notebookServerInfo = useNotebook.getState().notebookServerInfo; - if (!notebookServerInfo || !notebookServerInfo.notebookServerEndpoint) { - const error = "No server endpoint detected"; - Logger.logError(error, "NotebookContainerClient/resetWorkspace"); - logConsoleError(error); - return; - } - TelemetryProcessor.traceStart(Action.PhoenixResetWorkspace, { - dataExplorerArea: Areas.Notebook, - }); - if (useNotebook.getState().isPhoenixNotebooks) { - useTabs.getState().closeAllNotebookTabs(true); - connectionStatus = { - status: ConnectionStatusType.Connecting, - }; - useNotebook.getState().setConnectionInfo(connectionStatus); - } - const connectionInfo = await this.notebookManager?.notebookClient.resetWorkspace(); - if (connectionInfo?.status !== HttpStatusCodes.OK) { - throw new Error(`Reset Workspace: Received status code- ${connectionInfo?.status}`); - } - if (!connectionInfo?.data?.phoenixServiceUrl) { - throw new Error(`Reset Workspace: PhoenixServiceUrl is invalid!`); - } - if (useNotebook.getState().isPhoenixNotebooks) { - await this.setNotebookInfo(true, connectionInfo, connectionStatus); - useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); - } - logConsoleInfo("Successfully reset notebook workspace"); - TelemetryProcessor.traceSuccess(Action.PhoenixResetWorkspace, { - dataExplorerArea: Areas.Notebook, - }); - } catch (error) { - logConsoleError(`Failed to reset notebook workspace: ${error}`); - TelemetryProcessor.traceFailure(Action.PhoenixResetWorkspace, { - dataExplorerArea: Areas.Notebook, - error: getErrorMessage(error), - errorStack: getErrorStack(error), - }); - if (useNotebook.getState().isPhoenixNotebooks) { - connectionStatus = { - status: ConnectionStatusType.Failed, - }; - useNotebook.getState().resetContainerConnection(connectionStatus); - useNotebook.getState().setIsRefreshed(!useNotebook.getState().isRefreshed); - } - throw error; - } finally { - clearInProgressMessage(); - } - }; - private getDeltaDatabases( updatedDatabaseList: DataModels.Database[], databases: ViewModels.Database[], @@ -696,406 +566,6 @@ export default class Explorer { } } - public uploadFile( - name: string, - content: string, - parent: NotebookContentItem, - isGithubTree?: boolean, - ): Promise { - if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { - const error = "Attempt to upload notebook, but notebook is not enabled"; - handleError(error, "Explorer/uploadFile"); - throw new Error(error); - } - - const promise = this.notebookManager?.notebookContentClient.uploadFileAsync(name, content, parent, isGithubTree); - promise - .then(() => this.resourceTree.triggerRender()) - .catch((reason) => useDialog.getState().showOkModalDialog("Unable to upload file", getErrorMessage(reason))); - return promise; - } - - public async importAndOpen(path: string): Promise { - const name = NotebookUtil.getName(path); - const item = NotebookUtil.createNotebookContentItem(name, path, "file"); - const parent = this.resourceTree.myNotebooksContentRoot; - - if (parent && parent.children && useNotebook.getState().isNotebookEnabled && this.notebookManager?.notebookClient) { - const existingItem = _.find(parent.children, (node) => node.name === name); - if (existingItem) { - return this.openNotebook(existingItem); - } - - const content = await this.readFile(item); - const uploadedItem = await this.uploadFile(name, content, parent); - return this.openNotebook(uploadedItem); - } - - return Promise.resolve(false); - } - - public async importAndOpenContent(name: string, content: string): Promise { - const parent = this.resourceTree.myNotebooksContentRoot; - - if (parent && parent.children && useNotebook.getState().isNotebookEnabled && this.notebookManager?.notebookClient) { - if (this.notebookToImport && this.notebookToImport.name === name && this.notebookToImport.content === content) { - this.notebookToImport = undefined; // we don't want to try opening this notebook again - } - - const existingItem = _.find(parent.children, (node) => node.name === name); - if (existingItem) { - return this.openNotebook(existingItem); - } - - const uploadedItem = await this.uploadFile(name, content, parent); - return this.openNotebook(uploadedItem); - } - - this.notebookToImport = { name, content }; // we'll try opening this notebook later on - return Promise.resolve(false); - } - - public async publishNotebook( - name: string, - content: NotebookPaneContent, - notebookContentRef?: string, - onTakeSnapshot?: (request: SnapshotRequest) => void, - onClosePanel?: () => void, - ): Promise { - if (this.notebookManager) { - await this.notebookManager.openPublishNotebookPane( - name, - content, - notebookContentRef, - onTakeSnapshot, - onClosePanel, - ); - } - } - - public copyNotebook(name: string, content: string): void { - this.notebookManager?.openCopyNotebookPane(name, content); - } - - /** - * Note: To keep it simple, this creates a disconnected NotebookContentItem that is not connected to the resource tree. - * Connecting it to a tree possibly requires the intermediate missing folders if the item is nested in a subfolder. - * Manually creating the missing folders between the root and its parent dir would break the UX: expanding a folder - * will not fetch its content if the children array exists (and has only one child which was manually created). - * Fetching the intermediate folders possibly involves a few chained async calls which isn't ideal. - * - * @param name - * @param path - */ - public createNotebookContentItemFile(name: string, path: string): NotebookContentItem { - return NotebookUtil.createNotebookContentItem(name, path, "file"); - } - - public async openNotebook(notebookContentItem: NotebookContentItem): Promise { - if (!notebookContentItem || !notebookContentItem.path) { - throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`); - } - if (notebookContentItem.type === NotebookContentItemType.Notebook && useNotebook.getState().isPhoenixNotebooks) { - await this.allocateContainer(PoolIdType.DefaultPoolId); - } - - const notebookTabs = useTabs - .getState() - .getTabs( - ViewModels.CollectionTabKind.NotebookV2, - (tab) => - (tab as NotebookV2Tab).notebookPath && - FileSystemUtil.isPathEqual((tab as NotebookV2Tab).notebookPath(), notebookContentItem.path), - ) as NotebookV2Tab[]; - let notebookTab = notebookTabs && notebookTabs[0]; - - if (notebookTab) { - useTabs.getState().activateTab(notebookTab); - } else { - const options: NotebookTabOptions = { - account: userContext.databaseAccount, - tabKind: ViewModels.CollectionTabKind.NotebookV2, - node: undefined, - title: notebookContentItem.name, - tabPath: notebookContentItem.path, - collection: undefined, - masterKey: userContext.masterKey || "", - isTabsContentExpanded: ko.observable(true), - onLoadStartKey: undefined, - container: this, - notebookContentItem, - }; - - try { - const NotebookTabV2 = await import(/* webpackChunkName: "NotebookV2Tab" */ "./Tabs/NotebookV2Tab"); - notebookTab = new NotebookTabV2.default(options); - useTabs.getState().activateNewTab(notebookTab); - } catch (reason) { - console.error("Import NotebookV2Tab failed!", reason); - return false; - } - } - - return true; - } - - public renameNotebook(notebookFile: NotebookContentItem, isGithubTree?: boolean): void { - if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { - const error = "Attempt to rename notebook, but notebook is not enabled"; - handleError(error, "Explorer/renameNotebook"); - throw new Error(error); - } - - // Don't delete if tab is open to avoid accidental deletion - const openedNotebookTabs = useTabs - .getState() - .getTabs(ViewModels.CollectionTabKind.NotebookV2, (tab: NotebookV2Tab) => { - return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), notebookFile.path); - }); - if (openedNotebookTabs.length > 0) { - useDialog - .getState() - .showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again."); - } else { - useSidePanel.getState().openSidePanel( - "Rename Notebook", - { - useSidePanel.getState().closeSidePanel(); - this.resourceTree.triggerRender(); - }} - inputLabel="Enter new notebook name" - submitButtonLabel="Rename" - errorMessage="Could not rename notebook" - inProgressMessage="Renaming notebook to" - successMessage="Renamed notebook to" - paneTitle="Rename Notebook" - defaultInput={FileSystemUtil.stripExtension(notebookFile.name, "ipynb")} - onSubmit={(notebookFile: NotebookContentItem, input: string): Promise => - this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input, isGithubTree) - } - notebookFile={notebookFile} - />, - ); - } - } - - public onCreateDirectory(parent: NotebookContentItem, isGithubTree?: boolean): void { - if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { - const error = "Attempt to create notebook directory, but notebook is not enabled"; - handleError(error, "Explorer/onCreateDirectory"); - throw new Error(error); - } - - useSidePanel.getState().openSidePanel( - "Create new directory", - { - useSidePanel.getState().closeSidePanel(); - this.resourceTree.triggerRender(); - }} - errorMessage="Could not create directory " - inProgressMessage="Creating directory " - successMessage="Created directory " - inputLabel="Enter new directory name" - paneTitle="Create new directory" - submitButtonLabel="Create" - defaultInput="" - onSubmit={(notebookFile: NotebookContentItem, input: string): Promise => - this.notebookManager?.notebookContentClient.createDirectory(notebookFile, input, isGithubTree) - } - notebookFile={parent} - />, - ); - } - - public readFile(notebookFile: NotebookContentItem): Promise { - if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { - const error = "Attempt to read file, but notebook is not enabled"; - handleError(error, "Explorer/downloadFile"); - throw new Error(error); - } - - return this.notebookManager?.notebookContentClient.readFileContent(notebookFile.path); - } - - public downloadFile(notebookFile: NotebookContentItem): Promise { - if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { - const error = "Attempt to download file, but notebook is not enabled"; - handleError(error, "Explorer/downloadFile"); - throw new Error(error); - } - - const clearMessage = NotificationConsoleUtils.logConsoleProgress(`Downloading ${notebookFile.path}`); - - return this.notebookManager?.notebookContentClient.readFileContent(notebookFile.path).then( - (content: string) => { - const blob = stringToBlob(content, "text/plain"); - if (navigator.msSaveBlob) { - // for IE and Edge - navigator.msSaveBlob(blob, notebookFile.name); - } else { - const downloadLink: HTMLAnchorElement = document.createElement("a"); - const url = URL.createObjectURL(blob); - downloadLink.href = url; - downloadLink.target = "_self"; - downloadLink.download = notebookFile.name; - - // for some reason, FF displays the download prompt only when - // the link is added to the dom so we add and remove it - document.body.appendChild(downloadLink); - downloadLink.click(); - downloadLink.remove(); - } - - clearMessage(); - }, - (error) => { - logConsoleError(`Could not download notebook ${getErrorMessage(error)}`); - clearMessage(); - }, - ); - } - - private refreshNotebookList = async (): Promise => { - if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { - return; - } - - await this.resourceTree.initialize(); - await useNotebook.getState().initializeNotebooksTree(this.notebookManager); - - this.notebookManager?.refreshPinnedRepos(); - if (this.notebookToImport) { - this.importAndOpenContent(this.notebookToImport.name, this.notebookToImport.content); - } - }; - - public deleteNotebookFile(item: NotebookContentItem, isGithubTree?: boolean): Promise { - if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { - const error = "Attempt to delete notebook file, but notebook is not enabled"; - handleError(error, "Explorer/deleteNotebookFile"); - throw new Error(error); - } - - // Don't delete if tab is open to avoid accidental deletion - const openedNotebookTabs = useTabs - .getState() - .getTabs(ViewModels.CollectionTabKind.NotebookV2, (tab: NotebookV2Tab) => { - return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), item.path); - }); - if (openedNotebookTabs.length > 0) { - useDialog - .getState() - .showOkModalDialog("Unable to delete file", "This file is being edited. Please close the tab and try again."); - return Promise.reject(); - } - - if (item.type === NotebookContentItemType.Directory && item.children && item.children.length > 0) { - useDialog.getState().openDialog({ - isModal: true, - title: "Unable to delete file", - subText: "Directory is not empty.", - primaryButtonText: "Close", - secondaryButtonText: undefined, - onPrimaryButtonClick: () => useDialog.getState().closeDialog(), - onSecondaryButtonClick: undefined, - }); - return Promise.reject(); - } - - return this.notebookManager?.notebookContentClient.deleteContentItem(item, isGithubTree).then( - () => logConsoleInfo(`Successfully deleted: ${item.path}`), - (reason) => logConsoleError(`Failed to delete "${item.path}": ${JSON.stringify(reason)}`), - ); - } - - /** - * This creates a new notebook file, then opens the notebook - */ - public async onNewNotebookClicked(parent?: NotebookContentItem, isGithubTree?: boolean): Promise { - if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { - const error = "Attempt to create new notebook, but notebook is not enabled"; - handleError(error, "Explorer/onNewNotebookClicked"); - throw new Error(error); - } - if (useNotebook.getState().isPhoenixNotebooks) { - if (isGithubTree) { - await this.allocateContainer(PoolIdType.DefaultPoolId); - parent = parent || this.resourceTree.myNotebooksContentRoot; - this.createNewNoteBook(parent, isGithubTree); - } else { - useDialog.getState().showOkCancelModalDialog( - Notebook.newNotebookModalTitle, - undefined, - "Create", - async () => { - await this.allocateContainer(PoolIdType.DefaultPoolId); - parent = parent || this.resourceTree.myNotebooksContentRoot; - this.createNewNoteBook(parent, isGithubTree); - }, - "Cancel", - undefined, - this.getNewNoteWarningText(), - ); - } - } else { - parent = parent || this.resourceTree.myNotebooksContentRoot; - this.createNewNoteBook(parent, isGithubTree); - } - } - - private getNewNoteWarningText(): JSX.Element { - return ( - <> -

{Notebook.newNotebookModalContent1}

-
-

- {Notebook.newNotebookModalContent2} - - {Notebook.learnMore} - -

- - ); - } - - private createNewNoteBook(parent?: NotebookContentItem, isGithubTree?: boolean): void { - const clearInProgressMessage = logConsoleProgress(`Creating new notebook in ${parent.path}`); - const startKey: number = TelemetryProcessor.traceStart(Action.CreateNewNotebook, { - dataExplorerArea: Constants.Areas.Notebook, - }); - - this.notebookManager?.notebookContentClient - .createNewNotebookFile(parent, isGithubTree) - .then((newFile: NotebookContentItem) => { - logConsoleInfo(`Successfully created: ${newFile.name}`); - TelemetryProcessor.traceSuccess( - Action.CreateNewNotebook, - { - dataExplorerArea: Constants.Areas.Notebook, - }, - startKey, - ); - return this.openNotebook(newFile); - }) - .then(() => this.resourceTree.triggerRender()) - .catch((error) => { - const errorMessage = `Failed to create a new notebook: ${getErrorMessage(error)}`; - logConsoleError(errorMessage); - TelemetryProcessor.traceFailure( - Action.CreateNewNotebook, - { - dataExplorerArea: Constants.Areas.Notebook, - error: errorMessage, - errorStack: getErrorStack(error), - }, - startKey, - ); - }) - .finally(clearInProgressMessage); - } - // TODO: Delete this function when ResourceTreeAdapter is removed. public async refreshContentItem(item: NotebookContentItem): Promise { if (!useNotebook.getState().isNotebookEnabled || !this.notebookManager?.notebookContentClient) { @@ -1252,32 +722,6 @@ export default class Explorer { } } - public async handleOpenFileAction(path: string): Promise { - if (useNotebook.getState().isPhoenixNotebooks === undefined) { - await useNotebook.getState().getPhoenixStatus(); - } - if (useNotebook.getState().isPhoenixNotebooks) { - await this.allocateContainer(PoolIdType.DefaultPoolId); - } - - // We still use github urls like https://github.com/Azure-Samples/cosmos-notebooks/blob/master/CSharp_quickstarts/GettingStarted_CSharp.ipynb - // when launching a notebook quickstart from Portal. In future we should just use gallery id and use Juno to fetch instead of directly - // calling GitHub. For now convert this url to a raw url and download content. - const gitHubInfo = fromContentUri(path); - if (gitHubInfo) { - const rawUrl = toRawContentUri(gitHubInfo.owner, gitHubInfo.repo, gitHubInfo.branch, gitHubInfo.path); - const response = await fetch(rawUrl); - if (response.status === Constants.HttpStatusCodes.OK) { - this.notebookToImport = { - name: NotebookUtil.getName(path), - content: await response.text(), - }; - - this.importAndOpenContent(this.notebookToImport.name, this.notebookToImport.content); - } - } - } - public openUploadItemsPanePane(): void { useSidePanel.getState().openSidePanel("Upload " + getUploadName(), ); } @@ -1287,54 +731,6 @@ export default class Explorer { .openSidePanel("Input parameters", ); } - public openUploadFilePanel(parent?: NotebookContentItem): void { - if (useNotebook.getState().isPhoenixNotebooks) { - useDialog.getState().showOkCancelModalDialog( - Notebook.newNotebookUploadModalTitle, - undefined, - "Upload", - async () => { - await this.allocateContainer(PoolIdType.DefaultPoolId); - parent = parent || this.resourceTree.myNotebooksContentRoot; - this.uploadFilePanel(parent); - }, - "Cancel", - undefined, - this.getNewNoteWarningText(), - ); - } else { - parent = parent || this.resourceTree.myNotebooksContentRoot; - this.uploadFilePanel(parent); - } - } - - private uploadFilePanel(parent?: NotebookContentItem): void { - useSidePanel - .getState() - .openSidePanel( - "Upload file to notebook server", - this.uploadFile(name, content, parent)} />, - ); - } - - public getDownloadModalConent(fileName: string): JSX.Element { - if (useNotebook.getState().isPhoenixNotebooks) { - return ( - <> -

{Notebook.galleryNotebookDownloadContent1}

-
-

- {Notebook.galleryNotebookDownloadContent2} - - {Notebook.learnMore} - -

- - ); - } - return

Download {fileName} from gallery as a copy to your notebooks to run and/or edit the notebook.

; - } - public async refreshExplorer(): Promise { if (userContext.apiType !== "Postgres" && userContext.apiType !== "VCoreMongo") { userContext.authType === AuthType.ResourceToken @@ -1359,10 +755,6 @@ export default class Explorer { dataExplorerArea: Constants.Areas.Notebook, }); - if (useNotebook.getState().isPhoenixNotebooks) { - await this.initNotebooks(userContext.databaseAccount); - } - await this.refreshSampleData(); } diff --git a/src/Explorer/Notebook/NotebookManager.tsx b/src/Explorer/Notebook/NotebookManager.tsx index 3ccbefcaf..45afe6061 100644 --- a/src/Explorer/Notebook/NotebookManager.tsx +++ b/src/Explorer/Notebook/NotebookManager.tsx @@ -12,15 +12,13 @@ import * as Logger from "../../Common/Logger"; import { GitHubClient } from "../../GitHub/GitHubClient"; import { GitHubContentProvider } from "../../GitHub/GitHubContentProvider"; import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService"; -import { useSidePanel } from "../../hooks/useSidePanel"; import { JunoClient } from "../../Juno/JunoClient"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; -import { userContext } from "../../UserContext"; import { getFullName } from "../../Utils/UserUtils"; +import { useSidePanel } from "../../hooks/useSidePanel"; import { useDialog } from "../Controls/Dialog"; import Explorer from "../Explorer"; -import { CopyNotebookPane } from "../Panes/CopyNotebookPane/CopyNotebookPane"; import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel"; import { PublishNotebookPane } from "../Panes/PublishNotebookPane/PublishNotebookPane"; import { ResourceTreeAdapter } from "../Tree/ResourceTreeAdapter"; @@ -40,7 +38,6 @@ export interface NotebookManagerOptions { container: Explorer; resourceTree: ResourceTreeAdapter; refreshCommandBarButtons: () => void; - refreshNotebookList: () => void; } export default class NotebookManager { @@ -81,10 +78,6 @@ export default class NotebookManager { contents.JupyterContentProvider, ); - this.notebookClient = new NotebookContainerClient(() => - this.params.container.initNotebooks(userContext?.databaseAccount), - ); - this.notebookContentClient = new NotebookContentClient(this.notebookContentProvider); this.gitHubOAuthService.getTokenObservable().subscribe((token) => { @@ -106,11 +99,9 @@ export default class NotebookManager { } this.params.refreshCommandBarButtons(); - this.params.refreshNotebookList(); }); this.junoClient.subscribeToPinnedRepos((pinnedRepos) => { - this.params.resourceTree.initializeGitHubRepos(pinnedRepos); this.params.resourceTree.triggerRender(); useNotebook.getState().initializeGitHubRepos(pinnedRepos); }); @@ -149,22 +140,6 @@ export default class NotebookManager { ); } - public openCopyNotebookPane(name: string, content: string): void { - const { container } = this.params; - useSidePanel - .getState() - .openSidePanel( - "Copy Notebook", - , - ); - } - // Octokit's error handler uses any // eslint-disable-next-line @typescript-eslint/no-explicit-any private onGitHubClientError = (error: any): void => { diff --git a/src/Explorer/OpenActions/OpenActions.tsx b/src/Explorer/OpenActions/OpenActions.tsx index f3ef288c8..e2059cdba 100644 --- a/src/Explorer/OpenActions/OpenActions.tsx +++ b/src/Explorer/OpenActions/OpenActions.tsx @@ -195,17 +195,5 @@ export function handleOpenAction( return true; } - if ( - action.actionType === ActionContracts.ActionType.OpenSampleNotebook || - action.actionType === ActionContracts.ActionType[ActionContracts.ActionType.OpenSampleNotebook] - ) { - openFile(action as ActionContracts.OpenSampleNotebook, explorer); - return true; - } - return false; } - -function openFile(action: ActionContracts.OpenSampleNotebook, explorer: Explorer) { - explorer.handleOpenFileAction(decodeURIComponent(action.path)); -} diff --git a/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx b/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx deleted file mode 100644 index 0f7927b3b..000000000 --- a/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPane.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { IDropdownOption } from "@fluentui/react"; -import React, { FormEvent, FunctionComponent, useEffect, useState } from "react"; -import { HttpStatusCodes, PoolIdType } from "../../../Common/Constants"; -import { getErrorMessage, handleError } from "../../../Common/ErrorHandlingUtils"; -import { GitHubOAuthService } from "../../../GitHub/GitHubOAuthService"; -import { IPinnedRepo, JunoClient } from "../../../Juno/JunoClient"; -import * as GitHubUtils from "../../../Utils/GitHubUtils"; -import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils"; -import { useSidePanel } from "../../../hooks/useSidePanel"; -import Explorer from "../../Explorer"; -import { NotebookContentItem, NotebookContentItemType } from "../../Notebook/NotebookContentItem"; -import { useNotebook } from "../../Notebook/useNotebook"; -import { RightPaneForm, RightPaneFormProps } from "../RightPaneForm/RightPaneForm"; -import { CopyNotebookPaneComponent, CopyNotebookPaneProps } from "./CopyNotebookPaneComponent"; - -interface Location { - type: "MyNotebooks" | "GitHub"; - - // GitHub - owner?: string; - repo?: string; - branch?: string; -} -export interface CopyNotebookPanelProps { - name: string; - content: string; - container: Explorer; - junoClient: JunoClient; - gitHubOAuthService: GitHubOAuthService; -} - -export const CopyNotebookPane: FunctionComponent = ({ - name, - content, - container, - junoClient, - gitHubOAuthService, -}: CopyNotebookPanelProps) => { - const closeSidePanel = useSidePanel((state) => state.closeSidePanel); - const [isExecuting, setIsExecuting] = useState(); - const [formError, setFormError] = useState(""); - const [pinnedRepos, setPinnedRepos] = useState(); - const [selectedLocation, setSelectedLocation] = useState(); - - useEffect(() => { - open(); - }, []); - - const open = async (): Promise => { - if (gitHubOAuthService.isLoggedIn()) { - const response = await junoClient.getPinnedRepos(gitHubOAuthService.getTokenObservable()()?.scope); - if (response.status !== HttpStatusCodes.OK && response.status !== HttpStatusCodes.NoContent) { - handleError(`Received HTTP ${response.status} when fetching pinned repos`, "CopyNotebookPaneAdapter/submit"); - } - - if (response.data?.length > 0) { - setPinnedRepos(response.data); - } - } - }; - - const submit = async (): Promise => { - let destination: string = selectedLocation?.type; - let clearMessage: () => void; - setIsExecuting(true); - - try { - if (!selectedLocation) { - throw new Error(`No location selected`); - } - - if (selectedLocation.type === "GitHub") { - destination = `${destination} - ${GitHubUtils.toRepoFullName( - selectedLocation.owner, - selectedLocation.repo, - )} - ${selectedLocation.branch}`; - } else if (selectedLocation.type === "MyNotebooks" && useNotebook.getState().isPhoenixNotebooks) { - destination = useNotebook.getState().notebookFolderName; - } - - clearMessage = NotificationConsoleUtils.logConsoleProgress(`Copying ${name} to ${destination}`); - - const notebookContentItem = await copyNotebook(selectedLocation); - if (!notebookContentItem) { - throw new Error(`Failed to upload ${name}`); - } - - NotificationConsoleUtils.logConsoleInfo(`Successfully copied ${name} to ${destination}`); - closeSidePanel(); - } catch (error) { - const errorMessage = getErrorMessage(error); - setFormError(`Failed to copy ${name} to ${destination}`); - handleError(errorMessage, "CopyNotebookPaneAdapter/submit", formError); - } finally { - clearMessage && clearMessage(); - setIsExecuting(false); - } - }; - - const copyNotebook = async (location: Location): Promise => { - let parent: NotebookContentItem; - let isGithubTree: boolean; - switch (location.type) { - case "MyNotebooks": - parent = { - name: useNotebook.getState().notebookFolderName, - path: useNotebook.getState().notebookBasePath, - type: NotebookContentItemType.Directory, - }; - isGithubTree = false; - if (useNotebook.getState().isPhoenixNotebooks) { - await container.allocateContainer(PoolIdType.DefaultPoolId); - } - break; - - case "GitHub": - parent = { - name: selectedLocation.branch, - path: GitHubUtils.toContentUri(selectedLocation.owner, selectedLocation.repo, selectedLocation.branch, ""), - type: NotebookContentItemType.Directory, - }; - isGithubTree = true; - break; - - default: - throw new Error(`Unsupported location type ${location.type}`); - } - - return container.uploadFile(name, content, parent, isGithubTree); - }; - - const onDropDownChange = (_: FormEvent, option?: IDropdownOption): void => { - setSelectedLocation(option?.data); - }; - - const props: RightPaneFormProps = { - formError, - isExecuting: isExecuting, - submitButtonText: "OK", - onSubmit: () => submit(), - }; - - const copyNotebookPaneProps: CopyNotebookPaneProps = { - name, - pinnedRepos, - onDropDownChange: onDropDownChange, - }; - - return ( - - - - ); -}; diff --git a/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPaneComponent.tsx b/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPaneComponent.tsx deleted file mode 100644 index 5cd0cfdc1..000000000 --- a/src/Explorer/Panes/CopyNotebookPane/CopyNotebookPaneComponent.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { - Dropdown, - IDropdownOption, - IDropdownProps, - IRenderFunction, - ISelectableOption, - Label, - SelectableOptionMenuItemType, - Stack, - Text, -} from "@fluentui/react"; -import { GitHubReposTitle } from "Explorer/Tree/ResourceTree"; -import React, { FormEvent, FunctionComponent } from "react"; -import { IPinnedRepo } from "../../../Juno/JunoClient"; -import * as GitHubUtils from "../../../Utils/GitHubUtils"; -import { useNotebook } from "../../Notebook/useNotebook"; - -interface Location { - type: "MyNotebooks" | "GitHub"; - - // GitHub - owner?: string; - repo?: string; - branch?: string; -} - -export interface CopyNotebookPaneProps { - name: string; - pinnedRepos: IPinnedRepo[]; - onDropDownChange: (_: FormEvent, option?: IDropdownOption) => void; -} - -export const CopyNotebookPaneComponent: FunctionComponent = ({ - name, - pinnedRepos, - onDropDownChange, -}: CopyNotebookPaneProps) => { - const BranchNameWhiteSpace = " "; - - const onRenderDropDownTitle: IRenderFunction = (options: IDropdownOption[]): JSX.Element => { - return {options.length && options[0].title}; - }; - - const onRenderDropDownOption: IRenderFunction = (option: ISelectableOption): JSX.Element => { - return {option.text}; - }; - - const getDropDownOptions = (): IDropdownOption[] => { - const options: IDropdownOption[] = []; - options.push({ - key: "MyNotebooks-Item", - text: useNotebook.getState().notebookFolderName, - title: useNotebook.getState().notebookFolderName, - data: { - type: "MyNotebooks", - } as Location, - }); - - if (pinnedRepos && pinnedRepos.length > 0) { - options.push({ - key: "GitHub-Header-Divider", - text: undefined, - itemType: SelectableOptionMenuItemType.Divider, - }); - - options.push({ - key: "GitHub-Header", - text: GitHubReposTitle, - itemType: SelectableOptionMenuItemType.Header, - }); - - pinnedRepos.forEach((pinnedRepo) => { - const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name); - options.push({ - key: `GitHub-Repo-${repoFullName}`, - text: repoFullName, - disabled: true, - }); - - pinnedRepo.branches.forEach((branch) => - options.push({ - key: `GitHub-Repo-${repoFullName}-${branch.name}`, - text: `${BranchNameWhiteSpace}${branch.name}`, - title: `${repoFullName} - ${branch.name}`, - data: { - type: "GitHub", - owner: pinnedRepo.owner, - repo: pinnedRepo.name, - branch: branch.name, - } as Location, - }), - ); - }); - } - - return options; - }; - const dropDownProps: IDropdownProps = { - label: "Location", - ariaLabel: "Location", - placeholder: "Select an option", - onRenderTitle: onRenderDropDownTitle, - onRenderOption: onRenderDropDownOption, - options: getDropDownOptions(), - onChange: onDropDownChange, - }; - - return ( -
- - - - {name} - - - - -
- ); -}; diff --git a/src/Explorer/Panes/GitHubReposPanel/__snapshots__/GitHubReposPanel.test.tsx.snap b/src/Explorer/Panes/GitHubReposPanel/__snapshots__/GitHubReposPanel.test.tsx.snap index 4a3a8942e..aa2137c42 100644 --- a/src/Explorer/Panes/GitHubReposPanel/__snapshots__/GitHubReposPanel.test.tsx.snap +++ b/src/Explorer/Panes/GitHubReposPanel/__snapshots__/GitHubReposPanel.test.tsx.snap @@ -17,8 +17,6 @@ exports[`GitHub Repos Panel should render Default properly 1`] = ` addRepoProps={ Object { "container": Explorer { - "_isInitializingNotebooks": false, - "_resetNotebookWorkspace": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], @@ -35,10 +33,8 @@ exports[`GitHub Repos Panel should render Default properly 1`] = ` "queriesClient": QueriesClient { "container": [Circular], }, - "refreshNotebookList": [Function], "resourceTree": ResourceTreeAdapter { "container": [Circular], - "copyNotebook": [Function], "parameters": [Function], }, }, diff --git a/src/Explorer/Panes/StringInputPane/__snapshots__/StringInputPane.test.tsx.snap b/src/Explorer/Panes/StringInputPane/__snapshots__/StringInputPane.test.tsx.snap index 8054abe19..566808f4b 100644 --- a/src/Explorer/Panes/StringInputPane/__snapshots__/StringInputPane.test.tsx.snap +++ b/src/Explorer/Panes/StringInputPane/__snapshots__/StringInputPane.test.tsx.snap @@ -7,8 +7,6 @@ exports[`StringInput Pane should render Create new directory properly 1`] = ` errorMessage="Could not create directory " explorer={ Explorer { - "_isInitializingNotebooks": false, - "_resetNotebookWorkspace": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], @@ -25,10 +23,8 @@ exports[`StringInput Pane should render Create new directory properly 1`] = ` "queriesClient": QueriesClient { "container": [Circular], }, - "refreshNotebookList": [Function], "resourceTree": ResourceTreeAdapter { "container": [Circular], - "copyNotebook": [Function], "parameters": [Function], }, } diff --git a/src/Explorer/QueryCopilot/__snapshots__/QueryCopilotTab.test.tsx.snap b/src/Explorer/QueryCopilot/__snapshots__/QueryCopilotTab.test.tsx.snap index 26b52ff90..6d875fe2e 100644 --- a/src/Explorer/QueryCopilot/__snapshots__/QueryCopilotTab.test.tsx.snap +++ b/src/Explorer/QueryCopilot/__snapshots__/QueryCopilotTab.test.tsx.snap @@ -22,8 +22,6 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] = databaseId="CopilotSampleDb" explorer={ Explorer { - "_isInitializingNotebooks": false, - "_resetNotebookWorkspace": [Function], "isFixedCollectionWithSharedThroughputSupported": [Function], "isTabsContentExpanded": [Function], "onRefreshDatabasesKeyPress": [Function], @@ -40,10 +38,8 @@ exports[`Query copilot tab snapshot test should render with initial input 1`] = "queriesClient": QueriesClient { "container": [Circular], }, - "refreshNotebookList": [Function], "resourceTree": ResourceTreeAdapter { "container": [Circular], - "copyNotebook": [Function], "parameters": [Function], }, } diff --git a/src/Explorer/SplashScreen/SplashScreen.tsx b/src/Explorer/SplashScreen/SplashScreen.tsx index f4ebb9cd0..e9477e088 100644 --- a/src/Explorer/SplashScreen/SplashScreen.tsx +++ b/src/Explorer/SplashScreen/SplashScreen.tsx @@ -25,11 +25,9 @@ import * as React from "react"; import ConnectIcon from "../../../images/Connect_color.svg"; import ContainersIcon from "../../../images/Containers.svg"; import LinkIcon from "../../../images/Link_blue.svg"; -import NotebookColorIcon from "../../../images/Notebooks.svg"; import PowerShellIcon from "../../../images/PowerShell.svg"; import CopilotIcon from "../../../images/QueryCopilotNewLogo.svg"; import QuickStartIcon from "../../../images/Quickstart_Lightning.svg"; -import NotebookIcon from "../../../images/notebook/Notebook-resource.svg"; import CollectionIcon from "../../../images/tree-collection.svg"; import * as Constants from "../../Common/Constants"; import { userContext } from "../../UserContext"; @@ -410,14 +408,6 @@ export class SplashScreen extends React.Component { }, }; heroes.push(launchQuickstartBtn); - } else if (useNotebook.getState().isPhoenixNotebooks) { - const newNotebookBtn = { - iconSrc: NotebookColorIcon, - title: "New notebook", - description: "Visualize your data stored in Azure Cosmos DB", - onClick: () => this.container.onNewNotebookClicked(), - }; - heroes.push(newNotebookBtn); } heroes.push(this.getShellCard()); @@ -493,28 +483,12 @@ export class SplashScreen extends React.Component { }; } - private decorateOpenNotebookActivity({ name, path }: MostRecentActivity.OpenNotebookItem) { - return { - info: path, - iconSrc: NotebookIcon, - title: name, - description: "Notebook", - onClick: () => { - const notebookItem = this.container.createNotebookContentItemFile(name, path); - notebookItem && this.container.openNotebook(notebookItem); - }, - }; - } - private createRecentItems(): SplashScreenItem[] { return MostRecentActivity.mostRecentActivity.getItems(userContext.databaseAccount?.id).map((activity) => { switch (activity.type) { default: { - const unknownActivity: never = activity; - throw new Error(`Unknown activity: ${unknownActivity}`); + throw new Error(`Unknown activity: ${activity}`); } - case MostRecentActivity.Type.OpenNotebook: - return this.decorateOpenNotebookActivity(activity); case MostRecentActivity.Type.OpenCollection: return this.decorateOpenCollectionActivity(activity); diff --git a/src/Explorer/Tabs/NotebookV2Tab.ts b/src/Explorer/Tabs/NotebookV2Tab.ts index 92e0e6958..fadb43258 100644 --- a/src/Explorer/Tabs/NotebookV2Tab.ts +++ b/src/Explorer/Tabs/NotebookV2Tab.ts @@ -1,31 +1,11 @@ -import { stringifyNotebook, toJS } from "@nteract/commutable"; import * as ko from "knockout"; import * as Q from "q"; -import { userContext } from "UserContext"; -import ClearAllOutputsIcon from "../../../images/notebook/Notebook-clear-all-outputs.svg"; -import CopyIcon from "../../../images/notebook/Notebook-copy.svg"; -import CutIcon from "../../../images/notebook/Notebook-cut.svg"; -import NewCellIcon from "../../../images/notebook/Notebook-insert-cell.svg"; -import PasteIcon from "../../../images/notebook/Notebook-paste.svg"; -import RestartIcon from "../../../images/notebook/Notebook-restart.svg"; -import RunAllIcon from "../../../images/notebook/Notebook-run-all.svg"; -import RunIcon from "../../../images/notebook/Notebook-run.svg"; -import { default as InterruptKernelIcon, default as KillKernelIcon } from "../../../images/notebook/Notebook-stop.svg"; -import SaveIcon from "../../../images/save-cosmos.svg"; -import { useNotebookSnapshotStore } from "../../hooks/useNotebookSnapshotStore"; -import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants"; -import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import * as NotebookConfigurationUtils from "../../Utils/NotebookConfigurationUtils"; import { logConsoleInfo } from "../../Utils/NotificationConsoleUtils"; import { CommandButtonComponentProps } from "../Controls/CommandButton/CommandButtonComponent"; import { useDialog } from "../Controls/Dialog"; -import * as CommandBarComponentButtonFactory from "../Menus/CommandBar/CommandBarComponentButtonFactory"; -import { KernelSpecsDisplay } from "../Notebook/NotebookClientV2"; -import * as CdbActions from "../Notebook/NotebookComponent/actions"; import { NotebookComponentAdapter } from "../Notebook/NotebookComponent/NotebookComponentAdapter"; -import { CdbAppState, SnapshotRequest } from "../Notebook/NotebookComponent/types"; import { NotebookContentItem } from "../Notebook/NotebookContentItem"; -import { NotebookUtil } from "../Notebook/NotebookUtil"; import { useNotebook } from "../Notebook/useNotebook"; import NotebookTabBase, { NotebookTabBaseOptions } from "./NotebookTabBase"; @@ -90,275 +70,7 @@ export default class NotebookTabV2 extends NotebookTabBase { } protected getTabsButtons(): CommandButtonComponentProps[] { - const availableKernels = NotebookTabV2.clientManager.getAvailableKernelSpecs(); - const isNotebookUntrusted = this.notebookComponentAdapter.isNotebookUntrusted(); - - const runBtnTooltip = isNotebookUntrusted ? NotebookUtil.UntrustedNotebookRunHint : undefined; - - const saveLabel = "Save"; - const copyToLabel = "Copy to ..."; - const publishLabel = "Publish to gallery"; - const kernelLabel = "No Kernel"; - const runLabel = "Run"; - const runActiveCellLabel = "Run Active Cell"; - const runAllLabel = "Run All"; - const interruptKernelLabel = "Interrupt Kernel"; - const killKernelLabel = "Halt Kernel"; - const restartKernelLabel = "Restart Kernel"; - const clearLabel = "Clear outputs"; - const newCellLabel = "New Cell"; - const cellTypeLabel = "Cell Type"; - const codeLabel = "Code"; - const markdownLabel = "Markdown"; - const rawLabel = "Raw"; - const copyLabel = "Copy"; - const cutLabel = "Cut"; - const pasteLabel = "Paste"; - const cellCodeType = "code"; - const cellMarkdownType = "markdown"; - const cellRawType = "raw"; - - const saveButtonChildren = []; - if (this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) { - saveButtonChildren.push({ - iconName: copyToLabel, - onCommandClick: () => this.copyNotebook(), - commandButtonLabel: copyToLabel, - hasPopup: false, - disabled: false, - ariaLabel: copyToLabel, - }); - } - - if (userContext.features.publicGallery) { - saveButtonChildren.push({ - iconName: "PublishContent", - onCommandClick: async () => await this.publishToGallery(), - commandButtonLabel: publishLabel, - hasPopup: false, - disabled: false, - ariaLabel: publishLabel, - }); - } - - let buttons: CommandButtonComponentProps[] = [ - { - iconSrc: SaveIcon, - iconAlt: saveLabel, - onCommandClick: () => this.notebookComponentAdapter.notebookSave(), - commandButtonLabel: saveLabel, - hasPopup: false, - disabled: false, - ariaLabel: saveLabel, - children: saveButtonChildren.length && [ - { - iconName: "Save", - onCommandClick: () => this.notebookComponentAdapter.notebookSave(), - commandButtonLabel: saveLabel, - hasPopup: false, - disabled: false, - ariaLabel: saveLabel, - }, - ...saveButtonChildren, - ], - }, - { - iconSrc: null, - iconAlt: kernelLabel, - onCommandClick: () => {}, - commandButtonLabel: null, - hasPopup: false, - disabled: availableKernels.length < 1, - isDropdown: true, - dropdownPlaceholder: kernelLabel, - dropdownSelectedKey: this.notebookComponentAdapter.getSelectedKernelName(), //this.currentKernelName, - dropdownWidth: 100, - children: availableKernels.map( - (kernel: KernelSpecsDisplay) => - ({ - iconSrc: null, - iconAlt: kernel.displayName, - onCommandClick: () => this.notebookComponentAdapter.notebookChangeKernel(kernel.name), - commandButtonLabel: kernel.displayName, - dropdownItemKey: kernel.name, - hasPopup: false, - disabled: false, - ariaLabel: kernel.displayName, - }) as CommandButtonComponentProps, - ), - ariaLabel: kernelLabel, - }, - { - iconSrc: RunIcon, - iconAlt: runLabel, - onCommandClick: () => { - this.notebookComponentAdapter.notebookRunAndAdvance(); - this.traceTelemetry(Action.ExecuteCell); - }, - commandButtonLabel: runLabel, - tooltipText: runBtnTooltip, - ariaLabel: runLabel, - hasPopup: false, - disabled: isNotebookUntrusted, - children: [ - { - iconSrc: RunIcon, - iconAlt: runActiveCellLabel, - onCommandClick: () => { - this.notebookComponentAdapter.notebookRunAndAdvance(); - this.traceTelemetry(Action.ExecuteCell); - }, - commandButtonLabel: runActiveCellLabel, - hasPopup: false, - disabled: false, - ariaLabel: runActiveCellLabel, - }, - { - iconSrc: RunAllIcon, - iconAlt: runAllLabel, - onCommandClick: () => { - this.notebookComponentAdapter.notebookRunAll(); - this.traceTelemetry(Action.ExecuteAllCells); - }, - commandButtonLabel: runAllLabel, - hasPopup: false, - disabled: false, - ariaLabel: runAllLabel, - }, - { - iconSrc: InterruptKernelIcon, - iconAlt: interruptKernelLabel, - onCommandClick: () => this.notebookComponentAdapter.notebookInterruptKernel(), - commandButtonLabel: interruptKernelLabel, - hasPopup: false, - disabled: false, - ariaLabel: interruptKernelLabel, - }, - { - iconSrc: KillKernelIcon, - iconAlt: killKernelLabel, - onCommandClick: () => this.notebookComponentAdapter.notebookKillKernel(), - commandButtonLabel: killKernelLabel, - hasPopup: false, - disabled: false, - ariaLabel: killKernelLabel, - }, - { - iconSrc: RestartIcon, - iconAlt: restartKernelLabel, - onCommandClick: () => this.notebookComponentAdapter.notebookRestartKernel(), - commandButtonLabel: restartKernelLabel, - hasPopup: false, - disabled: false, - ariaLabel: restartKernelLabel, - }, - ], - }, - { - iconSrc: ClearAllOutputsIcon, - iconAlt: clearLabel, - onCommandClick: () => this.notebookComponentAdapter.notebookClearAllOutputs(), - commandButtonLabel: clearLabel, - hasPopup: false, - disabled: false, - ariaLabel: clearLabel, - }, - { - iconSrc: NewCellIcon, - iconAlt: newCellLabel, - onCommandClick: () => this.notebookComponentAdapter.notebookInsertBelow(), - commandButtonLabel: newCellLabel, - ariaLabel: newCellLabel, - hasPopup: false, - disabled: false, - }, - CommandBarComponentButtonFactory.createDivider(), - { - iconSrc: null, - iconAlt: null, - onCommandClick: () => {}, - commandButtonLabel: null, - ariaLabel: cellTypeLabel, - hasPopup: false, - disabled: false, - isDropdown: true, - dropdownPlaceholder: cellTypeLabel, - dropdownSelectedKey: this.notebookComponentAdapter.getActiveCellTypeStr(), - dropdownWidth: 110, - children: [ - { - iconSrc: null, - iconAlt: null, - onCommandClick: () => this.notebookComponentAdapter.notebookChangeCellType(cellCodeType), - commandButtonLabel: codeLabel, - ariaLabel: codeLabel, - dropdownItemKey: cellCodeType, - hasPopup: false, - disabled: false, - }, - { - iconSrc: null, - iconAlt: null, - onCommandClick: () => this.notebookComponentAdapter.notebookChangeCellType(cellMarkdownType), - commandButtonLabel: markdownLabel, - ariaLabel: markdownLabel, - dropdownItemKey: cellMarkdownType, - hasPopup: false, - disabled: false, - }, - { - iconSrc: null, - iconAlt: null, - onCommandClick: () => this.notebookComponentAdapter.notebookChangeCellType(cellRawType), - commandButtonLabel: rawLabel, - ariaLabel: rawLabel, - dropdownItemKey: cellRawType, - hasPopup: false, - disabled: false, - }, - ], - }, - { - iconSrc: CopyIcon, - iconAlt: copyLabel, - onCommandClick: () => this.notebookComponentAdapter.notebokCopy(), - commandButtonLabel: copyLabel, - ariaLabel: copyLabel, - hasPopup: false, - disabled: false, - children: [ - { - iconSrc: CopyIcon, - iconAlt: copyLabel, - onCommandClick: () => this.notebookComponentAdapter.notebokCopy(), - commandButtonLabel: copyLabel, - ariaLabel: copyLabel, - hasPopup: false, - disabled: false, - }, - { - iconSrc: CutIcon, - iconAlt: cutLabel, - onCommandClick: () => this.notebookComponentAdapter.notebookCut(), - commandButtonLabel: cutLabel, - ariaLabel: cutLabel, - hasPopup: false, - disabled: false, - }, - { - iconSrc: PasteIcon, - iconAlt: pasteLabel, - onCommandClick: () => this.notebookComponentAdapter.notebookPaste(), - commandButtonLabel: pasteLabel, - ariaLabel: pasteLabel, - hasPopup: false, - disabled: false, - }, - ], - }, - // TODO: Uncomment when undo/redo is reimplemented in nteract - ]; - return buttons; + return []; } protected buildCommandBarOptions(): void { @@ -382,50 +94,4 @@ export default class NotebookTabV2 extends NotebookTabBase { sparkClusterConnectionInfo, ); } - - private publishToGallery = async () => { - TelemetryProcessor.trace(Action.NotebooksGalleryClickPublishToGallery, ActionModifiers.Mark, { - source: Source.CommandBarMenu, - }); - - const notebookReduxStore = NotebookTabV2.clientManager.getStore(); - const unsubscribe = notebookReduxStore.subscribe(() => { - const cdbState = (notebookReduxStore.getState() as CdbAppState).cdb; - useNotebookSnapshotStore.setState({ - snapshot: cdbState.notebookSnapshot?.imageSrc, - error: cdbState.notebookSnapshotError, - }); - }); - - const notebookContent = this.notebookComponentAdapter.getContent(); - const notebookContentRef = this.notebookComponentAdapter.contentRef; - const onPanelClose = (): void => { - unsubscribe(); - useNotebookSnapshotStore.setState({ - snapshot: undefined, - error: undefined, - }); - notebookReduxStore.dispatch(CdbActions.takeNotebookSnapshot(undefined)); - }; - - await this.container.publishNotebook( - notebookContent.name, - notebookContent.content, - notebookContentRef, - (request: SnapshotRequest) => notebookReduxStore.dispatch(CdbActions.takeNotebookSnapshot(request)), - onPanelClose, - ); - }; - - private copyNotebook = () => { - const notebookContent = this.notebookComponentAdapter.getContent(); - let content: string; - if (typeof notebookContent.content === "string") { - content = notebookContent.content; - } else { - content = stringifyNotebook(toJS(notebookContent.content)); - } - - this.container.copyNotebook(notebookContent.name, content); - }; } diff --git a/src/Explorer/Tree/ResourceTree.tsx b/src/Explorer/Tree/ResourceTree.tsx index b5f759534..0933eac7b 100644 --- a/src/Explorer/Tree/ResourceTree.tsx +++ b/src/Explorer/Tree/ResourceTree.tsx @@ -1,42 +1,23 @@ -import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react"; import { SampleDataTree } from "Explorer/Tree/SampleDataTree"; import { getItemName } from "Utils/APITypeUtils"; import { useQueryCopilot } from "hooks/useQueryCopilot"; import * as React from "react"; import shallow from "zustand/shallow"; import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg"; -import GalleryIcon from "../../../images/GalleryIcon.svg"; -import DeleteIcon from "../../../images/delete.svg"; -import CopyIcon from "../../../images/notebook/Notebook-copy.svg"; -import NewNotebookIcon from "../../../images/notebook/Notebook-new.svg"; -import NotebookIcon from "../../../images/notebook/Notebook-resource.svg"; -import FileIcon from "../../../images/notebook/file-cosmos.svg"; -import PublishIcon from "../../../images/notebook/publish_content.svg"; -import RefreshIcon from "../../../images/refresh-cosmos.svg"; import CollectionIcon from "../../../images/tree-collection.svg"; -import { Areas, ConnectionStatusType, Notebook } from "../../Common/Constants"; import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility"; import * as DataModels from "../../Contracts/DataModels"; import * as ViewModels from "../../Contracts/ViewModels"; -import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility"; -import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants"; -import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../../UserContext"; import { isServerlessAccount } from "../../Utils/CapabilityUtils"; -import * as GitHubUtils from "../../Utils/GitHubUtils"; -import { useSidePanel } from "../../hooks/useSidePanel"; import { useTabs } from "../../hooks/useTabs"; import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory"; import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent"; -import { useDialog } from "../Controls/Dialog"; -import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeComponent/TreeComponent"; +import { TreeComponent, TreeNode } from "../Controls/TreeComponent/TreeComponent"; import Explorer from "../Explorer"; import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter"; import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity"; -import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem"; -import { NotebookUtil } from "../Notebook/NotebookUtil"; import { useNotebook } from "../Notebook/useNotebook"; -import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel"; import TabsBase from "../Tabs/TabsBase"; import { useDatabases } from "../useDatabases"; import { useSelectedNode } from "../useSelectedNode"; @@ -45,391 +26,21 @@ import StoredProcedure from "./StoredProcedure"; import Trigger from "./Trigger"; import UserDefinedFunction from "./UserDefinedFunction"; -export const MyNotebooksTitle = "My Notebooks"; -export const GitHubReposTitle = "GitHub repos"; - interface ResourceTreeProps { container: Explorer; } export const ResourceTree: React.FC = ({ container }: ResourceTreeProps): JSX.Element => { const databases = useDatabases((state) => state.databases); - const { - isNotebookEnabled, - myNotebooksContentRoot, - galleryContentRoot, - gitHubNotebooksContentRoot, - updateNotebookItem, - } = useNotebook( + const { isNotebookEnabled } = useNotebook( (state) => ({ isNotebookEnabled: state.isNotebookEnabled, - myNotebooksContentRoot: state.myNotebooksContentRoot, - galleryContentRoot: state.galleryContentRoot, - gitHubNotebooksContentRoot: state.gitHubNotebooksContentRoot, - updateNotebookItem: state.updateNotebookItem, }), shallow, ); - const { activeTab, refreshActiveTab } = useTabs(); + const { refreshActiveTab } = useTabs(); const showScriptNodes = configContext.platform !== Platform.Fabric && (userContext.apiType === "SQL" || userContext.apiType === "Gremlin"); - const pseudoDirPath = "PsuedoDir"; - - const buildGalleryCallout = (): JSX.Element => { - if ( - LocalStorageUtility.hasItem(StorageKey.GalleryCalloutDismissed) && - LocalStorageUtility.getEntryBoolean(StorageKey.GalleryCalloutDismissed) - ) { - return undefined; - } - - const calloutProps: ICalloutProps = { - calloutMaxWidth: 350, - ariaLabel: "New gallery", - role: "alertdialog", - gapSpace: 0, - target: ".galleryHeader", - directionalHint: DirectionalHint.leftTopEdge, - onDismiss: () => { - LocalStorageUtility.setEntryBoolean(StorageKey.GalleryCalloutDismissed, true); - }, - setInitialFocus: true, - }; - - const openGalleryProps: ILinkProps = { - onClick: () => { - LocalStorageUtility.setEntryBoolean(StorageKey.GalleryCalloutDismissed, true); - container.openGallery(); - }, - }; - - return ( - - - - New gallery - - - Sample notebooks are now combined in gallery. View and try out samples provided by Microsoft and other - contributors. - - Open gallery - - - ); - }; - - const buildNotebooksTree = (): TreeNode => { - const notebooksTree: TreeNode = { - label: undefined, - isExpanded: true, - children: [], - }; - - if (!useNotebook.getState().isPhoenixNotebooks) { - notebooksTree.children.push(buildNotebooksTemporarilyDownTree()); - } else { - if (galleryContentRoot) { - notebooksTree.children.push(buildGalleryNotebooksTree()); - } - - if ( - myNotebooksContentRoot && - useNotebook.getState().isPhoenixNotebooks && - useNotebook.getState().connectionInfo.status === ConnectionStatusType.Connected - ) { - notebooksTree.children.push(buildMyNotebooksTree()); - } - if (container.notebookManager?.gitHubOAuthService.isLoggedIn()) { - // collapse all other notebook nodes - notebooksTree.children.forEach((node) => (node.isExpanded = false)); - notebooksTree.children.push(buildGitHubNotebooksTree(true)); - } - } - return notebooksTree; - }; - - const buildNotebooksTemporarilyDownTree = (): TreeNode => { - return { - label: Notebook.temporarilyDownMsg, - className: "clickDisabled", - }; - }; - - const buildGalleryNotebooksTree = (): TreeNode => { - return { - label: "Gallery", - iconSrc: GalleryIcon, - className: "notebookHeader galleryHeader", - onClick: () => container.openGallery(), - isSelected: () => activeTab?.tabKind === ViewModels.CollectionTabKind.Gallery, - }; - }; - - const buildMyNotebooksTree = (): TreeNode => { - const myNotebooksTree: TreeNode = buildNotebookDirectoryNode( - myNotebooksContentRoot, - (item: NotebookContentItem) => { - container.openNotebook(item); - }, - ); - - myNotebooksTree.isExpanded = true; - myNotebooksTree.isAlphaSorted = true; - // Remove "Delete" menu item from context menu - myNotebooksTree.contextMenu = myNotebooksTree.contextMenu.filter((menuItem) => menuItem.label !== "Delete"); - return myNotebooksTree; - }; - - const buildGitHubNotebooksTree = (isConnected: boolean): TreeNode => { - const gitHubNotebooksTree: TreeNode = buildNotebookDirectoryNode( - gitHubNotebooksContentRoot, - (item: NotebookContentItem) => { - container.openNotebook(item); - }, - true, - ); - const manageGitContextMenu: TreeNodeMenuItem[] = [ - { - label: "Manage GitHub settings", - onClick: () => - useSidePanel - .getState() - .openSidePanel( - "Manage GitHub settings", - , - ), - }, - { - label: "Disconnect from GitHub", - onClick: () => { - TelemetryProcessor.trace(Action.NotebooksGitHubDisconnect, ActionModifiers.Mark, { - dataExplorerArea: Areas.Notebook, - }); - container.notebookManager?.gitHubOAuthService.logout(); - }, - }, - ]; - gitHubNotebooksTree.contextMenu = manageGitContextMenu; - gitHubNotebooksTree.isExpanded = true; - gitHubNotebooksTree.isAlphaSorted = true; - - return gitHubNotebooksTree; - }; - - const buildChildNodes = ( - item: NotebookContentItem, - onFileClick: (item: NotebookContentItem) => void, - isGithubTree?: boolean, - ): TreeNode[] => { - if (!item || !item.children) { - return []; - } else { - return item.children.map((item) => { - const result = - item.type === NotebookContentItemType.Directory - ? buildNotebookDirectoryNode(item, onFileClick, isGithubTree) - : buildNotebookFileNode(item, onFileClick, isGithubTree); - result.timestamp = item.timestamp; - return result; - }); - } - }; - - const buildNotebookFileNode = ( - item: NotebookContentItem, - onFileClick: (item: NotebookContentItem) => void, - isGithubTree?: boolean, - ): TreeNode => { - return { - label: item.name, - iconSrc: NotebookUtil.isNotebookFile(item.path) ? NotebookIcon : FileIcon, - className: "notebookHeader", - onClick: () => onFileClick(item), - isSelected: () => { - return ( - activeTab && - activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && - /* TODO Redesign Tab interface so that resource tree doesn't need to know about NotebookV2Tab. - NotebookV2Tab could be dynamically imported, but not worth it to just get this type right. - */ - (activeTab as any).notebookPath() === item.path - ); - }, - contextMenu: createFileContextMenu(container, item, isGithubTree), - data: item, - }; - }; - - const createFileContextMenu = ( - container: Explorer, - item: NotebookContentItem, - isGithubTree?: boolean, - ): TreeNodeMenuItem[] => { - let items: TreeNodeMenuItem[] = [ - { - label: "Rename", - iconSrc: NotebookIcon, - onClick: () => container.renameNotebook(item, isGithubTree), - }, - { - label: "Delete", - iconSrc: DeleteIcon, - onClick: () => { - useDialog - .getState() - .showOkCancelModalDialog( - "Confirm delete", - `Are you sure you want to delete "${item.name}"`, - "Delete", - () => container.deleteNotebookFile(item, isGithubTree), - "Cancel", - undefined, - ); - }, - }, - { - label: "Copy to ...", - iconSrc: CopyIcon, - onClick: () => copyNotebook(container, item), - }, - { - label: "Download", - iconSrc: NotebookIcon, - onClick: () => container.downloadFile(item), - }, - ]; - - if (item.type === NotebookContentItemType.Notebook && userContext.features.publicGallery) { - items.push({ - label: "Publish to gallery", - iconSrc: PublishIcon, - onClick: async () => { - TelemetryProcessor.trace(Action.NotebooksGalleryClickPublishToGallery, ActionModifiers.Mark, { - source: Source.ResourceTreeMenu, - }); - - const content = await container.readFile(item); - if (content) { - await container.publishNotebook(item.name, content); - } - }, - }); - } - - // "Copy to ..." isn't needed if github locations are not available - if (!container.notebookManager?.gitHubOAuthService.isLoggedIn()) { - items = items.filter((item) => item.label !== "Copy to ..."); - } - - return items; - }; - - const copyNotebook = async (container: Explorer, item: NotebookContentItem) => { - const content = await container.readFile(item); - if (content) { - container.copyNotebook(item.name, content); - } - }; - - const createDirectoryContextMenu = ( - container: Explorer, - item: NotebookContentItem, - isGithubTree?: boolean, - ): TreeNodeMenuItem[] => { - let items: TreeNodeMenuItem[] = [ - { - label: "Refresh", - iconSrc: RefreshIcon, - onClick: () => loadSubitems(item, isGithubTree), - }, - { - label: "Delete", - iconSrc: DeleteIcon, - onClick: () => { - useDialog - .getState() - .showOkCancelModalDialog( - "Confirm delete", - `Are you sure you want to delete "${item.name}?"`, - "Delete", - () => container.deleteNotebookFile(item, isGithubTree), - "Cancel", - undefined, - ); - }, - }, - { - label: "Rename", - iconSrc: NotebookIcon, - onClick: () => container.renameNotebook(item, isGithubTree), - }, - { - label: "New Directory", - iconSrc: NewNotebookIcon, - onClick: () => container.onCreateDirectory(item, isGithubTree), - }, - { - label: "Upload File", - iconSrc: NewNotebookIcon, - onClick: () => container.openUploadFilePanel(item), - }, - ]; - - //disallow renaming of temporary notebook workspace - if (item?.path === useNotebook.getState().notebookBasePath) { - items = items.filter((item) => item.label !== "Rename"); - } - - // For GitHub paths remove "Delete", "Rename", "New Directory", "Upload File" - if (GitHubUtils.fromContentUri(item.path)) { - items = items.filter( - (item) => - item.label !== "Delete" && - item.label !== "Rename" && - item.label !== "New Directory" && - item.label !== "Upload File", - ); - } - - return items; - }; - - const buildNotebookDirectoryNode = ( - item: NotebookContentItem, - onFileClick: (item: NotebookContentItem) => void, - isGithubTree?: boolean, - ): TreeNode => { - return { - label: item.name, - iconSrc: undefined, - className: "notebookHeader", - isAlphaSorted: true, - isLeavesParentsSeparate: true, - onClick: () => { - if (!item.children) { - loadSubitems(item, isGithubTree); - } - }, - isSelected: () => { - return ( - activeTab && - activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && - /* TODO Redesign Tab interface so that resource tree doesn't need to know about NotebookV2Tab. - NotebookV2Tab could be dynamically imported, but not worth it to just get this type right. - */ - (activeTab as any).notebookPath() === item.path - ); - }, - contextMenu: item.path !== pseudoDirPath ? createDirectoryContextMenu(container, item, isGithubTree) : undefined, - data: item, - children: buildChildNodes(item, onFileClick, isGithubTree), - }; - }; const buildDataTree = (): TreeNode => { const databaseTreeNodes: TreeNode[] = databases.map((database: ViewModels.Database) => { @@ -757,11 +368,6 @@ export const ResourceTree: React.FC = ({ container }: Resourc return traverse(schema); }; - const loadSubitems = async (item: NotebookContentItem, isGithubTree?: boolean): Promise => { - const updatedItem = await container.notebookManager?.notebookContentClient?.updateItemChildren(item); - updateNotebookItem(updatedItem, isGithubTree); - }; - const dataRootNode = buildDataTree(); const isSampleDataEnabled = useQueryCopilot().copilotEnabled && @@ -775,46 +381,16 @@ export const ResourceTree: React.FC = ({ container }: Resourc {!isNotebookEnabled && !isSampleDataEnabled && ( )} - {isNotebookEnabled && !isSampleDataEnabled && ( - <> - - - - - - - {/* {buildGalleryCallout()} */} - - )} {!isNotebookEnabled && isSampleDataEnabled && ( <> - + - - {/* {buildGalleryCallout()} */} - - )} - {isNotebookEnabled && isSampleDataEnabled && ( - <> - - - - - - - - - - - - - {/* {buildGalleryCallout()} */} )} diff --git a/src/Explorer/Tree/ResourceTreeAdapter.tsx b/src/Explorer/Tree/ResourceTreeAdapter.tsx index b22f0f916..67fb54773 100644 --- a/src/Explorer/Tree/ResourceTreeAdapter.tsx +++ b/src/Explorer/Tree/ResourceTreeAdapter.tsx @@ -1,42 +1,21 @@ -import { Callout, DirectionalHint, ICalloutProps, ILinkProps, Link, Stack, Text } from "@fluentui/react"; import { getItemName } from "Utils/APITypeUtils"; import * as ko from "knockout"; import * as React from "react"; import CosmosDBIcon from "../../../images/Azure-Cosmos-DB.svg"; -import GalleryIcon from "../../../images/GalleryIcon.svg"; -import DeleteIcon from "../../../images/delete.svg"; -import CopyIcon from "../../../images/notebook/Notebook-copy.svg"; -import NewNotebookIcon from "../../../images/notebook/Notebook-new.svg"; -import NotebookIcon from "../../../images/notebook/Notebook-resource.svg"; -import FileIcon from "../../../images/notebook/file-cosmos.svg"; -import PublishIcon from "../../../images/notebook/publish_content.svg"; -import RefreshIcon from "../../../images/refresh-cosmos.svg"; import CollectionIcon from "../../../images/tree-collection.svg"; import { ReactAdapter } from "../../Bindings/ReactBindingHandler"; -import { Areas } from "../../Common/Constants"; import { isPublicInternetAccessAllowed } from "../../Common/DatabaseAccountUtility"; import * as DataModels from "../../Contracts/DataModels"; import * as ViewModels from "../../Contracts/ViewModels"; -import { IPinnedRepo } from "../../Juno/JunoClient"; -import { LocalStorageUtility, StorageKey } from "../../Shared/StorageUtility"; -import { Action, ActionModifiers, Source } from "../../Shared/Telemetry/TelemetryConstants"; -import * as TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import { userContext } from "../../UserContext"; import { isServerlessAccount } from "../../Utils/CapabilityUtils"; -import * as GitHubUtils from "../../Utils/GitHubUtils"; -import { useSidePanel } from "../../hooks/useSidePanel"; import { useTabs } from "../../hooks/useTabs"; import * as ResourceTreeContextMenuButtonFactory from "../ContextMenuButtonFactory"; -import { AccordionComponent, AccordionItemComponent } from "../Controls/Accordion/AccordionComponent"; -import { useDialog } from "../Controls/Dialog"; -import { TreeComponent, TreeNode, TreeNodeMenuItem } from "../Controls/TreeComponent/TreeComponent"; +import { TreeComponent, TreeNode } from "../Controls/TreeComponent/TreeComponent"; import Explorer from "../Explorer"; import { useCommandBar } from "../Menus/CommandBar/CommandBarComponentAdapter"; import { mostRecentActivity } from "../MostRecentActivity/MostRecentActivity"; -import { NotebookContentItem, NotebookContentItemType } from "../Notebook/NotebookContentItem"; -import { NotebookUtil } from "../Notebook/NotebookUtil"; import { useNotebook } from "../Notebook/useNotebook"; -import { GitHubReposPanel } from "../Panes/GitHubReposPanel/GitHubReposPanel"; import TabsBase from "../Tabs/TabsBase"; import { useDatabases } from "../useDatabases"; import { useSelectedNode } from "../useSelectedNode"; @@ -46,19 +25,8 @@ import Trigger from "./Trigger"; import UserDefinedFunction from "./UserDefinedFunction"; export class ResourceTreeAdapter implements ReactAdapter { - public static readonly MyNotebooksTitle = "My Notebooks"; - public static readonly GitHubReposTitle = "GitHub repos"; - - private static readonly DataTitle = "DATA"; - private static readonly NotebooksTitle = "NOTEBOOKS"; - private static readonly PseudoDirPath = "PsuedoDir"; - public parameters: ko.Observable; - public galleryContentRoot: NotebookContentItem; - public myNotebooksContentRoot: NotebookContentItem; - public gitHubNotebooksContentRoot: NotebookContentItem; - public constructor(private container: Explorer) { this.parameters = ko.observable(Date.now()); @@ -76,111 +44,9 @@ export class ResourceTreeAdapter implements ReactAdapter { this.triggerRender(); } - private traceMyNotebookTreeInfo() { - const myNotebooksTree = this.myNotebooksContentRoot; - if (myNotebooksTree.children) { - // Count 1st generation children (tree is lazy-loaded) - const nodeCounts = { files: 0, notebooks: 0, directories: 0 }; - myNotebooksTree.children.forEach((treeNode) => { - switch ((treeNode as NotebookContentItem).type) { - case NotebookContentItemType.File: - nodeCounts.files++; - break; - case NotebookContentItemType.Directory: - nodeCounts.directories++; - break; - case NotebookContentItemType.Notebook: - nodeCounts.notebooks++; - break; - default: - break; - } - }); - TelemetryProcessor.trace(Action.RefreshResourceTreeMyNotebooks, ActionModifiers.Mark, { ...nodeCounts }); - } - } - public renderComponent(): JSX.Element { const dataRootNode = this.buildDataTree(); - const notebooksRootNode = this.buildNotebooksTrees(); - - if (useNotebook.getState().isNotebookEnabled) { - return ( - <> - - - - - - - - - - {/* {this.galleryContentRoot && this.buildGalleryCallout()} */} - - ); - } else { - return ; - } - } - - public async initialize(): Promise { - const refreshTasks: Promise[] = []; - - this.galleryContentRoot = { - name: "Gallery", - path: "Gallery", - type: NotebookContentItemType.File, - }; - this.myNotebooksContentRoot = { - name: useNotebook.getState().notebookFolderName, - path: useNotebook.getState().notebookBasePath, - type: NotebookContentItemType.Directory, - }; - - // Only if notebook server is available we can refresh - if (useNotebook.getState().notebookServerInfo?.notebookServerEndpoint) { - refreshTasks.push( - this.container.refreshContentItem(this.myNotebooksContentRoot).then(() => { - this.triggerRender(); - this.traceMyNotebookTreeInfo(); - }), - ); - } - this.gitHubNotebooksContentRoot = { - name: ResourceTreeAdapter.GitHubReposTitle, - path: ResourceTreeAdapter.PseudoDirPath, - type: NotebookContentItemType.Directory, - }; - - return Promise.all(refreshTasks); - } - - public initializeGitHubRepos(pinnedRepos: IPinnedRepo[]): void { - if (this.gitHubNotebooksContentRoot) { - this.gitHubNotebooksContentRoot.children = []; - pinnedRepos?.forEach((pinnedRepo) => { - const repoFullName = GitHubUtils.toRepoFullName(pinnedRepo.owner, pinnedRepo.name); - const repoTreeItem: NotebookContentItem = { - name: repoFullName, - path: ResourceTreeAdapter.PseudoDirPath, - type: NotebookContentItemType.Directory, - children: [], - }; - - pinnedRepo.branches.forEach((branch) => { - repoTreeItem.children.push({ - name: branch.name, - path: GitHubUtils.toContentUri(pinnedRepo.owner, pinnedRepo.name, branch.name, ""), - type: NotebookContentItemType.Directory, - }); - }); - - this.gitHubNotebooksContentRoot.children.push(repoTreeItem); - }); - - this.triggerRender(); - } + return ; } private buildDataTree(): TreeNode { @@ -504,365 +370,6 @@ export class ResourceTreeAdapter implements ReactAdapter { return traverse(schema); } - private buildNotebooksTrees(): TreeNode { - let notebooksTree: TreeNode = { - label: undefined, - isExpanded: true, - children: [], - }; - - if (this.galleryContentRoot) { - notebooksTree.children.push(this.buildGalleryNotebooksTree()); - } - - if (this.myNotebooksContentRoot) { - notebooksTree.children.push(this.buildMyNotebooksTree()); - } - - if (this.gitHubNotebooksContentRoot) { - // collapse all other notebook nodes - notebooksTree.children.forEach((node) => (node.isExpanded = false)); - notebooksTree.children.push(this.buildGitHubNotebooksTree()); - } - - return notebooksTree; - } - - private buildGalleryCallout(): JSX.Element { - if ( - LocalStorageUtility.hasItem(StorageKey.GalleryCalloutDismissed) && - LocalStorageUtility.getEntryBoolean(StorageKey.GalleryCalloutDismissed) - ) { - return undefined; - } - - const calloutProps: ICalloutProps = { - calloutMaxWidth: 350, - ariaLabel: "New gallery", - role: "alertdialog", - gapSpace: 0, - target: ".galleryHeader", - directionalHint: DirectionalHint.leftTopEdge, - onDismiss: () => { - LocalStorageUtility.setEntryBoolean(StorageKey.GalleryCalloutDismissed, true); - this.triggerRender(); - }, - setInitialFocus: true, - }; - - const openGalleryProps: ILinkProps = { - onClick: () => { - LocalStorageUtility.setEntryBoolean(StorageKey.GalleryCalloutDismissed, true); - this.container.openGallery(); - this.triggerRender(); - }, - }; - - return ( - - - - New gallery - - - Sample notebooks are now combined in gallery. View and try out samples provided by Microsoft and other - contributors. - - Open gallery - - - ); - } - - private buildGalleryNotebooksTree(): TreeNode { - return { - label: "Gallery", - iconSrc: GalleryIcon, - className: "notebookHeader galleryHeader", - onClick: () => this.container.openGallery(), - isSelected: () => { - const activeTab = useTabs.getState().activeTab; - return activeTab && activeTab.tabKind === ViewModels.CollectionTabKind.Gallery; - }, - }; - } - - private buildMyNotebooksTree(): TreeNode { - const myNotebooksTree: TreeNode = this.buildNotebookDirectoryNode( - this.myNotebooksContentRoot, - (item: NotebookContentItem) => { - this.container.openNotebook(item).then((hasOpened) => { - if (hasOpened) { - mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item); - } - }); - }, - true, - true, - ); - - myNotebooksTree.isExpanded = true; - myNotebooksTree.isAlphaSorted = true; - // Remove "Delete" menu item from context menu - myNotebooksTree.contextMenu = myNotebooksTree.contextMenu.filter((menuItem) => menuItem.label !== "Delete"); - return myNotebooksTree; - } - - private buildGitHubNotebooksTree(): TreeNode { - const gitHubNotebooksTree: TreeNode = this.buildNotebookDirectoryNode( - this.gitHubNotebooksContentRoot, - (item: NotebookContentItem) => { - this.container.openNotebook(item).then((hasOpened) => { - if (hasOpened) { - mostRecentActivity.notebookWasItemOpened(userContext.databaseAccount?.id, item); - } - }); - }, - true, - true, - ); - - gitHubNotebooksTree.contextMenu = [ - { - label: "Manage GitHub settings", - onClick: () => - useSidePanel - .getState() - .openSidePanel( - "Manage GitHub settings", - , - ), - }, - { - label: "Disconnect from GitHub", - onClick: () => { - TelemetryProcessor.trace(Action.NotebooksGitHubDisconnect, ActionModifiers.Mark, { - dataExplorerArea: Areas.Notebook, - }); - this.container.notebookManager?.gitHubOAuthService.logout(); - }, - }, - ]; - - gitHubNotebooksTree.isExpanded = true; - gitHubNotebooksTree.isAlphaSorted = true; - - return gitHubNotebooksTree; - } - - private buildChildNodes( - item: NotebookContentItem, - onFileClick: (item: NotebookContentItem) => void, - createDirectoryContextMenu: boolean, - createFileContextMenu: boolean, - ): TreeNode[] { - if (!item || !item.children) { - return []; - } else { - return item.children.map((item) => { - const result = - item.type === NotebookContentItemType.Directory - ? this.buildNotebookDirectoryNode(item, onFileClick, createDirectoryContextMenu, createFileContextMenu) - : this.buildNotebookFileNode(item, onFileClick, createFileContextMenu); - result.timestamp = item.timestamp; - return result; - }); - } - } - - private buildNotebookFileNode( - item: NotebookContentItem, - onFileClick: (item: NotebookContentItem) => void, - createFileContextMenu: boolean, - ): TreeNode { - return { - label: item.name, - iconSrc: NotebookUtil.isNotebookFile(item.path) ? NotebookIcon : FileIcon, - className: "notebookHeader", - onClick: () => onFileClick(item), - isSelected: () => { - const activeTab = useTabs.getState().activeTab; - return ( - activeTab && - activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && - /* TODO Redesign Tab interface so that resource tree doesn't need to know about NotebookV2Tab. - NotebookV2Tab could be dynamically imported, but not worth it to just get this type right. - */ - (activeTab as any).notebookPath() === item.path - ); - }, - contextMenu: createFileContextMenu && this.createFileContextMenu(item), - data: item, - }; - } - - private createFileContextMenu(item: NotebookContentItem): TreeNodeMenuItem[] { - let items: TreeNodeMenuItem[] = [ - { - label: "Rename", - iconSrc: NotebookIcon, - onClick: () => this.container.renameNotebook(item), - }, - { - label: "Delete", - iconSrc: DeleteIcon, - onClick: () => { - useDialog - .getState() - .showOkCancelModalDialog( - "Confirm delete", - `Are you sure you want to delete "${item.name}"`, - "Delete", - () => this.container.deleteNotebookFile(item).then(() => this.triggerRender()), - "Cancel", - undefined, - ); - }, - }, - { - label: "Copy to ...", - iconSrc: CopyIcon, - onClick: () => this.copyNotebook(item), - }, - { - label: "Download", - iconSrc: NotebookIcon, - onClick: () => this.container.downloadFile(item), - }, - ]; - - if (item.type === NotebookContentItemType.Notebook) { - items.push({ - label: "Publish to gallery", - iconSrc: PublishIcon, - onClick: async () => { - TelemetryProcessor.trace(Action.NotebooksGalleryClickPublishToGallery, ActionModifiers.Mark, { - source: Source.ResourceTreeMenu, - }); - - const content = await this.container.readFile(item); - if (content) { - await this.container.publishNotebook(item.name, content); - } - }, - }); - } - - // "Copy to ..." isn't needed if github locations are not available - if (!this.container.notebookManager?.gitHubOAuthService.isLoggedIn()) { - items = items.filter((item) => item.label !== "Copy to ..."); - } - - return items; - } - - private copyNotebook = async (item: NotebookContentItem) => { - const content = await this.container.readFile(item); - if (content) { - this.container.copyNotebook(item.name, content); - } - }; - - private createDirectoryContextMenu(item: NotebookContentItem): TreeNodeMenuItem[] { - let items: TreeNodeMenuItem[] = [ - { - label: "Refresh", - iconSrc: RefreshIcon, - onClick: () => this.container.refreshContentItem(item).then(() => this.triggerRender()), - }, - { - label: "Delete", - iconSrc: DeleteIcon, - onClick: () => { - useDialog - .getState() - .showOkCancelModalDialog( - "Confirm delete", - `Are you sure you want to delete "${item.name}?"`, - "Delete", - () => this.container.deleteNotebookFile(item).then(() => this.triggerRender()), - "Cancel", - undefined, - ); - }, - }, - { - label: "Rename", - iconSrc: NotebookIcon, - onClick: () => this.container.renameNotebook(item), - }, - { - label: "New Directory", - iconSrc: NewNotebookIcon, - onClick: () => this.container.onCreateDirectory(item), - }, - { - label: "Upload File", - iconSrc: NewNotebookIcon, - onClick: () => this.container.openUploadFilePanel(item), - }, - ]; - - //disallow renaming of temporary notebook workspace - if (item?.path === useNotebook.getState().notebookBasePath) { - items = items.filter((item) => item.label !== "Rename"); - } - - // For GitHub paths remove "Delete", "Rename", "New Directory", "Upload File" - if (GitHubUtils.fromContentUri(item.path)) { - items = items.filter( - (item) => - item.label !== "Delete" && - item.label !== "Rename" && - item.label !== "New Directory" && - item.label !== "Upload File", - ); - } - - return items; - } - - private buildNotebookDirectoryNode( - item: NotebookContentItem, - onFileClick: (item: NotebookContentItem) => void, - createDirectoryContextMenu: boolean, - createFileContextMenu: boolean, - ): TreeNode { - return { - label: item.name, - iconSrc: undefined, - className: "notebookHeader", - isAlphaSorted: true, - isLeavesParentsSeparate: true, - onClick: () => { - if (!item.children) { - this.container.refreshContentItem(item).then(() => this.triggerRender()); - } - }, - isSelected: () => { - const activeTab = useTabs.getState().activeTab; - return ( - activeTab && - activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && - /* TODO Redesign Tab interface so that resource tree doesn't need to know about NotebookV2Tab. - NotebookV2Tab could be dynamically imported, but not worth it to just get this type right. - */ - (activeTab as any).notebookPath() === item.path - ); - }, - contextMenu: - createDirectoryContextMenu && item.path !== ResourceTreeAdapter.PseudoDirPath - ? this.createDirectoryContextMenu(item) - : undefined, - data: item, - children: this.buildChildNodes(item, onFileClick, createDirectoryContextMenu, createFileContextMenu), - }; - } - public triggerRender() { window.requestAnimationFrame(() => this.parameters(Date.now())); } diff --git a/src/Utils/GalleryUtils.ts b/src/Utils/GalleryUtils.ts index f11b6df5c..1314b2b3c 100644 --- a/src/Utils/GalleryUtils.ts +++ b/src/Utils/GalleryUtils.ts @@ -245,7 +245,6 @@ export function downloadItem( }, "Cancel", undefined, - container.getDownloadModalConent(name), ); } export async function downloadNotebookItem( @@ -278,7 +277,6 @@ export async function downloadNotebookItem( metadata.untrusted = true; } - await container.importAndOpenContent(data.name, JSON.stringify(notebook)); logConsoleInfo(`Successfully downloaded ${data.name} to ${useNotebook.getState().notebookFolderName}`); const increaseDownloadResponse = await junoClient.increaseNotebookDownloadCount(data.id); From 81a5b7cb6d6011c9be922770581dcfe2021ee80d Mon Sep 17 00:00:00 2001 From: Ashley Stanton-Nurse Date: Tue, 30 Apr 2024 10:03:27 -0700 Subject: [PATCH 15/15] add shortcuts for the Items tab (#1827) * add shortcuts for the Items tab * Add shortcut to clear Items tab filter. --- src/Explorer/Tabs/DocumentsTab.html | 3 ++- src/Explorer/Tabs/DocumentsTab.ts | 33 ++++++++++++++++++++++++++++- src/Explorer/Tabs/TabsBase.ts | 2 ++ src/KeyboardShortcuts.tsx | 28 ++++++++++++++++++++++-- 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/Explorer/Tabs/DocumentsTab.html b/src/Explorer/Tabs/DocumentsTab.html index 4283a661c..cfe1b2039 100644 --- a/src/Explorer/Tabs/DocumentsTab.html +++ b/src/Explorer/Tabs/DocumentsTab.html @@ -80,7 +80,8 @@ placeholder:isPreferredApiMongoDB?'Type a query predicate (e.g., {´a´:´foo´}), or choose one from the drop down list, or leave empty to query all documents.':'Type a query predicate (e.g., WHERE c.id=´1´), or choose one from the drop down list, or leave empty to query all documents.' }, css: { placeholderVisible: filterContent().length === 0 }, - textInput: filterContent" + textInput: filterContent, + event: { keydown: onFilterKeyDown }" /> { super.onActivate(); + this.setKeyboardActions({ + [KeyboardAction.SEARCH]: () => { + this.onShowFilterClick(); + return true; + }, + [KeyboardAction.CLEAR_SEARCH]: () => { + this.filterContent(""); + this.refreshDocumentsGrid(true); + return true; + }, + }); + if (!this._documentsIterator) { try { await this.autoPopulateContent(); diff --git a/src/Explorer/Tabs/TabsBase.ts b/src/Explorer/Tabs/TabsBase.ts index 0425eac91..8b017f6bc 100644 --- a/src/Explorer/Tabs/TabsBase.ts +++ b/src/Explorer/Tabs/TabsBase.ts @@ -1,3 +1,4 @@ +import { KeyboardActionGroup, clearKeyboardActionGroup } from "KeyboardShortcuts"; import * as ko from "knockout"; import * as Constants from "../../Common/Constants"; import * as ThemeUtility from "../../Common/ThemeUtility"; @@ -107,6 +108,7 @@ export default class TabsBase extends WaitsForTemplateViewModel { } public onActivate(): void { + clearKeyboardActionGroup(KeyboardActionGroup.ACTIVE_TAB); this.updateSelectedNode(); this.collection?.selectedSubnodeKind(this.tabKind); this.database?.selectedSubnodeKind(this.tabKind); diff --git a/src/KeyboardShortcuts.tsx b/src/KeyboardShortcuts.tsx index 98f988038..2041662ee 100644 --- a/src/KeyboardShortcuts.tsx +++ b/src/KeyboardShortcuts.tsx @@ -17,8 +17,17 @@ export type KeyboardHandlerMap = Partial = { [KeyboardAction.SELECT_LEFT_TAB]: ["$mod+Alt+[", "$mod+Shift+F6"], [KeyboardAction.SELECT_RIGHT_TAB]: ["$mod+Alt+]", "$mod+F6"], [KeyboardAction.CLOSE_TAB]: ["$mod+Alt+W"], + [KeyboardAction.SEARCH]: ["$mod+Shift+F"], + [KeyboardAction.CLEAR_SEARCH]: ["$mod+Shift+C"], }; interface KeyboardShortcutState { @@ -91,13 +104,24 @@ interface KeyboardShortcutState { setHandlers: (group: KeyboardActionGroup, handlers: KeyboardHandlerMap) => void; } +export type KeyboardHandlerSetter = (handlers: KeyboardHandlerMap) => void; + /** * Defines the calling component as the manager of the keyboard actions for the given group. * @param group The group of keyboard actions to manage. * @returns A function that can be used to set the keyboard action handlers for the given group. */ -export const useKeyboardActionGroup = (group: KeyboardActionGroup) => (handlers: KeyboardHandlerMap) => - useKeyboardActionHandlers.getState().setHandlers(group, handlers); +export const useKeyboardActionGroup: (group: KeyboardActionGroup) => KeyboardHandlerSetter = + (group: KeyboardActionGroup) => (handlers: KeyboardHandlerMap) => + useKeyboardActionHandlers.getState().setHandlers(group, handlers); + +/** + * Clears the keyboard action handlers for the given group. + * @param group The group of keyboard actions to clear. + */ +export const clearKeyboardActionGroup = (group: KeyboardActionGroup) => { + useKeyboardActionHandlers.getState().setHandlers(group, {}); +}; const useKeyboardActionHandlers: UseStore = create((set, get) => ({ allHandlers: {},