From 2a54fed25bc8fc7db8c52e6a7bf4b267bb5ee1b4 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Tue, 17 May 2022 15:18:10 -0400 Subject: [PATCH 01/26] convert to ts --- packages/code-studio/src/styleguide/Buttons.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/code-studio/src/styleguide/Buttons.tsx b/packages/code-studio/src/styleguide/Buttons.tsx index 250820ef1b..6064de6942 100644 --- a/packages/code-studio/src/styleguide/Buttons.tsx +++ b/packages/code-studio/src/styleguide/Buttons.tsx @@ -4,10 +4,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ButtonOld, SocketedButton } from '@deephaven/components'; import { dhTruck } from '@deephaven/icons'; +interface ButtonsProps {} interface ButtonsState { toggle: boolean; } -class Buttons extends Component, ButtonsState> { +class Buttons extends Component { static renderButtonBrand(type: string, brand: string): ReactElement { const className = type.length ? `btn-${type}-${brand}` : `btn-${brand}`; return ( @@ -74,7 +75,7 @@ class Buttons extends Component, ButtonsState> { ); } - constructor(props: Record) { + constructor(props: ButtonsProps) { super(props); this.state = { From 2ee20e2cf12cf08785a60b90cfdbe5cdcaea1e4c Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Wed, 18 May 2022 16:34:36 -0400 Subject: [PATCH 02/26] convert js to ts in styleguide --- .../code-studio/src/storage/WorkspaceStorage.ts | 4 ++++ packages/code-studio/src/styleguide/Buttons.tsx | 9 ++++----- packages/code-studio/src/styleguide/Dialog.tsx | 2 +- .../code-studio/src/styleguide/DropdownMenus.tsx | 13 +++++++++++-- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/packages/code-studio/src/storage/WorkspaceStorage.ts b/packages/code-studio/src/storage/WorkspaceStorage.ts index 96db17b411..834f052134 100644 --- a/packages/code-studio/src/storage/WorkspaceStorage.ts +++ b/packages/code-studio/src/storage/WorkspaceStorage.ts @@ -1,4 +1,8 @@ +<<<<<<< HEAD import { FormattingRule } from '@deephaven/jsapi-utils'; +======= +import { FormattingRule } from '@deephaven/iris-grid'; +>>>>>>> d211793 (convert js to ts in styleguide) export type WorkspaceSettings = { defaultDateTimeFormat: string; diff --git a/packages/code-studio/src/styleguide/Buttons.tsx b/packages/code-studio/src/styleguide/Buttons.tsx index 6064de6942..bf44c0d221 100644 --- a/packages/code-studio/src/styleguide/Buttons.tsx +++ b/packages/code-studio/src/styleguide/Buttons.tsx @@ -4,11 +4,10 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { ButtonOld, SocketedButton } from '@deephaven/components'; import { dhTruck } from '@deephaven/icons'; -interface ButtonsProps {} interface ButtonsState { toggle: boolean; } -class Buttons extends Component { +class Buttons extends Component, ButtonsState> { static renderButtonBrand(type: string, brand: string): ReactElement { const className = type.length ? `btn-${type}-${brand}` : `btn-${brand}`; return ( @@ -39,7 +38,7 @@ class Buttons extends Component { ); } - static renderSocketedButtons(): ReactElement { + static renderSocketedButtons(): React.ReactElement { return (
Socketed Buttons (for linker)
@@ -75,7 +74,7 @@ class Buttons extends Component { ); } - constructor(props: ButtonsProps) { + constructor(props: Record) { super(props); this.state = { @@ -83,7 +82,7 @@ class Buttons extends Component { }; } - renderInlineButtons(): ReactElement { + renderInlineButtons(): React.ReactElement { const { toggle } = this.state; return ( diff --git a/packages/code-studio/src/styleguide/Dialog.tsx b/packages/code-studio/src/styleguide/Dialog.tsx index 1979e46999..8eb0c6c9ac 100644 --- a/packages/code-studio/src/styleguide/Dialog.tsx +++ b/packages/code-studio/src/styleguide/Dialog.tsx @@ -9,7 +9,7 @@ import { interface DialogState { isShown: boolean; - checkBoxMap: HierarchicalCheckboxValueMap; + checkBoxMap: Map>; } interface DialogProps { diff --git a/packages/code-studio/src/styleguide/DropdownMenus.tsx b/packages/code-studio/src/styleguide/DropdownMenus.tsx index 44d7fbf3ae..be0690a9ef 100644 --- a/packages/code-studio/src/styleguide/DropdownMenus.tsx +++ b/packages/code-studio/src/styleguide/DropdownMenus.tsx @@ -17,6 +17,7 @@ import { vsKebabVertical, vsQuestion, } from '@deephaven/icons'; +import { IconDefinition } from '@fortawesome/fontawesome-svg-core'; interface DropdownMenus { button: React.RefObject; @@ -26,6 +27,14 @@ interface DropdownMenusState { isShown: boolean; } +interface Actions { + title: string; + icon: IconDefinition; + action: () => void; + shortcut: Shortcut; + group?: number; +} + class DropdownMenus extends Component< Record, DropdownMenusState @@ -66,7 +75,7 @@ class DropdownMenus extends Component< macShortcut: [MODIFIER.CMD, KEY.L], }), }, - ] as DropdownAction[]; + ] as Actions[]; const globalActions = [ { @@ -83,7 +92,7 @@ class DropdownMenus extends Component< }), group: ContextActions.groups.global, }, - ] as DropdownAction[]; + ] as Actions[]; const actions = globalActions.concat(contextActions); From dfaeb9128e4783a8f17a8cc85e3d11bc3d438a88 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Thu, 26 May 2022 16:12:23 -0400 Subject: [PATCH 03/26] added requested changes --- packages/code-studio/src/styleguide/Buttons.tsx | 4 ++-- packages/code-studio/src/styleguide/Dialog.tsx | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/packages/code-studio/src/styleguide/Buttons.tsx b/packages/code-studio/src/styleguide/Buttons.tsx index bf44c0d221..250820ef1b 100644 --- a/packages/code-studio/src/styleguide/Buttons.tsx +++ b/packages/code-studio/src/styleguide/Buttons.tsx @@ -38,7 +38,7 @@ class Buttons extends Component, ButtonsState> { ); } - static renderSocketedButtons(): React.ReactElement { + static renderSocketedButtons(): ReactElement { return (
Socketed Buttons (for linker)
@@ -82,7 +82,7 @@ class Buttons extends Component, ButtonsState> { }; } - renderInlineButtons(): React.ReactElement { + renderInlineButtons(): ReactElement { const { toggle } = this.state; return ( diff --git a/packages/code-studio/src/styleguide/Dialog.tsx b/packages/code-studio/src/styleguide/Dialog.tsx index 8eb0c6c9ac..cf5f0cd79f 100644 --- a/packages/code-studio/src/styleguide/Dialog.tsx +++ b/packages/code-studio/src/styleguide/Dialog.tsx @@ -1,15 +1,12 @@ /* eslint no-alert: "off" */ /* eslint no-console: "off" */ import React, { Component } from 'react'; -import { - HierarchicalCheckboxMenu, - Popper, - HierarchicalCheckboxValueMap, -} from '@deephaven/components'; +import { HierarchicalCheckboxMenu, Popper } from '@deephaven/components'; +import { HierarchicalCheckboxValueMap } from '@deephaven/components/src/HierarchicalCheckboxMenu'; interface DialogState { isShown: boolean; - checkBoxMap: Map>; + checkBoxMap: HierarchicalCheckboxValueMap; } interface DialogProps { From e1fc2378501e67cd106694a9b3a9a07cb78571e1 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Fri, 27 May 2022 17:05:53 -0400 Subject: [PATCH 04/26] finished fixing requested changes --- .../code-studio/src/styleguide/Dialog.tsx | 7 +++++-- .../src/styleguide/DropdownMenus.tsx | 19 +++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/code-studio/src/styleguide/Dialog.tsx b/packages/code-studio/src/styleguide/Dialog.tsx index cf5f0cd79f..1979e46999 100644 --- a/packages/code-studio/src/styleguide/Dialog.tsx +++ b/packages/code-studio/src/styleguide/Dialog.tsx @@ -1,8 +1,11 @@ /* eslint no-alert: "off" */ /* eslint no-console: "off" */ import React, { Component } from 'react'; -import { HierarchicalCheckboxMenu, Popper } from '@deephaven/components'; -import { HierarchicalCheckboxValueMap } from '@deephaven/components/src/HierarchicalCheckboxMenu'; +import { + HierarchicalCheckboxMenu, + Popper, + HierarchicalCheckboxValueMap, +} from '@deephaven/components'; interface DialogState { isShown: boolean; diff --git a/packages/code-studio/src/styleguide/DropdownMenus.tsx b/packages/code-studio/src/styleguide/DropdownMenus.tsx index be0690a9ef..d47879aae2 100644 --- a/packages/code-studio/src/styleguide/DropdownMenus.tsx +++ b/packages/code-studio/src/styleguide/DropdownMenus.tsx @@ -17,7 +17,6 @@ import { vsKebabVertical, vsQuestion, } from '@deephaven/icons'; -import { IconDefinition } from '@fortawesome/fontawesome-svg-core'; interface DropdownMenus { button: React.RefObject; @@ -27,13 +26,13 @@ interface DropdownMenusState { isShown: boolean; } -interface Actions { - title: string; - icon: IconDefinition; - action: () => void; - shortcut: Shortcut; - group?: number; -} +// interface Actions { +// title: string; +// icon: IconDefinition; +// action: () => void; +// shortcut: Shortcut; +// group?: number; +// } class DropdownMenus extends Component< Record, @@ -75,7 +74,7 @@ class DropdownMenus extends Component< macShortcut: [MODIFIER.CMD, KEY.L], }), }, - ] as Actions[]; + ] as DropdownAction[]; const globalActions = [ { @@ -92,7 +91,7 @@ class DropdownMenus extends Component< }), group: ContextActions.groups.global, }, - ] as Actions[]; + ] as DropdownAction[]; const actions = globalActions.concat(contextActions); From 6c29525dd55762a30bc5b1e089edbf9b5a1226c0 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Wed, 8 Jun 2022 11:04:22 -0400 Subject: [PATCH 05/26] fixed merge conflicts --- packages/code-studio/src/styleguide/DropdownMenus.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/code-studio/src/styleguide/DropdownMenus.tsx b/packages/code-studio/src/styleguide/DropdownMenus.tsx index d47879aae2..317f848161 100644 --- a/packages/code-studio/src/styleguide/DropdownMenus.tsx +++ b/packages/code-studio/src/styleguide/DropdownMenus.tsx @@ -26,6 +26,7 @@ interface DropdownMenusState { isShown: boolean; } +<<<<<<< HEAD // interface Actions { // title: string; // icon: IconDefinition; @@ -34,6 +35,8 @@ interface DropdownMenusState { // group?: number; // } +======= +>>>>>>> 797bd35 (fixed merge conflicts) class DropdownMenus extends Component< Record, DropdownMenusState From 832f145ea6729fc72a306dedde32ffe89ed335d5 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Wed, 29 Jun 2022 13:37:10 -0400 Subject: [PATCH 06/26] fixed meerge errors --- packages/code-studio/src/storage/WorkspaceStorage.ts | 4 ---- packages/code-studio/src/styleguide/DropdownMenus.tsx | 11 ----------- 2 files changed, 15 deletions(-) diff --git a/packages/code-studio/src/storage/WorkspaceStorage.ts b/packages/code-studio/src/storage/WorkspaceStorage.ts index 834f052134..96db17b411 100644 --- a/packages/code-studio/src/storage/WorkspaceStorage.ts +++ b/packages/code-studio/src/storage/WorkspaceStorage.ts @@ -1,8 +1,4 @@ -<<<<<<< HEAD import { FormattingRule } from '@deephaven/jsapi-utils'; -======= -import { FormattingRule } from '@deephaven/iris-grid'; ->>>>>>> d211793 (convert js to ts in styleguide) export type WorkspaceSettings = { defaultDateTimeFormat: string; diff --git a/packages/code-studio/src/styleguide/DropdownMenus.tsx b/packages/code-studio/src/styleguide/DropdownMenus.tsx index 317f848161..44d7fbf3ae 100644 --- a/packages/code-studio/src/styleguide/DropdownMenus.tsx +++ b/packages/code-studio/src/styleguide/DropdownMenus.tsx @@ -26,17 +26,6 @@ interface DropdownMenusState { isShown: boolean; } -<<<<<<< HEAD -// interface Actions { -// title: string; -// icon: IconDefinition; -// action: () => void; -// shortcut: Shortcut; -// group?: number; -// } - -======= ->>>>>>> 797bd35 (fixed merge conflicts) class DropdownMenus extends Component< Record, DropdownMenusState From e719ba9c5ebd52b434e5adf974fb55eca034d7cc Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Mon, 11 Jul 2022 09:53:40 -0400 Subject: [PATCH 07/26] WIP console tsx conversion --- package-lock.json | 34 ++ package.json | 4 +- packages/components/src/shortcuts/Shortcut.ts | 2 +- packages/console/package.json | 1 + .../console/src/{Console.jsx => Console.tsx} | 385 +++++++++++------- .../{ConsoleInput.jsx => ConsoleInput.tsx} | 240 ++++++----- .../src/{ConsoleMenu.jsx => ConsoleMenu.tsx} | 151 ++++--- ...onsolePropTypes.js => ConsolePropTypes.ts} | 0 ...soleStatusBar.jsx => ConsoleStatusBar.tsx} | 68 ++-- ...toragePropTypes.js => StoragePropTypes.ts} | 0 ...{CommandHistory.jsx => CommandHistory.tsx} | 134 ++++-- ...yActions.jsx => CommandHistoryActions.tsx} | 42 +- ...HistoryItem.jsx => CommandHistoryItem.tsx} | 26 +- ...ltip.jsx => CommandHistoryItemTooltip.tsx} | 101 +++-- .../command-history/{index.jsx => index.tsx} | 0 .../console/src/common/{Code.jsx => Code.tsx} | 25 +- .../console/src/common/ConsoleUtils.test.js | 5 + .../{ConsoleUtils.js => ConsoleUtils.ts} | 51 ++- .../src/console-history/ConsoleHistory.jsx | 51 --- .../src/console-history/ConsoleHistory.tsx | 76 ++++ ...HistoryItem.jsx => ConsoleHistoryItem.tsx} | 70 ++-- ...esult.jsx => ConsoleHistoryItemResult.tsx} | 8 +- ...x => ConsoleHistoryResultErrorMessage.tsx} | 64 ++- ...jsx => ConsoleHistoryResultInProgress.tsx} | 43 +- .../console-history/{index.jsx => index.tsx} | 0 .../src/csv/{CsvFormats.js => CsvFormats.ts} | 22 +- .../csv/{CsvInputBar.jsx => CsvInputBar.tsx} | 111 ++--- .../csv/{CsvOverlay.jsx => CsvOverlay.tsx} | 114 ++++-- .../src/csv/{CsvParser.js => CsvParser.ts} | 124 ++++-- .../{CsvTypeParser.js => CsvTypeParser.ts} | 114 ++++-- ...eColumnTypes.js => NewTableColumnTypes.ts} | 0 packages/console/src/{index.js => index.ts} | 0 .../src/log/{LogLevel.js => LogLevel.ts} | 0 ...LevelMenuItem.jsx => LogLevelMenuItem.tsx} | 26 +- .../src/log/{LogView.jsx => LogView.tsx} | 170 ++++---- ...vider.jsx => MonacoCompletionProvider.tsx} | 47 ++- .../monaco/{MonacoUtils.js => MonacoUtils.ts} | 82 ++-- .../console/src/monaco/{index.js => index.ts} | 0 .../console/src/monaco/lang/{db.js => db.ts} | 0 .../src/monaco/lang/{groovy.js => groovy.ts} | 0 .../src/monaco/lang/{log.js => log.ts} | 0 .../src/monaco/lang/{python.js => python.ts} | 0 .../src/monaco/lang/{scala.js => scala.ts} | 0 .../src/notebook/{Editor.jsx => Editor.tsx} | 77 ++-- .../console/src/notebook/ScriptEditorUtils.js | 36 -- .../console/src/notebook/ScriptEditorUtils.ts | 40 ++ packages/jsapi-shim/src/dh.types.ts | 69 +++- 47 files changed, 1633 insertions(+), 980 deletions(-) rename packages/console/src/{Console.jsx => Console.tsx} (73%) rename packages/console/src/{ConsoleInput.jsx => ConsoleInput.tsx} (66%) rename packages/console/src/{ConsoleMenu.jsx => ConsoleMenu.tsx} (63%) rename packages/console/src/{ConsolePropTypes.js => ConsolePropTypes.ts} (100%) rename packages/console/src/{ConsoleStatusBar.jsx => ConsoleStatusBar.tsx} (67%) rename packages/console/src/{StoragePropTypes.js => StoragePropTypes.ts} (100%) rename packages/console/src/command-history/{CommandHistory.jsx => CommandHistory.tsx} (77%) rename packages/console/src/command-history/{CommandHistoryActions.jsx => CommandHistoryActions.tsx} (63%) rename packages/console/src/command-history/{CommandHistoryItem.jsx => CommandHistoryItem.tsx} (72%) rename packages/console/src/command-history/{CommandHistoryItemTooltip.jsx => CommandHistoryItemTooltip.tsx} (65%) rename packages/console/src/command-history/{index.jsx => index.tsx} (100%) rename packages/console/src/common/{Code.jsx => Code.tsx} (70%) rename packages/console/src/common/{ConsoleUtils.js => ConsoleUtils.ts} (54%) delete mode 100644 packages/console/src/console-history/ConsoleHistory.jsx create mode 100644 packages/console/src/console-history/ConsoleHistory.tsx rename packages/console/src/console-history/{ConsoleHistoryItem.jsx => ConsoleHistoryItem.tsx} (71%) rename packages/console/src/console-history/{ConsoleHistoryItemResult.jsx => ConsoleHistoryItemResult.tsx} (71%) rename packages/console/src/console-history/{ConsoleHistoryResultErrorMessage.jsx => ConsoleHistoryResultErrorMessage.tsx} (79%) rename packages/console/src/console-history/{ConsoleHistoryResultInProgress.jsx => ConsoleHistoryResultInProgress.tsx} (68%) rename packages/console/src/console-history/{index.jsx => index.tsx} (100%) rename packages/console/src/csv/{CsvFormats.js => CsvFormats.ts} (83%) rename packages/console/src/csv/{CsvInputBar.jsx => CsvInputBar.tsx} (81%) rename packages/console/src/csv/{CsvOverlay.jsx => CsvOverlay.tsx} (75%) rename packages/console/src/csv/{CsvParser.js => CsvParser.ts} (70%) rename packages/console/src/csv/{CsvTypeParser.js => CsvTypeParser.ts} (71%) rename packages/console/src/csv/{NewTableColumnTypes.js => NewTableColumnTypes.ts} (100%) rename packages/console/src/{index.js => index.ts} (100%) rename packages/console/src/log/{LogLevel.js => LogLevel.ts} (100%) rename packages/console/src/log/{LogLevelMenuItem.jsx => LogLevelMenuItem.tsx} (63%) rename packages/console/src/log/{LogView.jsx => LogView.tsx} (75%) rename packages/console/src/monaco/{MonacoCompletionProvider.jsx => MonacoCompletionProvider.tsx} (75%) rename packages/console/src/monaco/{MonacoUtils.js => MonacoUtils.ts} (88%) rename packages/console/src/monaco/{index.js => index.ts} (100%) rename packages/console/src/monaco/lang/{db.js => db.ts} (100%) rename packages/console/src/monaco/lang/{groovy.js => groovy.ts} (100%) rename packages/console/src/monaco/lang/{log.js => log.ts} (100%) rename packages/console/src/monaco/lang/{python.js => python.ts} (100%) rename packages/console/src/monaco/lang/{scala.js => scala.ts} (100%) rename packages/console/src/notebook/{Editor.jsx => Editor.tsx} (62%) delete mode 100644 packages/console/src/notebook/ScriptEditorUtils.js create mode 100644 packages/console/src/notebook/ScriptEditorUtils.ts diff --git a/package-lock.json b/package-lock.json index 34e6393938..aa1b123188 100644 --- a/package-lock.json +++ b/package-lock.json @@ -72,6 +72,7 @@ "@types/lodash.throttle": "^4.1.1", "@types/memoizee": "^0.4.5", "@types/node": "^16.11.7", + "@types/papaparse": "^5.3.2", "@types/pouchdb-browser": "^6.1.3", "@types/prop-types": "^15.7.3", "@types/react": "^17.0.2", @@ -82,6 +83,7 @@ "@types/react-transition-group": "^4.4.0", "@types/react-virtualized-auto-sizer": "^1.0.1", "@types/react-window": "^1.8.5", + "@types/shell-quote": "^1.7.1", "@types/shortid": "0.0.29", "@vscode/codicons": "0.0.25", "babel-eslint": "^10.1.0", @@ -6997,6 +6999,15 @@ "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" }, + "node_modules/@types/papaparse": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.2.tgz", + "integrity": "sha512-BNbCHJkTE4RwmAFkCxEalET4mDvGr/1ld7ZtQ4i/laWI/iiVt+GL07stdvufle4KfywyvloqqpIiJscXNCrKxA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -7204,6 +7215,12 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, + "node_modules/@types/shell-quote": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@types/shell-quote/-/shell-quote-1.7.1.tgz", + "integrity": "sha512-SWZ2Nom1pkyXCDohRSrkSKvDh8QOG9RfAsrt5/NsPQC4UQJ55eG0qClA40I+Gkez4KTQ0uDUT8ELRXThf3J5jw==", + "dev": true + }, "node_modules/@types/shortid": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", @@ -39227,6 +39244,7 @@ "memoizee": "^0.4.15", "monaco-editor": "^0.27.0", "papaparse": "^5.2.0", + "popper.js": "^1.16.1", "prop-types": "^15.7.2", "shell-quote": "^1.7.2" }, @@ -41028,6 +41046,7 @@ "memoizee": "^0.4.15", "monaco-editor": "^0.27.0", "papaparse": "^5.2.0", + "popper.js": "^1.16.1", "prop-types": "^15.7.2", "shell-quote": "^1.7.2" } @@ -45079,6 +45098,15 @@ "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==" }, + "@types/papaparse": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.2.tgz", + "integrity": "sha512-BNbCHJkTE4RwmAFkCxEalET4mDvGr/1ld7ZtQ4i/laWI/iiVt+GL07stdvufle4KfywyvloqqpIiJscXNCrKxA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -45286,6 +45314,12 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" }, + "@types/shell-quote": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@types/shell-quote/-/shell-quote-1.7.1.tgz", + "integrity": "sha512-SWZ2Nom1pkyXCDohRSrkSKvDh8QOG9RfAsrt5/NsPQC4UQJ55eG0qClA40I+Gkez4KTQ0uDUT8ELRXThf3J5jw==", + "dev": true + }, "@types/shortid": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", diff --git a/package.json b/package.json index f991445188..f39e52dfa0 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "build:profile": "lerna run build --stream --profile", "build:necessary": "lerna run build --scope=@deephaven/{icons,golden-layout}", "prestart": "lerna run prestart --stream", - "start": "npm-run-all types --parallel watch:types start:*", + "start": "npm-run-all --parallel watch:types start:*", "start:app": "lerna run start --scope=@deephaven/code-studio --stream", "start:embed-grid": "lerna run start --scope=@deephaven/embed-grid --stream", "start:packages": "lerna run watch --ignore=@deephaven/{code-studio,embed-grid} --stream --parallel", @@ -76,6 +76,7 @@ "@types/lodash.throttle": "^4.1.1", "@types/memoizee": "^0.4.5", "@types/node": "^16.11.7", + "@types/papaparse": "^5.3.2", "@types/pouchdb-browser": "^6.1.3", "@types/prop-types": "^15.7.3", "@types/react": "^17.0.2", @@ -86,6 +87,7 @@ "@types/react-transition-group": "^4.4.0", "@types/react-virtualized-auto-sizer": "^1.0.1", "@types/react-window": "^1.8.5", + "@types/shell-quote": "^1.7.1", "@types/shortid": "0.0.29", "@vscode/codicons": "0.0.25", "babel-eslint": "^10.1.0", diff --git a/packages/components/src/shortcuts/Shortcut.ts b/packages/components/src/shortcuts/Shortcut.ts index 0cd43b3bb8..57618a9608 100644 --- a/packages/components/src/shortcuts/Shortcut.ts +++ b/packages/components/src/shortcuts/Shortcut.ts @@ -145,7 +145,7 @@ export default class Shortcut extends EventTarget { private readonly defaultKeyState: ValidKeyState; - private keyState: ValidKeyState; + keyState: ValidKeyState; static NULL_KEY_STATE: ValidKeyState = { metaKey: false, diff --git a/packages/console/package.json b/packages/console/package.json index 71e666edc3..2053e85fa6 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -44,6 +44,7 @@ "memoizee": "^0.4.15", "monaco-editor": "^0.27.0", "papaparse": "^5.2.0", + "popper.js": "^1.16.1", "prop-types": "^15.7.2", "shell-quote": "^1.7.2" }, diff --git a/packages/console/src/Console.jsx b/packages/console/src/Console.tsx similarity index 73% rename from packages/console/src/Console.jsx rename to packages/console/src/Console.tsx index bbccc35027..9b52923e22 100644 --- a/packages/console/src/Console.jsx +++ b/packages/console/src/Console.tsx @@ -1,17 +1,30 @@ /** * Console display for use in the Iris environment. */ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; -import { ContextActions } from '@deephaven/components'; +import React, { + DragEvent, + PureComponent, + ReactElement, + ReactNode, + RefObject, +} from 'react'; +import { ContextActions, DropdownAction } from '@deephaven/components'; import { vsCheck } from '@deephaven/icons'; import classNames from 'classnames'; import memoize from 'memoize-one'; import throttle from 'lodash.throttle'; -import dh, { PropTypes as APIPropTypes } from '@deephaven/jsapi-shim'; +import type { JSZipObject } from 'jszip'; +import dh, { + IdeSession, + LogItem, + VariableChanges, + VariableDefinition, +} from '@deephaven/jsapi-shim'; import Log from '@deephaven/log'; -import { Pending } from '@deephaven/utils'; -import ConsoleHistory from './console-history/ConsoleHistory'; +import { assertNotNull, Pending } from '@deephaven/utils'; +import ConsoleHistory, { + ConsoleHistoryActionItem, +} from './console-history/ConsoleHistory'; import SHORTCUTS from './ConsoleShortcuts'; import LogLevel from './log/LogLevel'; import ConsoleInput from './ConsoleInput'; @@ -19,25 +32,102 @@ import CsvOverlay from './csv/CsvOverlay'; import CsvInputBar from './csv/CsvInputBar'; import './Console.scss'; import ConsoleStatusBar from './ConsoleStatusBar'; -import StoragePropTypes from './StoragePropTypes'; +import { + CommandHistoryStorage, + CommandHistoryStorageItem, +} from './command-history'; const log = Log.module('Console'); -const DEFAULT_SETTINGS = { +interface Settings { + isAutoLaunchPanelsEnabled: boolean; + isPrintStdOutEnabled: boolean; + isClosePanelsOnDisconnectEnabled: boolean; +} + +const DEFAULT_SETTINGS: Settings = { isAutoLaunchPanelsEnabled: true, isPrintStdOutEnabled: true, isClosePanelsOnDisconnectEnabled: true, -}; +} as const; + +interface ConsoleProps { + statusBarChildren: ReactNode; + settings: Partial; + focusCommandHistory: () => void; + openObject: (object: VariableDefinition) => void; + closeObject: (object: VariableDefinition) => void; + session: IdeSession; + language: string; + commandHistoryStorage: CommandHistoryStorage; + onSettingsChange: (settings: Record) => void; + scope: string; + actions: DropdownAction[]; + timeZone: string; + + // Children shown at the bottom of the console history + historyChildren: ReactNode; + + // Known object map + objectMap: Map; + + disabled: boolean; + + /** + * Function to unzip a zip file. If not provided, zip files will not be accepted + * (file:File) => Promise + */ + unzip: (file: File) => Promise; +} + +interface ConsoleState { + // Need separate histories as console history has stdout/stderr output + consoleHistory: ConsoleHistoryActionItem[]; + + // Height of the viewport of the console input and history + consoleHeight: number; + + isScrollDecorationShown: boolean; + + // Location of objects in the console history + objectHistoryMap: Map; + + // The object definitions, name/type + objectMap: Map; + + showCsvOverlay: boolean; + csvFile: File | null; + csvPaste: string | null; + dragError: string | null; + csvUploadInProgress: boolean; + isAutoLaunchPanelsEnabled: boolean; + isPrintStdOutEnabled: boolean; + isClosePanelsOnDisconnectEnabled: boolean; +} +export class Console extends PureComponent { + static defaultProps = { + statusBarChildren: null, + settings: {}, + onSettingsChange: (): void => undefined, + scope: null, + actions: [], + historyChildren: null, + timeZone: 'America/New_York', + objectMap: new Map(), + disabled: false, + unzip: null, + }; -export class Console extends PureComponent { static LOG_THROTTLE = 500; /** * Check if the provided log level is an error type - * @param {LogLevel} logLevel The LogLevel being checked - * @returns {boolean} true if the log level is an error level log + * @param logLevel The LogLevel being checked + * @returns= true if the log level is an error level log */ - static isErrorLevel(logLevel) { + static isErrorLevel( + logLevel: typeof LogLevel[keyof typeof LogLevel] + ): boolean { return ( logLevel === LogLevel.STDERR || logLevel === LogLevel.ERROR || @@ -47,16 +137,16 @@ export class Console extends PureComponent { /** * Check if the provided log level is output level - * @param {LogLevel} logLevel The LogLevel being checked - * @returns {boolean} true if the log level should be output to the console + * @para logLevel The LogLevel being checked + * @return true if the log level should be output to the console */ - static isOutputLevel(logLevel) { + static isOutputLevel(logLevel: string): boolean { // We want all errors to be output, in addition to STDOUT. // That way the user is more likely to see them. return logLevel === LogLevel.STDOUT || Console.isErrorLevel(logLevel); } - constructor(props) { + constructor(props: ConsoleProps) { super(props); this.handleCommandResult = this.handleCommandResult.bind(this); @@ -74,10 +164,6 @@ export class Console extends PureComponent { this ); this.handleTogglePrintStdout = this.handleTogglePrintStdout.bind(this); - this.processLogMessageQueue = throttle( - this.processLogMessageQueue.bind(this), - Console.LOG_THROTTLE - ); this.handleUploadCsv = this.handleUploadCsv.bind(this); this.handleHideCsv = this.handleHideCsv.bind(this); this.handleCsvFileOpened = this.handleCsvFileOpened.bind(this); @@ -90,7 +176,7 @@ export class Console extends PureComponent { this.handleCsvError = this.handleCsvError.bind(this); this.handleCsvInProgress = this.handleCsvInProgress.bind(this); - this.cancelListener = null; + this.cancelListener = undefined; this.consolePane = React.createRef(); this.consoleInput = React.createRef(); this.consoleHistoryScrollPane = React.createRef(); @@ -125,7 +211,7 @@ export class Console extends PureComponent { }; } - componentDidMount() { + componentDidMount(): void { this.initConsoleLogging(); const { session } = this.props; @@ -137,7 +223,7 @@ export class Console extends PureComponent { this.updateDimensions(); } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(prevProps: ConsoleProps, prevState: ConsoleState): void { const { props, state } = this; this.sendSettingsChange(prevState, state); @@ -146,7 +232,7 @@ export class Console extends PureComponent { } } - componentWillUnmount() { + componentWillUnmount(): void { const { session } = this.props; session.removeEventListener( @@ -160,34 +246,44 @@ export class Console extends PureComponent { this.deinitConsoleLogging(); } - initConsoleLogging() { + cancelListener?: () => void; + + consolePane: RefObject; + + consoleInput: RefObject; + + consoleHistoryScrollPane: RefObject; + + pending: Pending; + + queuedLogMessages: ConsoleHistoryActionItem[]; + + initConsoleLogging(): void { const { session } = this.props; this.cancelListener = session.onLogMessage(this.handleLogMessage); } - deinitConsoleLogging() { + deinitConsoleLogging(): void { if (this.cancelListener != null) { this.cancelListener(); - this.cancelListener = null; + this.cancelListener = undefined; } } - handleClearShortcut(event) { + handleClearShortcut(event: CustomEvent): void { event.preventDefault(); event.stopPropagation(); - this.consoleInput.current.clear(); + this.consoleInput.current?.clear(); } - handleCommandStarted(event) { + handleCommandStarted(event: CustomEvent): void { const { code, result } = event.detail; const wrappedResult = this.pending.add(result); const historyItem = { command: code, - result: null, disabledObjects: [], startTime: Date.now(), - endTime: null, cancelResult: () => { result.cancel(); }, @@ -202,8 +298,6 @@ export class Console extends PureComponent { { command: code, startTime: new Date().toJSON(), - endTime: null, - result: null, } ); workspaceItemPromise.catch(err => { @@ -228,10 +322,20 @@ export class Console extends PureComponent { }); } - handleCommandResult(result, historyItemParam, workspaceItemPromise) { + handleCommandResult( + result: + | { + message: string; + error: unknown; + changes: VariableChanges; + } + | undefined, + historyItemParam: ConsoleHistoryActionItem, + workspaceItemPromise: Promise + ): void { const historyItem = historyItemParam; - historyItem.wrappedResult = null; - historyItem.cancelResult = null; + historyItem.wrappedResult = undefined; + historyItem.cancelResult = undefined; if (!result) { return; @@ -250,12 +354,16 @@ export class Console extends PureComponent { this.openUpdatedItems(result.changes); } - handleCommandError(error, historyItemParam, workspaceItemPromise) { + handleCommandError( + error: unknown, + historyItemParam: ConsoleHistoryActionItem, + workspaceItemPromise: Promise + ): void { const historyItem = historyItemParam; - historyItem.wrappedResult = null; - historyItem.cancelResult = null; + historyItem.wrappedResult = undefined; + historyItem.cancelResult = undefined; - if (error && error.isCanceled) { + if (error && ((error as unknown) as { isCanceled: boolean }).isCanceled) { log.debug('Called handleCommandError on a cancelled promise result'); return; } @@ -277,7 +385,7 @@ export class Console extends PureComponent { }); } - handleFocusHistory(event) { + handleFocusHistory(event: CustomEvent): void { event.preventDefault(); event.stopPropagation(); @@ -285,7 +393,7 @@ export class Console extends PureComponent { focusCommandHistory(); } - handleLogMessage(message) { + handleLogMessage(message: LogItem): void { const { isPrintStdOutEnabled } = this.state; if (!isPrintStdOutEnabled) { return; @@ -296,22 +404,22 @@ export class Console extends PureComponent { } } - queueLogMessage(message, logLevel) { - const result = {}; + queueLogMessage(message: string, logLevel: string): void { + const result: Record = {}; if (Console.isErrorLevel(logLevel)) { result.error = message; } else { result.message = message; } - const historyItem = { command: null, result }; + const historyItem = { command: undefined, result }; this.queuedLogMessages.push(historyItem); this.processLogMessageQueue(); } - processLogMessageQueue() { + processLogMessageQueue = throttle(() => { this.scrollConsoleHistoryToBottom(); this.setState(state => { @@ -327,9 +435,9 @@ export class Console extends PureComponent { return { consoleHistory }; }); - } + }, Console.LOG_THROTTLE); - openUpdatedItems(changes) { + openUpdatedItems(changes: VariableChanges): void { const { isAutoLaunchPanelsEnabled } = this.state; if (!changes || !isAutoLaunchPanelsEnabled) { return; @@ -341,7 +449,7 @@ export class Console extends PureComponent { ); } - closeRemovedItems(changes) { + closeRemovedItems(changes: VariableChanges): void { if (!changes || !changes.removed || changes.removed.length === 0) { return; } @@ -351,7 +459,10 @@ export class Console extends PureComponent { removed.forEach(object => closeObject(object)); } - updateHistory(result, historyItemParam) { + updateHistory( + result: { changes: unknown }, + historyItemParam: ConsoleHistoryActionItem + ): void { const historyItem = historyItemParam; if (!result || !result.changes || !historyItem) { return; @@ -368,8 +479,11 @@ export class Console extends PureComponent { }); } - updateKnownObjects(historyItem) { - const { changes } = historyItem.result; + updateKnownObjects(historyItem: ConsoleHistoryActionItem): void { + let changes: undefined | VariableChanges; + if (historyItem.result) { + changes = historyItem.result.changes; + } if ( !changes || ((!changes.created || changes.created.length === 0) && @@ -391,42 +505,48 @@ export class Console extends PureComponent { const objectHistoryMap = new Map(state.objectHistoryMap); const objectMap = new Map(state.objectMap); - const disableOldObject = (object, isRemoved = false) => { - const { name } = object; - const oldIndex = objectHistoryMap.get(name); + const disableOldObject = ( + object: VariableDefinition, + isRemoved = false + ) => { + const { title } = object; + assertNotNull(title); + const oldIndex = objectHistoryMap.get(title); // oldIndex can be -1 if a object is active but doesn't have a command in consoleHistory // this can happen after clearing the console using 'clear' or 'cls' command + assertNotNull(oldIndex); if (oldIndex >= 0) { // disable outdated object variable in the old consoleHistory item history[oldIndex].disabledObjects = history[ oldIndex - ].disabledObjects.concat(name); + ].disabledObjects?.concat(title); history[oldIndex] = { ...history[oldIndex] }; } - objectHistoryMap.set(name, itemIndex); + objectHistoryMap.set(title, itemIndex); if (isRemoved) { - objectMap.delete(name); + objectMap.delete(title); } else { - objectMap.set(name, object); + objectMap.set(title, object); } }; - changes.updated.forEach(object => disableOldObject(object)); - changes.removed.forEach(object => disableOldObject(object, true)); + changes?.updated.forEach(object => disableOldObject(object)); + changes?.removed.forEach(object => disableOldObject(object, true)); // Created objects have to be processed after removed // in case the same object name is present in both removed and created - changes.created.forEach(object => { - const { name } = object; - objectHistoryMap.set(name, itemIndex); - objectMap.set(name, object); + changes?.created.forEach(object => { + const { title } = object; + assertNotNull(title); + objectHistoryMap.set(title, itemIndex); + objectMap.set(title, object); }); return { objectHistoryMap, objectMap, consoleHistory: history }; }); } - updateObjectMap() { + updateObjectMap(): void { const { objectMap } = this.props; this.setState({ objectMap }); } @@ -436,7 +556,10 @@ export class Console extends PureComponent { * @param {object} result The result to store with the history item. Could be empty object for success * @param {Promise} workspaceItemPromise The workspace data row promise for the workspace item to be updated */ - updateWorkspaceHistoryItem(result, workspaceItemPromise) { + updateWorkspaceHistoryItem( + result: { error: unknown }, + workspaceItemPromise: Promise + ): void { const promise = this.pending.add(workspaceItemPromise); const endTime = new Date().toJSON(); @@ -461,8 +584,9 @@ export class Console extends PureComponent { }); } - scrollConsoleHistoryToBottom(force = false) { + scrollConsoleHistoryToBottom(force = false): void { const pane = this.consoleHistoryScrollPane.current; + assertNotNull(pane); if (!force && pane.scrollTop < pane.scrollHeight - pane.offsetHeight) { return; } @@ -472,8 +596,9 @@ export class Console extends PureComponent { }); } - handleScrollPaneScroll() { + handleScrollPaneScroll(): void { const scrollPane = this.consoleHistoryScrollPane.current; + assertNotNull(scrollPane); if ( scrollPane.scrollTop > 0 && scrollPane.scrollHeight > scrollPane.clientHeight @@ -484,25 +609,25 @@ export class Console extends PureComponent { } } - handleToggleAutoLaunchPanels() { + handleToggleAutoLaunchPanels(): void { this.setState(state => ({ isAutoLaunchPanelsEnabled: !state.isAutoLaunchPanelsEnabled, })); } - handleToggleClosePanelsOnDisconnect() { + handleToggleClosePanelsOnDisconnect(): void { this.setState(state => ({ isClosePanelsOnDisconnectEnabled: !state.isClosePanelsOnDisconnectEnabled, })); } - handleTogglePrintStdout() { + handleTogglePrintStdout(): void { this.setState(state => ({ isPrintStdOutEnabled: !state.isPrintStdOutEnabled, })); } - handleUploadCsv() { + handleUploadCsv(): void { this.setState({ showCsvOverlay: true, dragError: null, @@ -510,7 +635,7 @@ export class Console extends PureComponent { }); } - handleHideCsv() { + handleHideCsv(): void { this.setState({ showCsvOverlay: false, csvFile: null, @@ -520,15 +645,15 @@ export class Console extends PureComponent { }); } - handleCsvFileOpened(file) { + handleCsvFileOpened(file: File | null): void { this.setState({ csvFile: file, csvPaste: null }); } - handleCsvPaste(value) { + handleCsvPaste(value: string): void { this.setState({ csvFile: null, csvPaste: value }); } - handleDragEnter(e) { + handleDragEnter(e: DragEvent): void { if ( !e.dataTransfer || !e.dataTransfer.items || @@ -555,9 +680,14 @@ export class Console extends PureComponent { } } - handleDragLeave(e) { + handleDragLeave(e: DragEvent): void { // DragLeave gets fired for every child element, so make sure we're actually leaving the drop zone - if (!e.currentTarget || e.currentTarget.contains(e.relatedTarget)) { + if ( + !e.currentTarget || + (e.currentTarget instanceof Element && + e.relatedTarget instanceof Element && + e.currentTarget.contains(e.relatedTarget)) + ) { return; } e.preventDefault(); @@ -565,11 +695,11 @@ export class Console extends PureComponent { this.setState({ showCsvOverlay: false, dragError: null }); } - handleClearDragError() { + handleClearDragError(): void { this.setState({ dragError: null }); } - handleOpenCsvTable(name) { + handleOpenCsvTable(name: string): void { const { openObject, commandHistoryStorage, language, scope } = this.props; const { consoleHistory, objectMap } = this.state; const object = { name, type: dh.VariableType.TABLE }; @@ -598,17 +728,16 @@ export class Console extends PureComponent { command: name, startTime: new Date().toJSON(), endTime: new Date().toJSON(), - result: null, }); } - addConsoleHistoryMessage(message, error) { + addConsoleHistoryMessage(message: string | null, error?: unknown): void { const { consoleHistory } = this.state; const historyItem = { command: '', startTime: Date.now(), endTime: Date.now(), - result: { message, error }, + result: { message: message ?? undefined, error }, }; const history = consoleHistory.concat(historyItem); this.setState({ @@ -616,19 +745,19 @@ export class Console extends PureComponent { }); } - handleCsvUpdate(message) { + handleCsvUpdate(message: string): void { this.addConsoleHistoryMessage(message); } - handleCsvError(error) { + handleCsvError(error: unknown): void { this.addConsoleHistoryMessage(null, error); } - handleCsvInProgress(csvUploadInProgress) { + handleCsvInProgress(csvUploadInProgress: boolean): void { this.setState({ csvUploadInProgress }); } - handleOverflowActions() { + handleOverflowActions(): DropdownAction[] { const { isAutoLaunchPanelsEnabled, isClosePanelsOnDisconnectEnabled, @@ -641,21 +770,21 @@ export class Console extends PureComponent { title: 'Print Stdout', action: this.handleTogglePrintStdout, group: ContextActions.groups.high, - icon: isPrintStdOutEnabled ? vsCheck : null, + icon: isPrintStdOutEnabled ? vsCheck : undefined, order: 10, }, { title: 'Auto Launch Panels', action: this.handleToggleAutoLaunchPanels, group: ContextActions.groups.high, - icon: isAutoLaunchPanelsEnabled ? vsCheck : null, + icon: isAutoLaunchPanelsEnabled ? vsCheck : undefined, order: 20, }, { title: 'Close Panels on Disconnect', action: this.handleToggleClosePanelsOnDisconnect, group: ContextActions.groups.high, - icon: isClosePanelsOnDisconnectEnabled ? vsCheck : null, + icon: isClosePanelsOnDisconnectEnabled ? vsCheck : undefined, order: 30, }, { @@ -667,7 +796,7 @@ export class Console extends PureComponent { ]; } - handleCommandSubmit(command) { + handleCommandSubmit(command: string): void { if (command === 'clear' || command === 'cls') { this.clearConsoleHistory(); } else if (command.length > 0) { @@ -693,7 +822,7 @@ export class Console extends PureComponent { } } - clearConsoleHistory() { + clearConsoleHistory(): void { this.pending.cancel(); this.setState(state => { @@ -720,7 +849,7 @@ export class Console extends PureComponent { }, ]); - addCommand(command, focus = true, execute = false) { + addCommand(command: string, focus = true, execute = false): void { if (!this.consoleInput.current) { return; } @@ -731,13 +860,17 @@ export class Console extends PureComponent { } } - focus() { + focus(): void { this.consoleInput.current?.focus(); } - sendSettingsChange(prevState, state, checkIfChanged = true) { - const keys = Object.keys(DEFAULT_SETTINGS); - const settings = {}; + sendSettingsChange( + prevState: ConsoleState, + state: ConsoleState, + checkIfChanged = true + ): void { + const keys = Object.keys(DEFAULT_SETTINGS) as Array; + const settings: Record = {}; let hasChanges = false; for (let i = 0; i < keys.length; i += 1) { const key = keys[i]; @@ -755,7 +888,7 @@ export class Console extends PureComponent { onSettingsChange(settings); } - updateDimensions() { + updateDimensions(): void { if (this.consolePane.current) { this.setState({ consoleHeight: this.consolePane.current.clientHeight, @@ -766,7 +899,7 @@ export class Console extends PureComponent { } } - render() { + render(): ReactElement { const { actions, historyChildren, @@ -803,7 +936,7 @@ export class Console extends PureComponent {
@@ -861,8 +994,8 @@ export class Console extends PureComponent { onClose={this.handleHideCsv} onUpdate={this.handleCsvUpdate} onError={this.handleCsvError} - file={csvFile} - paste={csvPaste} + file={csvFile ?? undefined} + paste={csvPaste ?? undefined} onInProgress={this.handleCsvInProgress} timeZone={timeZone} unzip={unzip} @@ -878,46 +1011,4 @@ export class Console extends PureComponent { } } -Console.propTypes = { - statusBarChildren: PropTypes.node, - settings: PropTypes.shape({}), - focusCommandHistory: PropTypes.func.isRequired, - openObject: PropTypes.func.isRequired, - closeObject: PropTypes.func.isRequired, - session: APIPropTypes.IdeSession.isRequired, - language: PropTypes.string.isRequired, - commandHistoryStorage: StoragePropTypes.CommandHistoryStorage.isRequired, - onSettingsChange: PropTypes.func, - scope: PropTypes.string, - actions: PropTypes.arrayOf(PropTypes.shape({})), - timeZone: PropTypes.string, - - // Children shown at the bottom of the console history - historyChildren: PropTypes.node, - - // Known object map - objectMap: PropTypes.instanceOf(Map), - - disabled: PropTypes.bool, - - /** - * Function to unzip a zip file. If not provided, zip files will not be accepted - * (file:File) => Promise - */ - unzip: PropTypes.func, -}; - -Console.defaultProps = { - statusBarChildren: null, - settings: {}, - onSettingsChange: () => {}, - scope: null, - actions: [], - historyChildren: null, - timeZone: 'America/New_York', - objectMap: new Map(), - disabled: false, - unzip: null, -}; - export default Console; diff --git a/packages/console/src/ConsoleInput.jsx b/packages/console/src/ConsoleInput.tsx similarity index 66% rename from packages/console/src/ConsoleInput.jsx rename to packages/console/src/ConsoleInput.tsx index 496c329d90..e4fb1c5248 100644 --- a/packages/console/src/ConsoleInput.jsx +++ b/packages/console/src/ConsoleInput.tsx @@ -1,12 +1,21 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; +import React, { PureComponent, ReactElement, RefObject } from 'react'; import classNames from 'classnames'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; import Log from '@deephaven/log'; -import { PromiseUtils } from '@deephaven/utils'; +import { + assertNotNull, + CancelablePromise, + PromiseUtils, +} from '@deephaven/utils'; +import { ViewportData } from '@deephaven/storage'; +import { IdeSession } from '@deephaven/jsapi-shim'; +import { + CommandHistoryStorage, + CommandHistoryStorageItem, + CommandHistoryTable, +} from './command-history'; import { MonacoCompletionProvider, MonacoTheme, MonacoUtils } from './monaco'; import './ConsoleInput.scss'; -import StoragePropTypes from './StoragePropTypes'; const log = Log.module('ConsoleInput'); @@ -16,23 +25,43 @@ const BOTTOM_PADDING = 6; const MIN_INPUT_HEIGHT = LINE_HEIGHT + TOP_PADDING + BOTTOM_PADDING; const BUFFER_SIZE = 100; +interface ConsoleInputProps { + session: IdeSession; + language: string; + scope?: string; + commandHistoryStorage: CommandHistoryStorage; + onSubmit: (command: string) => void; + maxHeight?: number; + disabled?: boolean; +} + +interface ConsoleInputState { + commandEditorHeight: number; + isFocused: boolean; + model: monaco.editor.ITextModel | null; +} /** * Component for input in a console session. Handles loading the recent command history */ -export class ConsoleInput extends PureComponent { +export class ConsoleInput extends PureComponent< + ConsoleInputProps, + ConsoleInputState +> { + static defaultProps = { + maxHeight: LINE_HEIGHT * 10, + scope: null, + disabled: false, + }; + static INPUT_CLASS_NAME = 'console-input'; - constructor(props) { + constructor(props: ConsoleInputProps) { super(props); this.handleWindowResize = this.handleWindowResize.bind(this); - this.cancelListener = null; this.commandContainer = React.createRef(); - this.commandEditor = null; this.commandHistoryIndex = null; - this.commandSuggestionContainer = null; - this.loadingPromise = null; this.timestamp = Date.now(); this.bufferIndex = 0; this.history = []; @@ -46,7 +75,7 @@ export class ConsoleInput extends PureComponent { }; } - componentDidMount() { + componentDidMount(): void { this.initCommandEditor(); window.addEventListener('resize', this.handleWindowResize); @@ -54,11 +83,11 @@ export class ConsoleInput extends PureComponent { this.loadMoreHistory(); } - componentDidUpdate() { + componentDidUpdate(): void { this.layoutEditor(); } - componentWillUnmount() { + componentWillUnmount(): void { window.removeEventListener('resize', this.handleWindowResize); if (this.loadingPromise != null) { @@ -68,15 +97,38 @@ export class ConsoleInput extends PureComponent { this.destroyCommandEditor(); } + cancelListener?: () => void; + + commandContainer: RefObject; + + commandEditor?: monaco.editor.IStandaloneCodeEditor; + + commandHistoryIndex: number | null; + + commandSuggestionContainer?: Element | null; + + loadingPromise?: + | CancelablePromise> + | CancelablePromise; + + timestamp: number; + + bufferIndex: number | null; + + history: string[]; + + // Tracks every command that has been modified by its commandHistoryIndex. Cleared on any command being executed + modifiedCommands: Map; + /** * Sets the console text from an external source. * Sets commandHistoryIndex to null since the source is not part of the history - * @param {string} text The text to set in the input - * @param {boolean} focus If the input should be focused - * @param {boolean} execute If the input should be executed + * @param text The text to set in the input + * @param focus If the input should be focused + * @param execute If the input should be executed * @returns void */ - setConsoleText(text, focus = true, execute = false) { + setConsoleText(text: string, focus = true, execute = false): void { if (!text) { return; } @@ -86,7 +138,7 @@ export class ConsoleInput extends PureComponent { // Need to set commandHistoryIndex before value // On value change, modified commands map updates this.commandHistoryIndex = null; - this.commandEditor.setValue(text); + this.commandEditor?.setValue(text); if (focus) { this.focusEnd(); @@ -99,10 +151,10 @@ export class ConsoleInput extends PureComponent { } } - initCommandEditor() { + initCommandEditor(): void { const { language, session } = this.props; const commandSettings = { - copyWithSyntaxHighlighting: 'false', + copyWithSyntaxHighlighting: false, cursorStyle: 'block', fixedOverflowWidgets: true, folding: false, @@ -127,21 +179,18 @@ export class ConsoleInput extends PureComponent { tabCompletion: 'on', value: '', wordWrap: 'on', - }; + } as const; - this.commandEditor = monaco.editor.create( - this.commandContainer.current, - commandSettings - ); + const element = this.commandContainer.current; + assertNotNull(element); + this.commandEditor = monaco.editor.create(element, commandSettings); MonacoUtils.setEOL(this.commandEditor); MonacoUtils.openDocument(this.commandEditor, session); this.commandEditor.onDidChangeModelContent(() => { - this.modifiedCommands.set( - this.commandHistoryIndex, - this.commandEditor.getValue() - ); + const value = this.commandEditor?.getValue(); + this.modifiedCommands.set(this.commandHistoryIndex, value ?? null); this.updateDimensions(); }); @@ -160,8 +209,10 @@ export class ConsoleInput extends PureComponent { */ this.commandEditor.onKeyDown(keyEvent => { const { commandEditor, commandHistoryIndex } = this; - const { lineNumber } = commandEditor.getPosition(); - const model = commandEditor.getModel(); + const position = commandEditor?.getPosition(); + assertNotNull(position); + const { lineNumber } = position; + const model = commandEditor?.getModel(); if ( keyEvent.keyCode === monaco.KeyCode.UpArrow && !this.isSuggestionMenuPopulated() && @@ -183,7 +234,7 @@ export class ConsoleInput extends PureComponent { if ( keyEvent.keyCode === monaco.KeyCode.DownArrow && !this.isSuggestionMenuPopulated() && - lineNumber === model.getLineCount() + lineNumber === model?.getLineCount() ) { if (commandHistoryIndex != null && commandHistoryIndex > 0) { this.loadCommand(commandHistoryIndex - 1); @@ -211,7 +262,7 @@ export class ConsoleInput extends PureComponent { keyEvent.stopPropagation(); keyEvent.preventDefault(); - this.commandEditor.trigger( + this.commandEditor?.trigger( 'Tab key trigger suggestions', 'editor.action.triggerSuggest', {} @@ -227,7 +278,7 @@ export class ConsoleInput extends PureComponent { // Override the Ctrl+F functionality so that the find window doesn't appear this.commandEditor.addCommand( monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_F, // eslint-disable-line no-bitwise - () => {} + () => undefined ); MonacoUtils.removeConflictingKeybindings(this.commandEditor); @@ -241,70 +292,75 @@ export class ConsoleInput extends PureComponent { this.setState({ model: this.commandEditor.getModel() }); } - destroyCommandEditor() { + destroyCommandEditor(): void { const { session } = this.props; if (this.commandEditor) { MonacoUtils.closeDocument(this.commandEditor, session); this.commandEditor.dispose(); - this.commandEditor = null; + this.commandEditor = undefined; } } - handleWindowResize() { + handleWindowResize(): void { this.updateDimensions(); } - isSuggestionMenuActive() { + isSuggestionMenuActive(): boolean { if (!this.commandSuggestionContainer) { this.commandSuggestionContainer = this.commandEditor - .getDomNode() - .querySelector('.suggest-widget'); + ?.getDomNode() + ?.querySelector('.suggest-widget'); } return ( - this.commandSuggestionContainer && - this.commandSuggestionContainer.classList.contains('visible') + (this.commandSuggestionContainer && + this.commandSuggestionContainer.classList.contains('visible')) ?? + false ); } - isSuggestionMenuPopulated() { + isSuggestionMenuPopulated(): boolean { return ( this.isSuggestionMenuActive() && - this.commandSuggestionContainer.querySelector('.monaco-list-rows') - .childElementCount > 0 + (this.commandSuggestionContainer?.querySelector('.monaco-list-rows') + ?.childElementCount ?? 0) > 0 ); } - focus() { - this.commandEditor.focus(); + focus(): void { + this.commandEditor?.focus(); } - focusStart() { - const model = this.commandEditor.getModel(); + focusStart(): void { + const model = this.commandEditor?.getModel(); + assertNotNull(model); const column = model.getLineLength(1) + 1; // Length of 1st line - const firstCharTop = this.commandEditor.getTopForPosition(1, column); - this.commandEditor.setPosition({ lineNumber: 1, column }); - this.commandEditor.setScrollTop(firstCharTop); - this.commandEditor.focus(); + const firstCharTop = this.commandEditor?.getTopForPosition(1, column); + assertNotNull(firstCharTop); + this.commandEditor?.setPosition({ lineNumber: 1, column }); + this.commandEditor?.setScrollTop(firstCharTop); + this.commandEditor?.focus(); } - focusEnd() { - const model = this.commandEditor.getModel(); + focusEnd(): void { + const model = this.commandEditor?.getModel(); + assertNotNull(model); const lastLine = model.getLineCount(); const column = model.getLineLength(lastLine) + 1; - const lastCharTop = this.commandEditor.getTopForPosition(lastLine, column); - this.commandEditor.setPosition({ lineNumber: lastLine, column }); - this.commandEditor.setScrollTop(lastCharTop); - this.commandEditor.focus(); + const lastCharTop = this.commandEditor?.getTopForPosition(lastLine, column); + assertNotNull(lastCharTop); + this.commandEditor?.setPosition({ lineNumber: lastLine, column }); + this.commandEditor?.setScrollTop(lastCharTop); + this.commandEditor?.focus(); } - clear() { - this.commandEditor.focus(); - this.commandEditor.getModel().setValue(''); + clear(): void { + this.commandEditor?.focus(); + this.commandEditor?.getModel()?.setValue(''); this.commandHistoryIndex = null; } - layoutEditor() { + layoutEditor(): void { if (this.commandEditor) { this.commandEditor.layout(); } @@ -313,10 +369,10 @@ export class ConsoleInput extends PureComponent { /** * Loads the given command from history * If edits have been made to the command since last run command, loads the modified version - * @param {number | null} index The index to load. Null to load command started in the editor and not in the history + * @param index The index to load. Null to load command started in the editor and not in the history */ - loadCommand(index) { - if (index !== null && index >= this.history.length) { + loadCommand(index: number | null): void { + if (index === null || index >= this.history.length) { return; } @@ -325,14 +381,14 @@ export class ConsoleInput extends PureComponent { index === null ? '' : this.history[this.history.length - index - 1]; this.commandHistoryIndex = index; - this.commandEditor.getModel().setValue(modifiedValue ?? historyValue); + this.commandEditor?.getModel()?.setValue(modifiedValue ?? historyValue); if (index !== null && index > this.history.length - BUFFER_SIZE) { this.loadMoreHistory(); } } - async loadMoreHistory() { + async loadMoreHistory(): Promise { try { if (this.loadingPromise != null || this.bufferIndex == null) { return; @@ -341,7 +397,7 @@ export class ConsoleInput extends PureComponent { const { commandHistoryStorage, language, scope } = this.props; this.loadingPromise = PromiseUtils.makeCancelable( - commandHistoryStorage.getTable(language, scope, this.timestamp), + commandHistoryStorage.getTable(language, scope ?? '', this.timestamp), resolved => resolved.close() ); @@ -363,15 +419,15 @@ export class ConsoleInput extends PureComponent { this.bufferIndex = null; } this.history = [ - ...viewportData.items.map(({ name }) => name).reverse(), + ...viewportData?.items.map(({ name }) => name).reverse(), ...this.history, ]; - this.loadingPromise = null; + this.loadingPromise = undefined; table.close(); } catch (err) { - this.loadingPromise = null; + this.loadingPromise = undefined; if (PromiseUtils.isCanceled(err)) { log.debug2('Promise canceled, not loading history'); return; @@ -381,25 +437,27 @@ export class ConsoleInput extends PureComponent { } } - processCommand() { + processCommand(): void { this.commandHistoryIndex = null; this.modifiedCommands.clear(); - const command = this.commandEditor.getValue().trim(); + const command = this.commandEditor?.getValue().trim(); + assertNotNull(command); this.history.push(command); - this.commandEditor.setValue(''); + this.commandEditor?.setValue(''); this.updateDimensions(); const { onSubmit } = this.props; onSubmit(command); } - updateDimensions() { + updateDimensions(): void { if (!this.commandEditor) { return; } const { maxHeight } = this.props; + assertNotNull(maxHeight); const contentHeight = this.commandEditor.getContentHeight(); const commandEditorHeight = Math.max( Math.min(contentHeight, maxHeight), @@ -408,25 +466,21 @@ export class ConsoleInput extends PureComponent { // Only show the overview ruler (markings overlapping sroll bar area) if the scrollbar will show const shouldScroll = contentHeight > commandEditorHeight; - const options = this.commandEditor.getOptions(); - if (shouldScroll) { - options.overviewRulerLanes = undefined; // Resets to default - } else { - options.overviewRulerLanes = 0; - } + + const options = { overviewRulerLanes: shouldScroll ? undefined : 0 }; this.setState( { commandEditorHeight, }, () => { - this.commandEditor.updateOptions(options); - this.commandEditor.layout(); + this.commandEditor?.updateOptions(options); + this.commandEditor?.layout(); } ); } - render() { + render(): ReactElement { const { disabled, language, session } = this.props; const { commandEditorHeight, isFocused, model } = this.state; return ( @@ -454,20 +508,4 @@ export class ConsoleInput extends PureComponent { } } -ConsoleInput.propTypes = { - session: PropTypes.shape({}).isRequired, - language: PropTypes.string.isRequired, - scope: PropTypes.string, - commandHistoryStorage: StoragePropTypes.CommandHistoryStorage.isRequired, - onSubmit: PropTypes.func.isRequired, - maxHeight: PropTypes.number, - disabled: PropTypes.bool, -}; - -ConsoleInput.defaultProps = { - maxHeight: LINE_HEIGHT * 10, - scope: null, - disabled: false, -}; - export default ConsoleInput; diff --git a/packages/console/src/ConsoleMenu.jsx b/packages/console/src/ConsoleMenu.tsx similarity index 63% rename from packages/console/src/ConsoleMenu.jsx rename to packages/console/src/ConsoleMenu.tsx index 09470fc1db..a20c0ebb4f 100644 --- a/packages/console/src/ConsoleMenu.jsx +++ b/packages/console/src/ConsoleMenu.tsx @@ -1,7 +1,17 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; +import React, { + ChangeEvent, + ChangeEventHandler, + PureComponent, + ReactElement, +} from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { DropdownMenu, SearchInput, Tooltip } from '@deephaven/components'; +import { + DropdownAction, + DropdownMenu, + PopperOptions, + SearchInput, + Tooltip, +} from '@deephaven/components'; import { dhTable, vsGraph, @@ -9,21 +19,32 @@ import { vsTriangleDown, } from '@deephaven/icons'; import Log from '@deephaven/log'; -import { PropTypes as APIPropTypes } from '@deephaven/jsapi-shim'; +import { VariableDefinition } from '@deephaven/jsapi-shim'; import memoize from 'memoize-one'; import './ConsoleMenu.scss'; import ConsoleUtils from './common/ConsoleUtils'; const log = Log.module('ConsoleMenu'); -class ConsoleMenu extends PureComponent { +interface ConsoleMenuProps { + openObject: (object: VariableDefinition) => void; + objects: VariableDefinition[]; + overflowActions: DropdownAction[]; +} + +interface ConsoleMenuState { + tableFilterText: string; + widgetFilterText: string; +} + +class ConsoleMenu extends PureComponent { static makeItemActions( - objects, - filterText, - refCallback, - changeCallback, - openCallback - ) { + objects: VariableDefinition[], + filterText: string, + refCallback: (ref: SearchInput) => void, + changeCallback: ChangeEventHandler, + openCallback: (object: VariableDefinition) => void + ): DropdownAction[] { if (objects.length === 0) { return []; } @@ -41,11 +62,13 @@ class ConsoleMenu extends PureComponent { let filteredItems = objects; if (filterText) { filteredItems = filteredItems.filter( - ({ name }) => name.toLowerCase().indexOf(filterText.toLowerCase()) >= 0 + ({ title }: { title?: string }) => + title != null && + title.toLowerCase().indexOf(filterText.toLowerCase()) >= 0 ); } const openActions = filteredItems.map(object => ({ - title: object.name, + title: object.title, action: () => { openCallback(object); }, @@ -54,7 +77,7 @@ class ConsoleMenu extends PureComponent { return [searchAction, ...openActions]; } - constructor(props) { + constructor(props: ConsoleMenuProps) { super(props); this.handleTableFilterChange = this.handleTableFilterChange.bind(this); @@ -64,76 +87,89 @@ class ConsoleMenu extends PureComponent { this.handleWidgetMenuClosed = this.handleWidgetMenuClosed.bind(this); this.handleWidgetMenuOpened = this.handleWidgetMenuOpened.bind(this); - this.tableSearchField = null; - this.widgetSearchField = null; - this.state = { tableFilterText: '', widgetFilterText: '', }; } - makeTableActions = memoize((objects, filterText, openObject) => { - const tables = objects.filter(object => - ConsoleUtils.isTableType(object.type) - ); - return ConsoleMenu.makeItemActions( - tables, - filterText, - searchField => { - this.tableSearchField = searchField; - }, - this.handleTableFilterChange, - openObject - ); - }); + tableSearchField?: SearchInput; - makeWidgetActions = memoize((objects, filterText, openObject) => { - const widgets = objects.filter(object => - ConsoleUtils.isWidgetType(object.type) - ); - return ConsoleMenu.makeItemActions( - widgets, - filterText, - searchField => { - this.widgetSearchField = searchField; - }, - this.handleWidgetFilterChange, - openObject - ); - }); + widgetSearchField?: SearchInput; - handleTableFilterChange(e) { + makeTableActions = memoize( + ( + objects: VariableDefinition[], + filterText: string, + openObject: (object: VariableDefinition) => void + ): DropdownAction[] => { + const tables = objects.filter(object => + ConsoleUtils.isTableType(object.type) + ); + return ConsoleMenu.makeItemActions( + tables, + filterText, + searchField => { + this.tableSearchField = searchField; + }, + this.handleTableFilterChange, + openObject + ); + } + ); + + makeWidgetActions = memoize( + ( + objects: VariableDefinition[], + filterText: string, + openObject: (object: VariableDefinition) => void + ): DropdownAction[] => { + const widgets = objects.filter(object => + ConsoleUtils.isWidgetType(object.type) + ); + return ConsoleMenu.makeItemActions( + widgets, + filterText, + searchField => { + this.widgetSearchField = searchField; + }, + this.handleWidgetFilterChange, + openObject + ); + } + ); + + handleTableFilterChange(e: ChangeEvent): void { log.debug('filtering tables...'); this.setState({ tableFilterText: e.target.value }); } - handleTableMenuClosed() { + handleTableMenuClosed(): void { this.setState({ tableFilterText: '' }); } - handleTableMenuOpened() { + handleTableMenuOpened(): void { if (this.tableSearchField && this.tableSearchField.focus) { this.tableSearchField.focus(); } } - handleWidgetFilterChange(e) { + handleWidgetFilterChange(e: ChangeEvent): void { log.debug('filtering widgets...'); this.setState({ widgetFilterText: e.target.value }); } - handleWidgetMenuClosed() { + handleWidgetMenuClosed(): void { this.setState({ widgetFilterText: '' }); } - handleWidgetMenuOpened() { + handleWidgetMenuOpened(): void { if (this.widgetSearchField && this.widgetSearchField.focus) { this.widgetSearchField.focus(); } } - render() { + render(): ReactElement { const { overflowActions, objects, openObject } = this.props; const { tableFilterText, widgetFilterText } = this.state; const tableActions = this.makeTableActions( @@ -146,7 +182,7 @@ class ConsoleMenu extends PureComponent { widgetFilterText, openObject ); - const popperOptions = { placement: 'bottom-end' }; + const popperOptions: PopperOptions = { placement: 'bottom-end' }; return (
@@ -212,13 +248,4 @@ class ConsoleMenu extends PureComponent { } } -ConsoleMenu.propTypes = { - openObject: PropTypes.func.isRequired, - objects: PropTypes.arrayOf(APIPropTypes.VariableDefinition).isRequired, - overflowActions: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.arrayOf(PropTypes.shape({})), - ]).isRequired, -}; - export default ConsoleMenu; diff --git a/packages/console/src/ConsolePropTypes.js b/packages/console/src/ConsolePropTypes.ts similarity index 100% rename from packages/console/src/ConsolePropTypes.js rename to packages/console/src/ConsolePropTypes.ts diff --git a/packages/console/src/ConsoleStatusBar.jsx b/packages/console/src/ConsoleStatusBar.tsx similarity index 67% rename from packages/console/src/ConsoleStatusBar.jsx rename to packages/console/src/ConsoleStatusBar.tsx index 94782e6359..7ba04ca616 100644 --- a/packages/console/src/ConsoleStatusBar.jsx +++ b/packages/console/src/ConsoleStatusBar.tsx @@ -1,14 +1,33 @@ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; +import React, { PureComponent, ReactElement, ReactNode } from 'react'; import classNames from 'classnames'; -import dh, { PropTypes as APIPropTypes } from '@deephaven/jsapi-shim'; -import { Tooltip } from '@deephaven/components'; +import dh, { IdeSession, VariableDefinition } from '@deephaven/jsapi-shim'; +import { DropdownAction, Tooltip } from '@deephaven/components'; import { CanceledPromiseError, Pending } from '@deephaven/utils'; import ConsoleMenu from './ConsoleMenu'; import './ConsoleStatusBar.scss'; -export class ConsoleStatusBar extends PureComponent { - constructor(props) { +interface ConsoleStatusBarProps { + children: ReactNode; + session: IdeSession; + openObject: (object: VariableDefinition) => void; + objects: VariableDefinition[]; + overflowActions: DropdownAction[]; +} + +interface ConsoleStatusBarState { + isDisconnected: boolean; + isCommandRunning: boolean; +} + +export class ConsoleStatusBar extends PureComponent< + ConsoleStatusBarProps, + ConsoleStatusBarState +> { + static defaultProps = { + children: null, + }; + + constructor(props: ConsoleStatusBarProps) { super(props); this.handleCommandStarted = this.handleCommandStarted.bind(this); @@ -22,16 +41,18 @@ export class ConsoleStatusBar extends PureComponent { }; } - componentDidMount() { + componentDidMount(): void { this.startListening(); } - componentWillUnmount() { + componentWillUnmount(): void { this.stopListening(); this.cancelPendingPromises(); } - startListening() { + pending: Pending; + + startListening(): void { const { session } = this.props; session.addEventListener( dh.IdeSession.EVENT_COMMANDSTARTED, @@ -39,7 +60,7 @@ export class ConsoleStatusBar extends PureComponent { ); } - stopListening() { + stopListening(): void { const { session } = this.props; session.removeEventListener( dh.IdeSession.EVENT_COMMANDSTARTED, @@ -47,24 +68,24 @@ export class ConsoleStatusBar extends PureComponent { ); } - cancelPendingPromises() { + cancelPendingPromises(): void { this.pending.cancel(); } - handleCommandStarted(event) { + handleCommandStarted(event: CustomEvent): void { const { result } = event.detail; this.pending .add(result) - .then(() => this.handleCommandCompleted(null, result)) - .catch(error => this.handleCommandCompleted(error, result)); + .then(() => this.handleCommandCompleted(null)) + .catch(error => this.handleCommandCompleted(error)); this.setState({ isCommandRunning: true, }); } - handleCommandCompleted(error) { + handleCommandCompleted(error: unknown): void { // Don't update state if the promise was canceled if (!(error instanceof CanceledPromiseError)) { this.setState({ @@ -73,7 +94,7 @@ export class ConsoleStatusBar extends PureComponent { } } - render() { + render(): ReactElement { const { children, openObject, overflowActions, objects } = this.props; const { isDisconnected, isCommandRunning } = this.state; @@ -110,19 +131,4 @@ export class ConsoleStatusBar extends PureComponent { } } -ConsoleStatusBar.propTypes = { - children: PropTypes.node, - session: APIPropTypes.IdeSession.isRequired, - openObject: PropTypes.func.isRequired, - objects: PropTypes.arrayOf(APIPropTypes.VariableDefinition).isRequired, - overflowActions: PropTypes.oneOfType([ - PropTypes.func, - PropTypes.arrayOf(PropTypes.shape({})), - ]).isRequired, -}; - -ConsoleStatusBar.defaultProps = { - children: null, -}; - export default ConsoleStatusBar; diff --git a/packages/console/src/StoragePropTypes.js b/packages/console/src/StoragePropTypes.ts similarity index 100% rename from packages/console/src/StoragePropTypes.js rename to packages/console/src/StoragePropTypes.ts diff --git a/packages/console/src/command-history/CommandHistory.jsx b/packages/console/src/command-history/CommandHistory.tsx similarity index 77% rename from packages/console/src/command-history/CommandHistory.jsx rename to packages/console/src/command-history/CommandHistory.tsx index 31dbbb72c9..186128b2ed 100644 --- a/packages/console/src/command-history/CommandHistory.jsx +++ b/packages/console/src/command-history/CommandHistory.tsx @@ -1,33 +1,84 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { ChangeEvent, Component, ReactElement, RefObject } from 'react'; import { ContextActions, ContextActionUtils, ItemList, SearchInput, GLOBAL_SHORTCUTS, + Shortcut, + RenderItemProps, } from '@deephaven/components'; +import { ViewportData } from '@deephaven/storage'; import { + IconDefinition, vsFileCode, vsFiles, vsNewFile, vsPlay, vsTerminal, } from '@deephaven/icons'; -import { Pending } from '@deephaven/utils'; +import { Pending, Range } from '@deephaven/utils'; import Log from '@deephaven/log'; import CommandHistoryItem from './CommandHistoryItem'; import CommandHistoryActions from './CommandHistoryActions'; import ConsoleConstants from '../common/ConsoleConstants'; import './CommandHistory.scss'; -import StoragePropTypes from '../StoragePropTypes'; import CommandHistoryViewportUpdater from './CommandHistoryViewportUpdater'; import SHORTCUTS from '../ConsoleShortcuts'; +import CommandHistoryStorage, { + CommandHistoryStorageItem, + CommandHistoryTable, +} from './CommandHistoryStorage'; const log = Log.module('CommandHistory'); -class CommandHistory extends Component { +type TODOEVENTHUB = { + value: string; + language: string; +}; +export type ItemAction = { + title: string; + description: string; + icon: IconDefinition; + shortcut?: Shortcut; + action: () => void; + group: number; + order?: number; +}; + +export type HistoryAction = { + action: () => void; + title: string; + description: string; + icon: IconDefinition; + selectionRequired?: boolean; + className?: string; +}; + +interface CommandHistoryProps { + language: string; + sendToConsole: (command: string, focus?: boolean, execute?: boolean) => void; + sendToNotebook: (settings: TODOEVENTHUB, forceNewNotebook?: boolean) => void; + table: CommandHistoryTable; + commandHistoryStorage: CommandHistoryStorage; +} +interface CommandHistoryState { + actions: ItemAction[]; + historyActions: HistoryAction[]; + top: number; + bottom: number; + itemCount: number; + items: CommandHistoryStorageItem[]; + offset: number; + selectedRanges: Range[]; + searchText: string; +} + +class CommandHistory extends Component< + CommandHistoryProps, + CommandHistoryState +> { static ITEM_HEIGHT = 29; static MAX_SELECTION_COUNT = 10000; @@ -36,7 +87,11 @@ class CommandHistory extends Component { send: ContextActions.groups.medium + 100, }; - static getCommandsFromViewport(items, offset, sortedRanges) { + static getCommandsFromViewport( + items: CommandHistoryStorageItem[], + offset: number, + sortedRanges: Range[] + ): string[] { const commands = []; for (let i = 0; i < sortedRanges.length; i += 1) { const range = sortedRanges[i]; @@ -50,12 +105,15 @@ class CommandHistory extends Component { return commands; } - static async getCommandsFromSnapshot(table, sortedRanges) { + static async getCommandsFromSnapshot( + table: CommandHistoryTable, + sortedRanges: Range[] + ): Promise { const items = await table.getSnapshot(sortedRanges); return [...items.values()].map(item => item.name); } - constructor(props) { + constructor(props: CommandHistoryProps) { super(props); this.copySelectedCommands = this.copySelectedCommands.bind(this); @@ -151,16 +209,24 @@ class CommandHistory extends Component { }; } - componentWillUnmount() { + componentWillUnmount(): void { this.pending.cancel(); } + itemActions: ItemAction[]; + + historyActions: HistoryAction[]; + + pending: Pending; + + searchInputRef: RefObject; + /** * Retrieves the selected commands as an array. * If they're not within the current viewport, will fetch them from the table - * @returns {Promise} Array of selected commands + * @returns Array of selected commands */ - async getSelectedCommands() { + async getSelectedCommands(): Promise { const { items, offset, selectedRanges } = this.state; const ranges = selectedRanges.slice().sort((a, b) => a[0] - b[0]); @@ -184,11 +250,11 @@ class CommandHistory extends Component { * Retrieves the text of all the currently selected commands, joined by a new line char * @returns {Promise} The commands joined by \n char */ - getSelectedCommandText() { + getSelectedCommandText(): Promise { return this.getSelectedCommands().then(commands => commands.join('\n')); } - updateActions() { + updateActions(): void { this.setState(state => { const { selectedRanges } = state; const selectedRowCount = selectedRanges.reduce( @@ -207,13 +273,13 @@ class CommandHistory extends Component { }); } - copySelectedCommands() { + copySelectedCommands(): void { this.getSelectedCommandText() .then(ContextActionUtils.copyToClipboard) .catch(log.error); } - createNotebook() { + createNotebook(): void { this.getSelectedCommandText() .then(commandText => { const { language, sendToNotebook } = this.props; @@ -222,7 +288,7 @@ class CommandHistory extends Component { .catch(log.error); } - sendToNotebook() { + sendToNotebook(): void { this.getSelectedCommandText() .then(commandText => { const { language, sendToNotebook } = this.props; @@ -231,12 +297,12 @@ class CommandHistory extends Component { .catch(log.error); } - sendToConsole() { + sendToConsole(): void { const { sendToConsole } = this.props; this.getSelectedCommandText().then(sendToConsole).catch(log.error); } - runInConsole() { + runInConsole(): void { this.getSelectedCommandText() .then(commandText => { const { sendToConsole } = this.props; @@ -245,7 +311,7 @@ class CommandHistory extends Component { .catch(log.error); } - handleSelect(index) { + handleSelect(index: number): void { const { sendToConsole } = this.props; const { items, offset } = this.state; if (index < offset || index >= offset + items.length) { @@ -257,31 +323,37 @@ class CommandHistory extends Component { sendToConsole(name); } - handleSelectionChange(selectedRanges) { + handleSelectionChange(selectedRanges: Range[]): void { this.setState({ selectedRanges }); this.updateActions(); } - handleViewportChange(top, bottom) { + handleViewportChange(top: number, bottom: number): void { this.setState({ top, bottom }); } - handleSearchChange(e) { + handleSearchChange(e: ChangeEvent): void { // clear selected range, as old selection could be filtered from list this.setState({ searchText: e.target.value, selectedRanges: [] }); } - handleViewportUpdate({ items, offset }) { + handleViewportUpdate({ + items, + offset, + }: ViewportData): void { const { table } = this.props; const itemCount = table.size; this.setState({ items, itemCount, offset }); } - renderItem({ item, itemIndex, isSelected }) { + renderItem({ + item, + itemIndex, + isSelected, + }: RenderItemProps): ReactElement { const { language, commandHistoryStorage } = this.props; return ( +> { + static itemKey(i: unknown, item: HistoryAction): string { return `${item.title}`; } - static renderContent(item) { - if (item.selectionRequired && item.icon) { + static renderContent(item: HistoryAction): JSX.Element { + if (item.selectionRequired) { return (
); } - - if (!item.selectionRequired && item.icon) { - return ; - } - - return item.title; + return ; } - render() { + render(): ReactElement { const { actions, hasSelection } = this.props; return ( @@ -56,17 +59,4 @@ class CommandHistoryActions extends Component { } } -CommandHistoryActions.propTypes = { - actions: PropTypes.arrayOf( - PropTypes.shape({ - action: PropTypes.func.isRequired, - title: PropTypes.string.isRequired, - description: PropTypes.string, - icon: PropTypes.FontAwesomeIcon, - selectionRequired: PropTypes.bool, - }) - ).isRequired, - hasSelection: PropTypes.bool.isRequired, -}; - export default CommandHistoryActions; diff --git a/packages/console/src/command-history/CommandHistoryItem.jsx b/packages/console/src/command-history/CommandHistoryItem.tsx similarity index 72% rename from packages/console/src/command-history/CommandHistoryItem.jsx rename to packages/console/src/command-history/CommandHistoryItem.tsx index 6bad29a5a1..ab58faac87 100644 --- a/packages/console/src/command-history/CommandHistoryItem.jsx +++ b/packages/console/src/command-history/CommandHistoryItem.tsx @@ -1,19 +1,26 @@ -import React, { useRef, useCallback } from 'react'; +import React, { useRef, useCallback, ReactElement } from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { Tooltip } from '@deephaven/components'; import './CommandHistoryItem.scss'; import CommandHistoryItemTooltip from './CommandHistoryItemTooltip'; -import ConsolePropTypes from '../ConsolePropTypes'; -import StoragePropTypes from '../StoragePropTypes'; +import CommandHistoryStorage, { + CommandHistoryStorageItem, +} from './CommandHistoryStorage'; + +interface CommandHistoryItemProps { + item: CommandHistoryStorageItem; + language: string; + isSelected?: boolean; + commandHistoryStorage: CommandHistoryStorage; +} const MAX_TRUNCATE_LENGTH = 512; -const CommandHistoryItem = props => { +const CommandHistoryItem = (props: CommandHistoryItemProps): ReactElement => { const { item, language, isSelected, commandHistoryStorage } = props; const previewText = item.name.substring(0, MAX_TRUNCATE_LENGTH); - const tooltip = useRef(); + const tooltip = useRef(null); const handleUpdate = useCallback(() => { tooltip.current?.update(); }, [tooltip]); @@ -51,13 +58,6 @@ const CommandHistoryItem = props => { ); }; -CommandHistoryItem.propTypes = { - item: ConsolePropTypes.CommandHistoryItem.isRequired, - language: PropTypes.string.isRequired, - isSelected: PropTypes.bool, - commandHistoryStorage: StoragePropTypes.CommandHistoryStorage.isRequired, -}; - CommandHistoryItem.defaultProps = { isSelected: false, }; diff --git a/packages/console/src/command-history/CommandHistoryItemTooltip.jsx b/packages/console/src/command-history/CommandHistoryItemTooltip.tsx similarity index 65% rename from packages/console/src/command-history/CommandHistoryItemTooltip.jsx rename to packages/console/src/command-history/CommandHistoryItemTooltip.tsx index 24308ae563..7aa28089a8 100644 --- a/packages/console/src/command-history/CommandHistoryItemTooltip.jsx +++ b/packages/console/src/command-history/CommandHistoryItemTooltip.tsx @@ -1,27 +1,52 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { Component, ReactElement } from 'react'; import debounce from 'lodash.debounce'; import memoize from 'memoizee'; import { LoadingSpinner } from '@deephaven/components'; import { TimeUtils } from '@deephaven/utils'; +import { StorageListenerRemover } from '@deephaven/storage'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { vsWarning } from '@deephaven/icons'; import Code from '../common/Code'; import './CommandHistoryItemTooltip.scss'; -import ConsolePropTypes from '../ConsolePropTypes'; -import StoragePropTypes from '../StoragePropTypes'; +import CommandHistoryStorage, { + CommandHistoryStorageData, + CommandHistoryStorageItem, +} from './CommandHistoryStorage'; + +interface CommandHistoryItemTooltipProps { + item: CommandHistoryStorageItem; + language: string; + onUpdate?: (data: CommandHistoryStorageData | null) => void; + commandHistoryStorage: CommandHistoryStorage; +} + +interface CommandHistoryItemTooltipState { + currentTime: number; + data: CommandHistoryStorageData | null; + error: string | null; +} const LOAD_DATA_DEBOUNCE = 250; const MAX_NUMBER_OF_LINES = 2500; -export class CommandHistoryItemTooltip extends Component { - static getTimeString(startTime, endTime) { +export class CommandHistoryItemTooltip extends Component< + CommandHistoryItemTooltipProps, + CommandHistoryItemTooltipState +> { + static defaultProps = { + onUpdate: (): void => undefined, + }; + + static getTimeString( + startTime: string | undefined, + endTime: string | number + ): string | null { if (!startTime || !endTime) { return null; } const deltaTime = Math.round( - (new Date(endTime) - new Date(startTime)) / 1000 + (new Date(endTime).valueOf() - new Date(startTime).valueOf()) / 1000 ); if (deltaTime < 1) return '<1s'; @@ -29,17 +54,13 @@ export class CommandHistoryItemTooltip extends Component { return TimeUtils.formatElapsedTime(deltaTime); } - constructor(props) { + constructor(props: CommandHistoryItemTooltipProps) { super(props); - this.loadData = debounce(this.loadData.bind(this), LOAD_DATA_DEBOUNCE); this.handleUpdate = this.handleUpdate.bind(this); this.handleError = this.handleError.bind(this); this.handleTimeout = this.handleTimeout.bind(this); - this.timer = null; - this.cleanup = null; - this.state = { currentTime: Date.now(), data: null, @@ -47,11 +68,14 @@ export class CommandHistoryItemTooltip extends Component { }; } - componentDidMount() { + componentDidMount(): void { this.loadData(); } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate( + prevProps: CommandHistoryItemTooltipProps, + prevState: CommandHistoryItemTooltipState + ): void { const { data } = this.state; if ( @@ -70,7 +94,7 @@ export class CommandHistoryItemTooltip extends Component { } } - componentWillUnmount() { + componentWillUnmount(): void { this.loadData.cancel(); if (this.cleanup != null) { this.cleanup(); @@ -78,7 +102,11 @@ export class CommandHistoryItemTooltip extends Component { this.stopTimer(); } - loadData() { + timer?: NodeJS.Timer; + + cleanup?: StorageListenerRemover; + + loadData = debounce((): void => { const { commandHistoryStorage, item, language } = this.props; const { id } = item; this.cleanup = commandHistoryStorage.listenItem( @@ -87,40 +115,40 @@ export class CommandHistoryItemTooltip extends Component { this.handleUpdate, this.handleError ); - } + }, LOAD_DATA_DEBOUNCE); - startTimer() { + startTimer(): void { this.stopTimer(); this.timer = setInterval(this.handleTimeout, 1000); } - stopTimer() { + stopTimer(): void { if (this.timer) { clearInterval(this.timer); - this.timer = null; + this.timer = undefined; } } - updateTime() { + updateTime(): void { this.setState({ currentTime: Date.now(), }); } - handleError(error) { + handleError(error: string): void { this.setState({ error: `${error}` }); } - handleUpdate(item) { + handleUpdate(item: CommandHistoryStorageItem): void { const { data = null } = item ?? {}; this.setState({ data }); const { onUpdate } = this.props; - onUpdate(data); + onUpdate?.(data); } - handleTimeout() { + handleTimeout(): void { this.updateTime(); } @@ -129,14 +157,22 @@ export class CommandHistoryItemTooltip extends Component { { max: 1 } ); - render() { + render(): ReactElement { const { item: { name }, language, } = this.props; const { currentTime, data, error } = this.state; const { result, startTime, endTime } = data ?? {}; - const errorMessage = result?.error?.message ?? result?.error ?? error; + + const errorMessage = result?.error ?? error; + // let errorMessage = error; + // const tempError = result?.error; + // if (typeof tempError === 'string') { + // errorMessage = tempError; + // } else if ((tempError as { message: string }).message != null) { + // errorMessage = (tempError as { message: string }).message; + // } const timeString = CommandHistoryItemTooltip.getTimeString( startTime, endTime || currentTime @@ -176,15 +212,4 @@ export class CommandHistoryItemTooltip extends Component { } } -CommandHistoryItemTooltip.propTypes = { - item: ConsolePropTypes.CommandHistoryItem.isRequired, - language: PropTypes.string.isRequired, - onUpdate: PropTypes.func, - commandHistoryStorage: StoragePropTypes.CommandHistoryStorage.isRequired, -}; - -CommandHistoryItemTooltip.defaultProps = { - onUpdate: () => {}, -}; - export default CommandHistoryItemTooltip; diff --git a/packages/console/src/command-history/index.jsx b/packages/console/src/command-history/index.tsx similarity index 100% rename from packages/console/src/command-history/index.jsx rename to packages/console/src/command-history/index.tsx diff --git a/packages/console/src/common/Code.jsx b/packages/console/src/common/Code.tsx similarity index 70% rename from packages/console/src/common/Code.jsx rename to packages/console/src/common/Code.tsx index b22133d88b..9d76da607e 100644 --- a/packages/console/src/common/Code.jsx +++ b/packages/console/src/common/Code.tsx @@ -1,19 +1,25 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { Component, ReactElement, ReactNode } from 'react'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; -class Code extends Component { - constructor(props) { +interface CodeProps { + children: ReactNode; + language: string; +} + +class Code extends Component> { + constructor(props: CodeProps) { super(props); this.container = null; } - componentDidMount() { + componentDidMount(): void { this.colorize(); } - colorize() { + container: HTMLDivElement | null; + + colorize(): void { const { children } = this.props; if (this.container && children) { monaco.editor.colorizeElement(this.container, { @@ -22,7 +28,7 @@ class Code extends Component { } } - render() { + render(): ReactElement { const { children, language } = this.props; return (
@@ -42,9 +48,4 @@ class Code extends Component { } } -Code.propTypes = { - children: PropTypes.node.isRequired, - language: PropTypes.string.isRequired, -}; - export default Code; diff --git a/packages/console/src/common/ConsoleUtils.test.js b/packages/console/src/common/ConsoleUtils.test.js index fafac40262..670e159695 100644 --- a/packages/console/src/common/ConsoleUtils.test.js +++ b/packages/console/src/common/ConsoleUtils.test.js @@ -40,3 +40,8 @@ describe('parsing shell arguments from text', () => { testStr('foo # bar', ['foo']); }); }); + +describe('predicates', () => { + expect(ConsoleUtils.hasComment('asdf')).toBeFalsy(); + expect(ConsoleUtils.hasComment({ comment: 123 })).toBeTruthy(); +}); diff --git a/packages/console/src/common/ConsoleUtils.js b/packages/console/src/common/ConsoleUtils.ts similarity index 54% rename from packages/console/src/common/ConsoleUtils.js rename to packages/console/src/common/ConsoleUtils.ts index f5cd3c47b0..f9dddbfe09 100644 --- a/packages/console/src/common/ConsoleUtils.js +++ b/packages/console/src/common/ConsoleUtils.ts @@ -1,25 +1,45 @@ -import ShellQuote from 'shell-quote'; +import ShellQuote, { ParseEntry, ControlOperator } from 'shell-quote'; import dh from '@deephaven/jsapi-shim'; class ConsoleUtils { + static hasComment(arg: ParseEntry): arg is { comment: string } { + return (arg as { comment: string }).comment !== undefined; + } + + static hasPattern(arg: ParseEntry): arg is { op: 'glob'; pattern: string } { + return (arg as { pattern: string }).pattern !== undefined; + } + + static hasOp(arg: ParseEntry): arg is { op: ControlOperator } { + return (arg as { op: ControlOperator }).op !== undefined; + } + /** * Given the provided text, parse out arguments using shell quoting rules. * @param str The text to parse. * @return string[] of the arguments. Empty if no arguments found. */ - static parseArguments(str) { + static parseArguments(str: unknown): string[] { if (!str || !(typeof str === 'string' || str instanceof String)) { return []; } // Parse can return an object, not just a string. See the `ParseEntry` type def for all types // We must map them all to strings. Filter out comments that will not be needed as well. - return ShellQuote.parse(str) - .filter(arg => !arg.comment) - .map(arg => arg.pattern ?? arg.op ?? `${arg}`); + return ShellQuote.parse(str as string) + .filter(arg => !this.hasComment(arg)) + .map(arg => { + let ret = `${arg}`; + if (this.hasPattern(arg)) { + ret = arg.pattern; + } else if (this.hasOp(arg)) { + ret = arg.op; + } + return ret; + }); } - static formatTimestamp(date) { + static formatTimestamp(date: Date): string | null { if (!date || !(date instanceof Date)) { return null; } @@ -32,22 +52,23 @@ class ConsoleUtils { return `${hours}:${minutes}:${seconds}.${milliseconds}`; } - static defaultHost() { + static defaultHost(): string { let defaultHost = null; - try { - const url = new URL(process.env.REACT_APP_CORE_API_URL); + const apiUrl = process.env.REACT_APP_CORE_API_URL; + if (apiUrl != null) { + const url = new URL(apiUrl); defaultHost = url.hostname; - } catch (error) { + } else { defaultHost = window.location.hostname; } return defaultHost; } - static isTableType(type) { + static isTableType(type: unknown): boolean { return type === dh.VariableType.TABLE || type === dh.VariableType.TREETABLE; } - static isWidgetType(type) { + static isWidgetType(type: unknown): boolean { return ( type === dh.VariableType.FIGURE || type === dh.VariableType.OTHERWIDGET || @@ -55,15 +76,15 @@ class ConsoleUtils { ); } - static isOpenableType(type) { + static isOpenableType(type: unknown): boolean { return ConsoleUtils.isTableType(type) || ConsoleUtils.isWidgetType(type); } - static isFigureType(type) { + static isFigureType(type: unknown): boolean { return type === dh.VariableType.FIGURE; } - static isPandas(type) { + static isPandas(type: unknown): boolean { return type === dh.VariableType.PANDAS; } } diff --git a/packages/console/src/console-history/ConsoleHistory.jsx b/packages/console/src/console-history/ConsoleHistory.jsx deleted file mode 100644 index 2ecb693964..0000000000 --- a/packages/console/src/console-history/ConsoleHistory.jsx +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Console display for use in the Iris environment. - */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import ConsoleHistoryItem from './ConsoleHistoryItem'; - -import './ConsoleHistory.scss'; - -class ConsoleHistory extends Component { - static itemKey(i, item) { - return `${i}.${item.command}.${item.result && item.result.message}.${ - item.result && item.result.error - }`; - } - - render() { - const { disabled, items, language, openObject } = this.props; - const historyElements = []; - for (let i = 0; i < items.length; i += 1) { - const item = items[i]; - const historyElement = ( - - ); - historyElements.push(historyElement); - } - - return ( -
{historyElements}
- ); - } -} - -ConsoleHistory.propTypes = { - items: PropTypes.arrayOf(PropTypes.shape({})).isRequired, - language: PropTypes.string.isRequired, - openObject: PropTypes.func.isRequired, - disabled: PropTypes.bool, -}; - -ConsoleHistory.defaultProps = { - disabled: false, -}; - -export default ConsoleHistory; diff --git a/packages/console/src/console-history/ConsoleHistory.tsx b/packages/console/src/console-history/ConsoleHistory.tsx new file mode 100644 index 0000000000..891bd0f284 --- /dev/null +++ b/packages/console/src/console-history/ConsoleHistory.tsx @@ -0,0 +1,76 @@ +/** + * Console display for use in the Iris environment. + */ +import React, { Component, ReactElement } from 'react'; +import { CancelablePromise } from '@deephaven/utils'; +import { VariableChanges, VariableDefinition } from '@deephaven/jsapi-shim'; +import ConsoleHistoryItem from './ConsoleHistoryItem'; + +import './ConsoleHistory.scss'; + +export type ConsoleHistoryError = + | string + | { + message: string; + } + | undefined; + +export interface ConsoleHistoryActionItem { + command?: string; + result?: { + message?: string; + error?: unknown; + changes?: VariableChanges; + }; + disabledObjects?: string[]; + startTime?: number; + endTime?: number; + cancelResult?: () => void; + wrappedResult?: CancelablePromise; +} + +interface ConsoleHistoryProps { + items: ConsoleHistoryActionItem[]; + language: string; + openObject: (object: VariableDefinition) => void; + disabled?: boolean; +} + +class ConsoleHistory extends Component< + ConsoleHistoryProps, + Record +> { + static defaultProps = { + disabled: false, + }; + + static itemKey(i: number, item: ConsoleHistoryActionItem): string { + return `${i}.${item.command}.${item.result && item.result.message}.${ + item.result && item.result.error + }`; + } + + render(): ReactElement { + const { disabled, items, language, openObject } = this.props; + const historyElements = []; + for (let i = 0; i < items.length; i += 1) { + const item = items[i]; + const historyElement = ( + + ); + historyElements.push(historyElement); + } + + return ( +
{historyElements}
+ ); + } +} + +export default ConsoleHistory; diff --git a/packages/console/src/console-history/ConsoleHistoryItem.jsx b/packages/console/src/console-history/ConsoleHistoryItem.tsx similarity index 71% rename from packages/console/src/console-history/ConsoleHistoryItem.jsx rename to packages/console/src/console-history/ConsoleHistoryItem.tsx index cbac386036..3b58df2748 100644 --- a/packages/console/src/console-history/ConsoleHistoryItem.jsx +++ b/packages/console/src/console-history/ConsoleHistoryItem.tsx @@ -1,35 +1,49 @@ /** * Console display for use in the Iris environment. */ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; +import React, { PureComponent, ReactElement } from 'react'; import { ButtonOld } from '@deephaven/components'; -import { PropTypes as APIPropTypes } from '@deephaven/jsapi-shim'; import Log from '@deephaven/log'; +import { VariableDefinition } from '@deephaven/jsapi-shim'; import { Code, ObjectIcon } from '../common'; import ConsoleHistoryItemResult from './ConsoleHistoryItemResult'; import ConsoleHistoryResultInProgress from './ConsoleHistoryResultInProgress'; import ConsoleHistoryResultErrorMessage from './ConsoleHistoryResultErrorMessage'; import './ConsoleHistoryItem.scss'; +import { ConsoleHistoryActionItem } from './ConsoleHistory'; const log = Log.module('ConsoleHistoryItem'); -class ConsoleHistoryItem extends PureComponent { - constructor(props) { +interface ConsoleHistoryItemProps { + item: ConsoleHistoryActionItem; + language: string; + openObject: (object: VariableDefinition) => void; + disabled?: boolean; +} + +class ConsoleHistoryItem extends PureComponent< + ConsoleHistoryItemProps, + Record +> { + static defaultProps = { + disabled: false, + }; + + constructor(props: ConsoleHistoryItemProps) { super(props); this.handleCancelClick = this.handleCancelClick.bind(this); this.handleObjectClick = this.handleObjectClick.bind(this); } - handleObjectClick(object) { + handleObjectClick(object: VariableDefinition): void { log.debug('handleObjectClick', object); const { openObject } = this.props; openObject(object); } - handleCancelClick() { + handleCancelClick(): void { const { item } = this.props; if (item && item.cancelResult) { log.debug(`Cancelling command: ${item.command}`); @@ -37,7 +51,7 @@ class ConsoleHistoryItem extends PureComponent { } } - render() { + render(): ReactElement { const { disabled, item, language } = this.props; const { disabledObjects, result } = item; @@ -61,10 +75,10 @@ class ConsoleHistoryItem extends PureComponent { if (changes) { const { created, updated } = changes; [...created, ...updated].forEach(object => { - const { name } = object; - const key = `${name}`; + const { title } = object; + const key = `${title}`; const btnDisabled = - disabled || (disabledObjects ?? []).indexOf(name) >= 0; + disabled || (disabledObjects ?? []).indexOf(key) >= 0; const element = ( - {name} + {title} ); resultElements.push(element); @@ -80,15 +94,16 @@ class ConsoleHistoryItem extends PureComponent { } // If the error has an associated command, we'll actually get a separate ERROR item printed out, so only print an error if there isn't an associated command - if (error && !item.command) { - let errorMessage = error.message; + let errorMessage = error; + if (typeof error !== 'string' && error && !item.command) { + errorMessage = (error as { message: string }).message; if (!errorMessage) { errorMessage = error; } const element = ( ); resultElements.push(element); @@ -129,29 +144,4 @@ class ConsoleHistoryItem extends PureComponent { } } -ConsoleHistoryItem.propTypes = { - item: PropTypes.shape({ - cancelResult: PropTypes.func, - command: PropTypes.string, - disabledObjects: PropTypes.arrayOf(PropTypes.string), - result: PropTypes.shape({ - error: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.shape({ message: PropTypes.string }), - ]), - message: PropTypes.string, - changes: APIPropTypes.VariableChanges, - }), - startTime: PropTypes.number, - endTime: PropTypes.number, - }).isRequired, - language: PropTypes.string.isRequired, - openObject: PropTypes.func.isRequired, - disabled: PropTypes.bool, -}; - -ConsoleHistoryItem.defaultProps = { - disabled: false, -}; - export default ConsoleHistoryItem; diff --git a/packages/console/src/console-history/ConsoleHistoryItemResult.jsx b/packages/console/src/console-history/ConsoleHistoryItemResult.tsx similarity index 71% rename from packages/console/src/console-history/ConsoleHistoryItemResult.jsx rename to packages/console/src/console-history/ConsoleHistoryItemResult.tsx index 783089b753..40adae76b1 100644 --- a/packages/console/src/console-history/ConsoleHistoryItemResult.jsx +++ b/packages/console/src/console-history/ConsoleHistoryItemResult.tsx @@ -1,10 +1,14 @@ /** * Console display for use in the Iris environment. */ -import React from 'react'; +import React, { ReactElement, ReactNode } from 'react'; import PropTypes from 'prop-types'; -const ConsoleHistoryItemResult = ({ children }) => ( +const ConsoleHistoryItemResult = ({ + children, +}: { + children: ReactNode; +}): ReactElement => (
-
{children}
diff --git a/packages/console/src/console-history/ConsoleHistoryResultErrorMessage.jsx b/packages/console/src/console-history/ConsoleHistoryResultErrorMessage.tsx similarity index 79% rename from packages/console/src/console-history/ConsoleHistoryResultErrorMessage.jsx rename to packages/console/src/console-history/ConsoleHistoryResultErrorMessage.tsx index bcb846e004..ade57b5553 100644 --- a/packages/console/src/console-history/ConsoleHistoryResultErrorMessage.jsx +++ b/packages/console/src/console-history/ConsoleHistoryResultErrorMessage.tsx @@ -1,16 +1,37 @@ /** * Error message that can be expanded */ -import React, { PureComponent } from 'react'; +import React, { + KeyboardEvent, + MouseEvent, + PureComponent, + ReactElement, +} from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { vsTriangleRight, vsTriangleDown } from '@deephaven/icons'; +import { assertNotNull } from '@deephaven/utils'; + +interface ConsoleHistoryResultErrorMessageProps { + message?: string; +} + +interface ConsoleHistoryResultErrorMessageState { + isExpanded: boolean; + isTriggerHovered: boolean; +} + +class ConsoleHistoryResultErrorMessage extends PureComponent< + ConsoleHistoryResultErrorMessageProps, + ConsoleHistoryResultErrorMessageState +> { + static defaultProps = { + message: '', + }; -class ConsoleHistoryResultErrorMessage extends PureComponent { static mouseDragThreshold = 5; - constructor(props) { + constructor(props: ConsoleHistoryResultErrorMessageProps) { super(props); this.handleKeyPress = this.handleKeyPress.bind(this); @@ -31,7 +52,13 @@ class ConsoleHistoryResultErrorMessage extends PureComponent { }; } - handleKeyPress(event) { + mouseX: number | null; + + mouseY: number | null; + + isClicking: boolean; + + handleKeyPress(event: KeyboardEvent): void { switch (event.key) { case 'Enter': case ' ': @@ -45,13 +72,13 @@ class ConsoleHistoryResultErrorMessage extends PureComponent { } } - handleMouseDown(event) { + handleMouseDown(event: MouseEvent): void { this.mouseX = event.clientX; this.mouseY = event.clientY; this.isClicking = true; } - handleMouseMove(event) { + handleMouseMove(event: MouseEvent): void { if (this.mouseX != null && this.mouseY != null) { if ( Math.abs(event.clientX - this.mouseX) >= @@ -67,7 +94,7 @@ class ConsoleHistoryResultErrorMessage extends PureComponent { } } - handleMouseUp(event) { + handleMouseUp(event: MouseEvent): void { // We don't want to expand/collapse the error if user is holding shift or an alt key // They may be trying to adjust their selection if (this.isClicking && !event.shiftKey && !event.metaKey && !event.altKey) { @@ -78,21 +105,22 @@ class ConsoleHistoryResultErrorMessage extends PureComponent { this.isClicking = false; } - handleToggleError() { + handleToggleError(): void { this.setState(state => ({ isExpanded: !state.isExpanded })); } - handleMouseEnter() { + handleMouseEnter(): void { this.setState({ isTriggerHovered: true }); } - handleMouseLeave() { + handleMouseLeave(): void { this.setState({ isTriggerHovered: false }); } - render() { + render(): ReactElement { const { isExpanded, isTriggerHovered } = this.state; const { message: messageProp } = this.props; + assertNotNull(messageProp); const lineBreakIndex = messageProp.indexOf('\n'); const isMultiline = lineBreakIndex > -1; let topLineOfMessage = messageProp; @@ -118,7 +146,7 @@ class ConsoleHistoryResultErrorMessage extends PureComponent { type="button" onClick={this.handleToggleError} className={arrowBtnClasses} - tabIndex="-1" + tabIndex={-1} > void; + disabled: boolean; +} + +interface ConsoleHistoryResultInProgressState { + elapsed: number; +} /** * A spinner shown when a command is taking a while. */ -class ConsoleHistoryResultInProgress extends Component { - constructor(props) { +class ConsoleHistoryResultInProgress extends Component< + ConsoleHistoryResultInProgressProps, + ConsoleHistoryResultInProgressState +> { + static defaultProps = { + disabled: false, + }; + + constructor(props: ConsoleHistoryResultInProgressProps) { super(props); this.updateElapsed = this.updateElapsed.bind(this); @@ -24,11 +38,11 @@ class ConsoleHistoryResultInProgress extends Component { }; } - componentDidMount() { + componentDidMount(): void { this.timer = setInterval(this.updateElapsed, 1000); } - componentWillUnmount() { + componentWillUnmount(): void { if (this.timer) { clearInterval(this.timer); } @@ -36,13 +50,17 @@ class ConsoleHistoryResultInProgress extends Component { this.timer = null; } - updateElapsed() { + timer: NodeJS.Timer | null; + + startTime: number; + + updateElapsed(): void { this.setState({ elapsed: Math.round((Date.now() - this.startTime) / 1000), }); } - render() { + render(): ReactElement { const { disabled, onCancelClick } = this.props; const { elapsed } = this.state; return ( @@ -64,13 +82,4 @@ class ConsoleHistoryResultInProgress extends Component { } } -ConsoleHistoryResultInProgress.propTypes = { - onCancelClick: PropTypes.func.isRequired, - disabled: PropTypes.bool, -}; - -ConsoleHistoryResultInProgress.defaultProps = { - disabled: false, -}; - export default ConsoleHistoryResultInProgress; diff --git a/packages/console/src/console-history/index.jsx b/packages/console/src/console-history/index.tsx similarity index 100% rename from packages/console/src/console-history/index.jsx rename to packages/console/src/console-history/index.tsx diff --git a/packages/console/src/csv/CsvFormats.js b/packages/console/src/csv/CsvFormats.ts similarity index 83% rename from packages/console/src/csv/CsvFormats.js rename to packages/console/src/csv/CsvFormats.ts index ec0533b24f..a0b93cdb67 100644 --- a/packages/console/src/csv/CsvFormats.js +++ b/packages/console/src/csv/CsvFormats.ts @@ -1,9 +1,11 @@ +export type CsvTypes = typeof CsvFormats.TYPES[keyof typeof CsvFormats.TYPES]; + class CsvFormats { - static DEFAULT_TYPE = 'DEFAULT_CSV'; + static DEFAULT_TYPE = 'DEFAULT_CSV' as const; - static AUTO = 'AUTODETECT'; + static AUTO = 'AUTODETECT' as const; - static fromExtension(fileName) { + static fromExtension(fileName: string): keyof typeof CsvFormats.TYPES { if (fileName.endsWith('.csv')) { return 'DEFAULT_CSV'; } @@ -20,7 +22,7 @@ class CsvFormats { DEFAULT_CSV: { name: 'Default csv (trimmed)', delimiter: ',', - newline: '', // autodetect + newline: undefined, // autodetect escapeChar: '"', shouldTrim: true, skipEmptyLines: true, @@ -30,7 +32,7 @@ class CsvFormats { TSV: { name: 'Tab seperated (tsv)', delimiter: '\t', - newline: '', // autodetect + newline: undefined, // autodetect escapeChar: '"', shouldTrim: true, skipEmptyLines: true, @@ -84,7 +86,7 @@ class CsvFormats { COLON_SV: { name: ': colon sv', delimiter: ':', - newline: '', // autodetect + newline: undefined, // autodetect escapeChar: '"', shouldTrim: true, skipEmptyLines: true, @@ -94,7 +96,7 @@ class CsvFormats { SEMI_COLON_SV: { name: '; semi-colon sv', delimiter: ';', - newline: '', // autodetect + newline: undefined, // autodetect escapeChar: '"', shouldTrim: true, skipEmptyLines: true, @@ -104,7 +106,7 @@ class CsvFormats { PIPE_SV: { name: '| pipe separated (psv)', delimiter: '|', - newline: '', // autodetect + newline: undefined, // autodetect escapeChar: '"', shouldTrim: true, skipEmptyLines: true, @@ -114,7 +116,7 @@ class CsvFormats { SPACE_SV: { name: '" " space sv', delimiter: ' ', - newline: '', // autodetect + newline: undefined, // autodetect escapeChar: '"', shouldTrim: true, skipEmptyLines: true, @@ -124,7 +126,7 @@ class CsvFormats { AUTODETECT: { name: 'autodetect', delimiter: '', // autodetect - newline: '', // autodetect + newline: undefined, // autodetect escapeChar: '"', shouldTrim: true, skipEmptyLines: true, diff --git a/packages/console/src/csv/CsvInputBar.jsx b/packages/console/src/csv/CsvInputBar.tsx similarity index 81% rename from packages/console/src/csv/CsvInputBar.jsx rename to packages/console/src/csv/CsvInputBar.tsx index d9ddf9f413..5151d41ad8 100644 --- a/packages/console/src/csv/CsvInputBar.jsx +++ b/packages/console/src/csv/CsvInputBar.tsx @@ -1,7 +1,14 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { + ChangeEvent, + Component, + FormEvent, + ReactElement, + RefObject, +} from 'react'; import classNames from 'classnames'; +import type { JSZipObject } from 'jszip'; import { Button, Checkbox } from '@deephaven/components'; +import { IdeSession, Table } from '@deephaven/jsapi-shim'; import Log from '@deephaven/log'; import { DbNameValidator } from '@deephaven/utils'; import CsvOverlay from './CsvOverlay'; @@ -17,11 +24,39 @@ const TYPE_OPTIONS = Object.entries(CsvFormats.TYPES).map(([key, value]) => ( )); +interface CsvInputBarProps { + session: IdeSession; + onOpenTable: (name: string) => void; + onClose: () => void; + onUpdate: (update: string) => void; + onError: (e: unknown) => void; + file: File; + paste?: string; + onInProgress: (boolean: boolean) => void; + timeZone: string; + unzip?: (zipFile: File) => Promise; +} + +interface CsvInputBarState { + tableName: string; + tableNameSet: boolean; + isFirstRowHeaders: boolean; + showProgress: boolean; + progressValue: number; + type: keyof typeof CsvFormats.TYPES; + parser: CsvParser | null; +} /** * Input controls for CSV upload. */ -class CsvInputBar extends Component { - constructor(props) { +class CsvInputBar extends Component { + static defaultProps = { + file: null, + paste: null, + unzip: null, + }; + + constructor(props: CsvInputBarProps) { super(props); this.handleUpload = this.handleUpload.bind(this); @@ -48,7 +83,7 @@ class CsvInputBar extends Component { // React documentation says it is fine to update state inside an if statment /* eslint-disable react/no-did-update-set-state */ - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: CsvInputBarProps): void { const { file, paste } = this.props; const { tableName, tableNameSet } = this.state; // Set the table name from a file @@ -61,7 +96,7 @@ class CsvInputBar extends Component { tableName: fileTableName, tableNameSet: true, }); - this.inputRef.current.focus(); + this.inputRef.current?.focus(); } else if ((!file && prevProps.file) || (!paste && prevProps.paste)) { // The file or paste was unstaged this.setState({ @@ -87,35 +122,37 @@ class CsvInputBar extends Component { } } - componentWillUnmount() { + componentWillUnmount(): void { const { parser } = this.state; if (parser) { parser.cancel(); } } - handleCancel() { + inputRef: RefObject; + + handleCancel(): void { const { onClose } = this.props; onClose(); } - handleError(e) { + handleError(e: unknown): void { const { onClose, onError } = this.props; log.error(e); onError(e); onClose(); } - handleTableName(event) { + handleTableName(event: ChangeEvent): void { this.setState({ tableName: event.target.value, tableNameSet: true }); } - toggleFirstRowHeaders() { + toggleFirstRowHeaders(): void { const { isFirstRowHeaders } = this.state; this.setState({ isFirstRowHeaders: !isFirstRowHeaders }); } - handleUpload(event) { + handleUpload(event: FormEvent): void { event.stopPropagation(); event.preventDefault(); const { file, paste } = this.props; @@ -134,15 +171,15 @@ class CsvInputBar extends Component { } } - handleFile(file, isZip = false) { + handleFile(file: Blob | JSZipObject, isZip = false): void { log.info( - `Starting CSV parser for ${file.name} ${ - isZip ? '' : `${file.size} bytes` - }` + `Starting CSV parser for ${ + file instanceof File ? file.name : 'pasted values' + } ${isZip ? '' : (file as Blob).size} bytes` ); const { session, timeZone, onInProgress } = this.props; const { tableName, isFirstRowHeaders, type } = this.state; - const handleParseDone = tables => { + const handleParseDone = (tables: Table[]) => { // Do not bother merging just one table if (tables.length === 1) { session @@ -182,7 +219,7 @@ class CsvInputBar extends Component { onInProgress(true); } - handleZipFile(zipFile) { + handleZipFile(zipFile: File): void { const { onUpdate, unzip } = this.props; if (unzip == null) { this.handleError(new Error('No support for zip files available.')); @@ -209,7 +246,7 @@ class CsvInputBar extends Component { .catch(e => this.handleError(e)); } - handleProgress(progressValue) { + handleProgress(progressValue: number): boolean { const { showProgress } = this.state; if (showProgress) { this.setState({ @@ -221,7 +258,7 @@ class CsvInputBar extends Component { } // Cancels an in progress upload - handleCancelInProgress() { + handleCancelInProgress(): void { const { onInProgress } = this.props; const { parser } = this.state; if (parser) { @@ -234,20 +271,20 @@ class CsvInputBar extends Component { onInProgress(false); } - openTable() { + openTable(): void { const { onOpenTable, onClose } = this.props; const { tableName } = this.state; onOpenTable(tableName); onClose(); } - handleQueryTypeChange(event) { + handleQueryTypeChange(event: ChangeEvent): void { this.setState({ - type: event.target.value, + type: event.target.value as keyof typeof CsvFormats.TYPES, }); } - render() { + render(): ReactElement { const { file, paste } = this.props; const { tableName, @@ -318,8 +355,8 @@ class CsvInputBar extends Component { className="progress-bar bg-primary" style={{ width: `${progressValue}%` }} aria-valuenow={progressValue} - aria-valuemin="0" - aria-valuemax="100" + aria-valuemin={0} + aria-valuemax={100} />
@@ -337,26 +374,4 @@ class CsvInputBar extends Component { } } -CsvInputBar.propTypes = { - session: PropTypes.shape({ - bindTableToVariable: PropTypes.func.isRequired, - mergeTables: PropTypes.func.isRequired, - }).isRequired, - onOpenTable: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired, - onUpdate: PropTypes.func.isRequired, - onError: PropTypes.func.isRequired, - file: PropTypes.instanceOf(File), - paste: PropTypes.string, - onInProgress: PropTypes.func.isRequired, - timeZone: PropTypes.string.isRequired, - unzip: PropTypes.func, -}; - -CsvInputBar.defaultProps = { - file: null, - paste: null, - unzip: null, -}; - export default CsvInputBar; diff --git a/packages/console/src/csv/CsvOverlay.jsx b/packages/console/src/csv/CsvOverlay.tsx similarity index 75% rename from packages/console/src/csv/CsvOverlay.jsx rename to packages/console/src/csv/CsvOverlay.tsx index 776992fade..8667e20ea0 100644 --- a/packages/console/src/csv/CsvOverlay.jsx +++ b/packages/console/src/csv/CsvOverlay.tsx @@ -1,19 +1,45 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { + ChangeEvent, + Component, + DragEvent, + MouseEvent, + ReactElement, + RefObject, +} from 'react'; import memoize from 'memoize-one'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { ContextActions, GLOBAL_SHORTCUTS } from '@deephaven/components'; +import { + ContextAction, + ContextActions, + GLOBAL_SHORTCUTS, +} from '@deephaven/components'; import { dhFileCsv, dhFileDownload, dhFileSpreadsheet, + IconDefinition, vsClippy, vsFileZip, vsTrash, vsWarning, } from '@deephaven/icons'; import './CsvOverlay.scss'; -import { TextUtils } from '@deephaven/utils'; +import { assertNotNull, TextUtils } from '@deephaven/utils'; + +interface CsvOverlayProps { + allowZip: boolean; + onFileOpened: (file: File | null) => void; + onPaste: (clipText: string) => void; + clearDragError: () => void; + dragError: string | null; + onError: (e: unknown) => void; + uploadInProgress: boolean; +} + +interface CsvOverlayState { + selectedFileName: string; + dropError: string | null; +} const PASTED_VALUES = 'pasted values'; @@ -26,12 +52,17 @@ const ZIP_EXTENSIONS = ['.zip']; /** * Overlay that is displayed when uploading a CSV file. */ -class CsvOverlay extends Component { +class CsvOverlay extends Component { + static defaultProps = { + allowZip: false, + dragError: null, + }; + static MULTIPLE_FILE_ERROR = 'Please select only one file'; static FILE_TYPE_ERROR = 'Filetype not supported.'; - static isValidDropItem(item) { + static isValidDropItem(item: DataTransferItem): boolean { return ( item && item.kind === 'file' && @@ -39,19 +70,19 @@ class CsvOverlay extends Component { ); } - static isValidExtension(name, allowZip = false) { + static isValidExtension(name: string, allowZip = false): boolean { return ( VALID_EXTENSIONS.some(ext => name.endsWith(ext)) || (allowZip && ZIP_EXTENSIONS.some(ext => name.endsWith(ext))) ); } - static handleDragOver(e) { + static handleDragOver(e: DragEvent): void { e.preventDefault(); e.stopPropagation(); } - static getIcon(fileName) { + static getIcon(fileName: string): IconDefinition { if (fileName === PASTED_VALUES) { return vsClippy; } @@ -64,7 +95,7 @@ class CsvOverlay extends Component { return dhFileSpreadsheet; } - constructor(props) { + constructor(props: CsvOverlayProps) { super(props); this.handleSelectFile = this.handleSelectFile.bind(this); @@ -83,27 +114,36 @@ class CsvOverlay extends Component { }; } - componentDidMount() { - this.divElem.current.addEventListener('paste', this.handlePasteEvent); - this.divElem.current.focus(); + componentDidMount(): void { + this.divElem.current?.addEventListener('paste', this.handlePasteEvent); + this.divElem.current?.focus(); } - componentWillUnmount() { - this.divElem.current.removeEventListener('paste', this.handlePasteEvent); + componentWillUnmount(): void { + this.divElem.current?.removeEventListener('paste', this.handlePasteEvent); } - handleSelectFile() { - this.fileElem.current.value = null; - this.fileElem.current.click(); + fileElem: RefObject; + + divElem: RefObject; + + handleSelectFile(): void { + if (this.fileElem.current) { + this.fileElem.current.value = ''; + this.fileElem.current?.click(); + } } - handleFiles(event) { + handleFiles(event: ChangeEvent): void { event.stopPropagation(); event.preventDefault(); - this.handleFile(event.target.files[0]); + const { files } = event.target; + if (files != null) { + this.handleFile(files[0]); + } } - handleDrop(e) { + handleDrop(e: DragEvent): void { const { allowZip, clearDragError, dragError } = this.props; e.preventDefault(); e.stopPropagation(); @@ -118,6 +158,7 @@ class CsvOverlay extends Component { } const file = e.dataTransfer.items[0].getAsFile(); + assertNotNull(file); if (CsvOverlay.isValidExtension(file.name, allowZip)) { this.handleFile(file); } else { @@ -127,7 +168,7 @@ class CsvOverlay extends Component { } } - unstageFile(event) { + unstageFile(event: MouseEvent): void { const { onFileOpened } = this.props; event.stopPropagation(); event.preventDefault(); @@ -138,7 +179,7 @@ class CsvOverlay extends Component { }); } - handleFile(file) { + handleFile(file: File): void { const { onFileOpened } = this.props; onFileOpened(file); this.setState({ @@ -147,7 +188,7 @@ class CsvOverlay extends Component { }); } - handleMenuPaste() { + handleMenuPaste(): void { const { onPaste, onError, uploadInProgress } = this.props; if (uploadInProgress) { return; @@ -161,16 +202,16 @@ class CsvOverlay extends Component { dropError: null, }); }) - .catch(e => onError(e)); + .catch((e: unknown) => onError(e)); } - handlePasteEvent(event) { + handlePasteEvent(event: ClipboardEvent): void { event.stopPropagation(); event.preventDefault(); this.handleMenuPaste(); } - makeContextMenuItems() { + makeContextMenuItems(): ContextAction[] { const { uploadInProgress } = this.props; return [ { @@ -195,7 +236,7 @@ class CsvOverlay extends Component { TextUtils.join(this.getValidExtensions(allowZip), 'or') ); - render() { + render(): ReactElement { const { allowZip, dragError, uploadInProgress } = this.props; const { selectedFileName, dropError } = this.state; const error = dragError || dropError; @@ -206,7 +247,7 @@ class CsvOverlay extends Component { className="csv-overlay fill-parent-absolute" onDragOver={CsvOverlay.handleDragOver} onDrop={this.handleDrop} - tabIndex="-1" + tabIndex={-1} > void; + session: IdeSession; + file: Blob | JSZipObject; + type: CsvTypes; + readHeaders: boolean; + onProgress: (progressValue: number) => boolean; + onError: (e: unknown) => void; + timeZone: string; + isZip: boolean; +} + /** * Parser a CSV file in chunks and returns a table handle for each chunk. */ class CsvParser { // Generates column names A-Z, AA-AZ, BA-BZ, etc... - static generateHeaders = numColumns => { + static generateHeaders = (numColumns: number): string[] => { const headers = []; for (let i = 0; i < numColumns; i += 1) { headers.push(CsvParser.generateHeaderRecursive(i)); @@ -22,7 +37,7 @@ class CsvParser { return headers; }; - static generateHeaderRecursive(n) { + static generateHeaderRecursive(n: number): string { let header = ''; let char = n; if (n >= 26) { @@ -44,7 +59,7 @@ class CsvParser { onError, timeZone, isZip, - }) { + }: CsvParserConstructor) { this.onFileCompleted = onFileCompleted; this.session = session; this.file = file; @@ -55,13 +70,12 @@ class CsvParser { this.onProgress = onProgress; this.onError = onError; this.tables = []; - this.headers = null; - this.types = null; this.chunks = 0; - this.totalChunks = isZip ? 0 : Math.ceil(file.size / Papa.LocalChunkSize); + this.totalChunks = isZip + ? 0 + : Math.ceil((file as Blob).size / Papa.LocalChunkSize); this.isComplete = false; this.zipProgress = 0; - this.consolidatedChunks = null; this.numConsolidated = 0; this.isCancelled = false; @@ -73,7 +87,7 @@ class CsvParser { this.config = { delimiter: type.delimiter, - newline: type.newline, + newline: type.newline as '\r\n' | '\n' | '\r' | undefined, escapeChar: type.escapeChar, dynamicTyping: false, error: this.handleError, @@ -84,11 +98,51 @@ class CsvParser { }; } - cancel() { + onFileCompleted: (tables: Table[]) => void; + + session: IdeSession; + + file: Blob | JSZipObject; + + isZip: boolean; + + type: CsvTypes; + + readHeaders: boolean; + + timeZone: string; + + onProgress: (progressValue: number) => boolean; + + onError: (e: unknown) => void; + + tables: Table[]; + + headers?: string[]; + + types?: string[]; + + chunks: number; + + totalChunks: number; + + isComplete: boolean; + + zipProgress: number; + + consolidatedChunks?: string[][]; + + numConsolidated: number; + + isCancelled: boolean; + + config: ParseLocalConfig; + + cancel(): void { this.isCancelled = true; } - transpose(numColumns, array) { + transpose(numColumns: number, array: string[][]): string[][] { const numRows = array.length; const columns = new Array(numColumns) .fill(null) @@ -105,18 +159,23 @@ class CsvParser { columns[c][r] = this.nullCheck(value); } } - return columns; + return columns as string[][]; } - nullCheck(value) { + nullCheck(value: string): string { return value === this.type.nullString ? '' : value; } - parse() { - const handleParseDone = types => { + parse(): void { + const handleParseDone = (types: string[]) => { const toParse = this.isZip - ? this.file.nodeStream('nodebuffer', this.handleNodeUpdate) - : this.file; + ? (this.file as JSZipObject).nodeStream( + // JsZip types are incorrect, thus the funny casting + // Actual parameter is 'nodebuffer' + 'nodebuffer' as 'nodestream', + this.handleNodeUpdate + ) + : (this.file as Blob); this.types = types; Papa.parse(toParse, this.config); }; @@ -135,7 +194,7 @@ class CsvParser { typeParser.parse(); } - handleChunk(result, parser) { + handleChunk(result: ParseResult, parser: Parser): void { const { readHeaders, onError, handleCreateTable, isZip, tables } = this; if (this.isCancelled) { log.debug2('CSV parser cancelled.'); @@ -153,7 +212,7 @@ class CsvParser { } } - let columns = []; + let columns: string[][] = []; try { columns = this.transpose(this.headers.length, data); if (isZip) { @@ -164,12 +223,12 @@ class CsvParser { this.chunks += 1; handleCreateTable(index, columns, parser); } - } catch (e) { + } catch (e: unknown) { onError(e); } } - consolidateChunks(columns, parser) { + consolidateChunks(columns: string[][], parser: Parser): void { if (!this.consolidatedChunks) { this.consolidatedChunks = columns.slice(); } else { @@ -185,17 +244,22 @@ class CsvParser { } } - uploadConsolidatedChunks(parser) { + uploadConsolidatedChunks(parser: Parser | null): void { const { handleCreateTable } = this; const index = this.chunks; this.chunks += 1; - const toUpload = this.consolidatedChunks.slice(); - this.consolidatedChunks = null; + const toUpload = this.consolidatedChunks?.slice(); + this.consolidatedChunks = undefined; this.numConsolidated = 0; + assertNotNull(toUpload); handleCreateTable(index, toUpload, parser); } - handleCreateTable(index, columns, parser) { + handleCreateTable( + index: number, + columns: string[][], + parser: Parser | null + ): void { const { session, tables, @@ -208,6 +272,8 @@ class CsvParser { if (parser) { parser.pause(); } + assertNotNull(this.headers); + assertNotNull(types); session .newTable(this.headers, types, columns, this.timeZone) .then(table => { @@ -245,7 +311,7 @@ class CsvParser { }); } - handleComplete(results) { + handleComplete(results: ParseResult): void { // results is undefined for a succesful parse, but has meta data for an abort if (!results || !results.meta.aborted) { this.isComplete = true; @@ -256,12 +322,12 @@ class CsvParser { } } - handleError(error) { + handleError(error: unknown): void { const { onError } = this; onError(error); } - handleNodeUpdate(metadata) { + handleNodeUpdate(metadata: { percent: number }): void { this.zipProgress = metadata.percent; } } diff --git a/packages/console/src/csv/CsvTypeParser.js b/packages/console/src/csv/CsvTypeParser.ts similarity index 71% rename from packages/console/src/csv/CsvTypeParser.js rename to packages/console/src/csv/CsvTypeParser.ts index b6d6470fb3..f64c7410c8 100644 --- a/packages/console/src/csv/CsvTypeParser.js +++ b/packages/console/src/csv/CsvTypeParser.ts @@ -1,6 +1,8 @@ +import type { JSZipObject } from 'jszip'; +import { assertNotNull } from '@deephaven/utils'; +import Papa, { Parser, ParseResult, ParseLocalConfig } from 'papaparse'; // Intentionally using isNaN rather than Number.isNaN /* eslint-disable no-restricted-globals */ -import Papa from 'papaparse'; import NewTableColumnTypes from './NewTableColumnTypes'; // Initially column types start al unknown @@ -16,7 +18,11 @@ const LOCAL_TIME_REGEX = /^([0-9]+T)?([0-9]+):([0-9]+)(:[0-9]+)?(?:\.[0-9]{1,9}) * Determines the type of each column in a CSV file by parsing it and looking at every value. */ class CsvTypeParser { - static determineType(value, type, nullString) { + static determineType( + value: string, + type: string, + nullString: string | null + ): string { if (!value || value === nullString) { // A null tells us nothing about the type return type; @@ -44,11 +50,13 @@ class CsvTypeParser { } // Allows for cusomt rules in addition to isNaN - static isNotParsableNumber(s) { - return isNaN(s) || s === 'Infinity' || s === '-Infinity'; + static isNotParsableNumber(s: string): boolean { + return ( + isNaN((s as unknown) as number) || s === 'Infinity' || s === '-Infinity' + ); } - static checkInteger(value) { + static checkInteger(value: string): string { const noCommas = value.replace(/,/g, ''); if (CsvTypeParser.isNotParsableNumber(noCommas)) { return NewTableColumnTypes.STRING; @@ -57,7 +65,7 @@ class CsvTypeParser { return CsvTypeParser.getNumberType(noCommas); } - static checkLong(value) { + static checkLong(value: string): string { const noCommas = value.replace(/,/g, ''); if (CsvTypeParser.isNotParsableNumber(noCommas)) { return NewTableColumnTypes.STRING; @@ -70,7 +78,7 @@ class CsvTypeParser { return NewTableColumnTypes.LONG; } - static checkDouble(value) { + static checkDouble(value: string): string { const noCommas = value.replace(/,/g, ''); if (CsvTypeParser.isNotParsableNumber(noCommas)) { return NewTableColumnTypes.STRING; @@ -79,7 +87,7 @@ class CsvTypeParser { return NewTableColumnTypes.DOUBLE; } - static checkBoolean(value) { + static checkBoolean(value: string): string { const lower = value.toLowerCase(); if (lower === 'true' || lower === 'false') { return NewTableColumnTypes.BOOLEAN; @@ -87,7 +95,7 @@ class CsvTypeParser { return NewTableColumnTypes.STRING; } - static checkDateTime(value) { + static checkDateTime(value: string): string { if (DATE_TIME_REGEX.test(value)) { return NewTableColumnTypes.DATE_TIME; } @@ -95,7 +103,7 @@ class CsvTypeParser { return NewTableColumnTypes.STRING; } - static checkLocalTime(value) { + static checkLocalTime(value: string): string { if (LOCAL_TIME_REGEX.test(value)) { return NewTableColumnTypes.LOCAL_TIME; } @@ -103,7 +111,7 @@ class CsvTypeParser { return NewTableColumnTypes.STRING; } - static getTypeFromUnknown(value) { + static getTypeFromUnknown(value: string): string { const noCommas = value.replace(/,/g, ''); if (CsvTypeParser.isNotParsableNumber(noCommas)) { const lower = value.toLowerCase(); @@ -125,7 +133,7 @@ class CsvTypeParser { return CsvTypeParser.getNumberType(noCommas); } - static getNumberType(value) { + static getNumberType(value: string): string { if (value.includes('.')) { return NewTableColumnTypes.DOUBLE; } @@ -146,16 +154,16 @@ class CsvTypeParser { } constructor( - onFileCompleted, - file, - readHeaders, - parentConfig, - nullString, - onProgress, - onError, - totalChunks, - isZip, - shouldTrim + onFileCompleted: (types: string[]) => void, + file: Blob | JSZipObject, + readHeaders: boolean, + parentConfig: ParseLocalConfig, + nullString: string | null, + onProgress: (progressValue: number) => boolean, + onError: (e: unknown) => void, + totalChunks: number, + isZip: boolean, + shouldTrim: boolean ) { this.onFileCompleted = onFileCompleted; this.file = file; @@ -163,7 +171,6 @@ class CsvTypeParser { this.nullString = nullString; this.onProgress = onProgress; this.onError = onError; - this.types = null; this.chunks = 0; this.totalChunks = totalChunks; this.isZip = isZip; @@ -183,14 +190,45 @@ class CsvTypeParser { }; } - parse() { + onFileCompleted: (types: string[]) => void; + + file: Blob | JSZipObject; + + readHeaders: boolean; + + nullString: string | null; + + onProgress: (progressValue: number) => boolean; + + onError: (e: unknown) => void; + + types?: string[]; + + chunks: number; + + totalChunks: number; + + isZip: boolean; + + shouldTrim: boolean; + + zipProgress: number; + + config: ParseLocalConfig; + + parse(): void { const toParse = this.isZip - ? this.file.nodeStream('nodebuffer', this.handleNodeUpdate) - : this.file; + ? (this.file as JSZipObject).nodeStream( + // JsZip types are incorrect, thus the funny casting + // Actual parameter is 'nodebuffer' + 'nodebuffer' as 'nodestream', + this.handleNodeUpdate + ) + : (this.file as Blob); Papa.parse(toParse, this.config); } - handleChunk(result, parser) { + handleChunk(result: ParseResult, parser: Parser): void { let { data } = result; if (!this.types) { if (!data || data.length === 0) { @@ -205,19 +243,24 @@ class CsvTypeParser { } } + assertNotNull(this.types); + + const cloneTypes = [...this.types]; + data.forEach(row => { - if (row.length >= this.types.length) { - for (let i = 0; i < this.types.length; i += 1) { - this.types[i] = CsvTypeParser.determineType( + if (row.length >= cloneTypes.length) { + for (let i = 0; i < cloneTypes.length; i += 1) { + cloneTypes[i] = CsvTypeParser.determineType( this.shouldTrim ? row[i].trim() : row[i], - this.types[i], + cloneTypes[i], this.nullString ); } + this.types = cloneTypes; } else { parser.abort(); this.onError( - `Error parsing CSV: Insufficient data in row.\nExpected length ${this.types.length} but found ${row.length}.\nRow = ${row}` + `Error parsing CSV: Insufficient data in row.\nExpected length ${cloneTypes.length} but found ${row.length}.\nRow = ${row}` ); } }); @@ -236,9 +279,10 @@ class CsvTypeParser { } } - handleComplete(results) { + handleComplete(results: ParseResult): void { const { types, onFileCompleted } = this; // results is undefined for a succesful parse, but has meta data for an abort + assertNotNull(types); if (!results || !results.meta.aborted) { onFileCompleted( types.map(type => @@ -248,12 +292,12 @@ class CsvTypeParser { } } - handleError(error) { + handleError(error: unknown): void { const { onError } = this; onError(error); } - handleNodeUpdate(metadata) { + handleNodeUpdate(metadata: { percent: number }): void { this.zipProgress = metadata.percent; } } diff --git a/packages/console/src/csv/NewTableColumnTypes.js b/packages/console/src/csv/NewTableColumnTypes.ts similarity index 100% rename from packages/console/src/csv/NewTableColumnTypes.js rename to packages/console/src/csv/NewTableColumnTypes.ts diff --git a/packages/console/src/index.js b/packages/console/src/index.ts similarity index 100% rename from packages/console/src/index.js rename to packages/console/src/index.ts diff --git a/packages/console/src/log/LogLevel.js b/packages/console/src/log/LogLevel.ts similarity index 100% rename from packages/console/src/log/LogLevel.js rename to packages/console/src/log/LogLevel.ts diff --git a/packages/console/src/log/LogLevelMenuItem.jsx b/packages/console/src/log/LogLevelMenuItem.tsx similarity index 63% rename from packages/console/src/log/LogLevelMenuItem.jsx rename to packages/console/src/log/LogLevelMenuItem.tsx index d637c57d27..ce1a4e4289 100644 --- a/packages/console/src/log/LogLevelMenuItem.jsx +++ b/packages/console/src/log/LogLevelMenuItem.tsx @@ -1,23 +1,31 @@ // Port of https://github.com/react-bootstrap/react-bootstrap/blob/master/src/Collapse.js -import React, { PureComponent } from 'react'; +import React, { PureComponent, ReactElement } from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { UISwitch } from '@deephaven/components'; import './LogLevelMenuItem.scss'; -class LogLevelMenuItem extends PureComponent { - constructor(props) { +interface LogLevelMenuItemProps { + logLevel: string; + on: boolean; + onClick: (logLevel: string) => void; +} + +class LogLevelMenuItem extends PureComponent< + LogLevelMenuItemProps, + Record +> { + constructor(props: LogLevelMenuItemProps) { super(props); this.handleSwitchClick = this.handleSwitchClick.bind(this); } - handleSwitchClick() { + handleSwitchClick(): void { const { logLevel, onClick } = this.props; onClick(logLevel); } - render() { + render(): ReactElement { const { logLevel, on } = this.props; return (
@@ -28,10 +36,4 @@ class LogLevelMenuItem extends PureComponent { } } -LogLevelMenuItem.propTypes = { - logLevel: PropTypes.string.isRequired, - on: PropTypes.bool.isRequired, - onClick: PropTypes.func.isRequired, -}; - export default LogLevelMenuItem; diff --git a/packages/console/src/log/LogView.jsx b/packages/console/src/log/LogView.tsx similarity index 75% rename from packages/console/src/log/LogView.jsx rename to packages/console/src/log/LogView.tsx index 132a643e69..7cbb2bbeee 100644 --- a/packages/console/src/log/LogView.jsx +++ b/packages/console/src/log/LogView.tsx @@ -1,18 +1,27 @@ -import React, { PureComponent } from 'react'; +import React, { PureComponent, ReactElement } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { DropdownMenu, Tooltip } from '@deephaven/components'; +import { DropdownActions, DropdownMenu, Tooltip } from '@deephaven/components'; import { vsGear, dhTrashUndo } from '@deephaven/icons'; -import { PropTypes as APIPropTypes } from '@deephaven/jsapi-shim'; +import { assertNotNull } from '@deephaven/utils'; +import { IdeSession, LogItem } from '@deephaven/jsapi-shim'; +import { Placement } from 'popper.js'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; import ConsoleUtils from '../common/ConsoleUtils'; import LogLevel from './LogLevel'; import './LogView.scss'; import LogLevelMenuItem from './LogLevelMenuItem'; +interface LogViewProps { + session: IdeSession; +} + +interface LogViewState { + shownLogLevels: Record; +} /** * Log view contents. Uses a monaco editor to display/search the contents of the log. */ -class LogView extends PureComponent { +class LogView extends PureComponent { static DefaultLogLevels = [ LogLevel.STDOUT, LogLevel.ERROR, @@ -40,13 +49,13 @@ class LogView extends PureComponent { static truncateSize = 65536; - static getLogText(logItem) { + static getLogText(logItem: LogItem): string { const date = new Date(logItem.micros / 1000); const timestamp = ConsoleUtils.formatTimestamp(date); return `${timestamp} ${logItem.logLevel} ${logItem.message}`; } - constructor(props) { + constructor(props: LogViewProps) { super(props); this.handleClearClick = this.handleClearClick.bind(this); @@ -57,11 +66,9 @@ class LogView extends PureComponent { this.handleResize = this.handleResize.bind(this); this.handleToggleAllClick = this.handleToggleAllClick.bind(this); - this.cancelListener = null; - this.editor = null; - this.editorContainer = null; this.logLevelMenuItems = {}; - this.flushTimer = null; + + this.editorContainer = null; this.bufferedMessages = []; this.messages = []; @@ -71,7 +78,7 @@ class LogView extends PureComponent { }; } - componentDidMount() { + componentDidMount(): void { this.resetLogLevels(); this.initMonaco(); this.startListening(); @@ -79,7 +86,7 @@ class LogView extends PureComponent { window.addEventListener('resize', this.handleResize); } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(prevProps: LogViewProps, prevState: LogViewState): void { this.updateDimensions(); const { shownLogLevels } = this.state; @@ -96,7 +103,7 @@ class LogView extends PureComponent { } } - componentWillUnmount() { + componentWillUnmount(): void { this.stopFlushTimer(); this.stopListening(); this.destroyMonaco(); @@ -104,7 +111,21 @@ class LogView extends PureComponent { window.removeEventListener('resize', this.handleResize); } - getMenuActions(shownLogLevels) { + cancelListener?: () => void | null; + + editor?: monaco.editor.IStandaloneCodeEditor; + + editorContainer: HTMLDivElement | null; + + logLevelMenuItems: Record; + + flushTimer?: ReturnType; + + bufferedMessages: LogItem[]; + + messages: LogItem[]; + + getMenuActions(shownLogLevels: Record): DropdownActions { const actions = []; actions.push({ @@ -163,8 +184,8 @@ class LogView extends PureComponent { return actions; } - resetLogLevels() { - const shownLogLevels = {}; + resetLogLevels(): void { + const shownLogLevels: Record = {}; for (let i = 0; i < LogView.AllLogLevels.length; i += 1) { const logLevel = LogView.AllLogLevels[i]; const isEnabled = LogView.DefaultLogLevels.indexOf(logLevel) >= 0; @@ -174,28 +195,30 @@ class LogView extends PureComponent { this.setState({ shownLogLevels }); } - startListening() { + startListening(): void { const { session } = this.props; this.cancelListener = session.onLogMessage(this.handleLogMessage); } - stopListening() { + stopListening(): void { if (this.cancelListener != null) { this.cancelListener(); - this.cancelListener = null; + this.cancelListener = undefined; } } - initMonaco() { + initMonaco(): void { + assertNotNull(this.editorContainer); this.editor = monaco.editor.create(this.editorContainer, { - copyWithSyntaxHighlighting: 'false', + copyWithSyntaxHighlighting: false, fixedOverflowWidgets: true, folding: false, fontFamily: 'Fira Mono', glyphMargin: false, language: 'log', lineDecorationsWidth: 0, - lineNumbers: '', + // I commented this out since '' is not a valid parameter for line Numbers + // lineNumbers: '', lineNumbersMinChars: 0, minimap: { enabled: false }, readOnly: true, @@ -207,18 +230,20 @@ class LogView extends PureComponent { // When find widget is open, escape key closes it. // Instead, capture it and do nothing. Same for shift-escape. - this.editor.addCommand(monaco.KeyCode.Escape, () => {}); + this.editor.addCommand(monaco.KeyCode.Escape, () => undefined); this.editor.addCommand( // eslint-disable-next-line no-bitwise monaco.KeyMod.Shift | monaco.KeyCode.Escape, - () => {} + () => undefined ); // Restore regular escape to clear selection, when editorText has focus. this.editor.addCommand( monaco.KeyCode.Escape, () => { - this.editor.setPosition(this.editor.getPosition()); + const position = this.editor?.getPosition(); + assertNotNull(position); + this.editor?.setPosition(position); }, 'findWidgetVisible && editorTextFocus' ); @@ -227,28 +252,30 @@ class LogView extends PureComponent { // eslint-disable-next-line no-bitwise monaco.KeyMod.Shift | monaco.KeyCode.Escape, () => { - this.editor.setPosition(this.editor.getPosition()); + const position = this.editor?.getPosition(); + assertNotNull(position); + this.editor?.setPosition(position); }, 'findWidgetVisible && editorTextFocus' ); } - destroyMonaco() { + destroyMonaco(): void { if (this.editor) { this.editor.dispose(); - this.editor = null; + this.editor = undefined; } } - triggerFindWidget() { + triggerFindWidget(): void { // The actions.find action can no longer be triggered when the editor is not in focus, with monaco 0.22.x. // As a workaround, just focus the editor before triggering the action // https://github.com/microsoft/monaco-editor/issues/2355 - this.editor.focus(); - this.editor.trigger('keyboard', 'actions.find'); + this.editor?.focus(); + this.editor?.trigger('keyboard', 'actions.find', undefined); } - toggleAll() { + toggleAll(): void { const { shownLogLevels } = this.state; let isAllEnabled = true; for (let i = 0; i < LogView.AllLogLevels.length; i += 1) { @@ -262,7 +289,7 @@ class LogView extends PureComponent { if (isAllEnabled) { this.setState({ shownLogLevels: {} }); } else { - const updatedLogLevels = {}; + const updatedLogLevels: Record = {}; for (let i = 0; i < LogView.AllLogLevels.length; i += 1) { const logLevel = LogView.AllLogLevels[i]; updatedLogLevels[logLevel] = true; @@ -271,28 +298,30 @@ class LogView extends PureComponent { } } - toggleLogLevel(logLevel) { + toggleLogLevel(logLevel: string): void { const { shownLogLevels } = this.state; const isEnabled = shownLogLevels[logLevel]; - const updatedLogLevels = {}; + const updatedLogLevels: Record = {}; updatedLogLevels[logLevel] = !isEnabled; this.updateLogLevels(updatedLogLevels); } - updateLogLevels(updatedLogLevels) { + updateLogLevels(updatedLogLevels: Record): void { let { shownLogLevels } = this.state; shownLogLevels = { ...shownLogLevels, ...updatedLogLevels }; this.setState({ shownLogLevels }); } - appendLogText(text) { + appendLogText(text: string): void { if (!this.editor) { return; } const model = this.editor.getModel(); - let line = model.getLineCount(); - let column = model.getLineLength(line); + let line = model?.getLineCount(); + assertNotNull(line); + let column = model?.getLineLength(line); + assertNotNull(column); const isBottomVisible = this.isBottomVisible(); const edits = []; @@ -322,17 +351,19 @@ class LogView extends PureComponent { forceMoveMarkers: true, }); - model.applyEdits(edits); + model?.applyEdits(edits); if (isBottomVisible) { - this.editor.revealLine(model.getLineCount(), 1); + const lineCount = model?.getLineCount(); + assertNotNull(lineCount); + this.editor.revealLine(lineCount, 1); } } /** * Refresh the contents of the log component with the updated filter text */ - refreshLogText() { + refreshLogText(): void { if (!this.editor) { return; } @@ -360,7 +391,8 @@ class LogView extends PureComponent { this.editor.setValue(text); if (isBottomVisible) { - const line = this.editor.getModel().getLineCount(); + const line = this.editor.getModel()?.getLineCount(); + assertNotNull(line); this.editor.revealLine(line, 1); } @@ -368,7 +400,7 @@ class LogView extends PureComponent { this.bufferedMessages = []; } - truncateLogIfNecessary() { + truncateLogIfNecessary(): void { if (this.messages.length > LogView.maxLogSize) { this.messages = this.messages.splice( this.messages.length - LogView.truncateSize @@ -376,28 +408,30 @@ class LogView extends PureComponent { } } - scrollToBottom() { + scrollToBottom(): void { if (!this.editor) { return; } - const line = this.editor.getModel().getLineCount(); + const line = this.editor?.getModel?.()?.getLineCount(); + assertNotNull(line); this.editor.revealLine(line, 1); } - isBottomVisible() { + isBottomVisible(): boolean { if (!this.editor) { return true; } const model = this.editor.getModel(); - const line = model.getLineCount(); + const line = model?.getLineCount(); + assertNotNull(line); return this.isLineVisible(line); } - isLineVisible(line) { - const visibleRanges = this.editor.getVisibleRanges(); + isLineVisible(line: number): boolean { + const visibleRanges = this.editor?.getVisibleRanges(); if (visibleRanges == null || visibleRanges.length === 0) { return false; } @@ -413,12 +447,12 @@ class LogView extends PureComponent { } /** Checks if the given log message is visible with the current filters */ - isLogItemVisible(message) { + isLogItemVisible(message: LogItem): boolean { const { shownLogLevels } = this.state; return shownLogLevels[message.logLevel]; } - flush() { + flush(): void { let text = ''; for (let i = 0; i < this.bufferedMessages.length; i += 1) { const message = this.bufferedMessages[i]; @@ -434,7 +468,7 @@ class LogView extends PureComponent { this.appendLogText(text); } - queue(message) { + queue(message: LogItem): void { this.bufferedMessages.push(message); if (this.bufferedMessages.length === 1) { this.flushTimer = setTimeout( @@ -444,34 +478,34 @@ class LogView extends PureComponent { } } - stopFlushTimer() { + stopFlushTimer(): void { if (this.flushTimer) { clearTimeout(this.flushTimer); - this.flushTimer = null; + this.flushTimer = undefined; } } - updateDimensions() { + updateDimensions(): void { if (this.editor) { this.editor.layout(); } } - handleClearClick() { + handleClearClick(): void { this.clearLogs(); } - clearLogs() { + clearLogs(): void { this.messages = []; this.refreshLogText(); } - handleFlushTimeout() { + handleFlushTimeout(): void { this.stopFlushTimer(); this.flush(); } - handleLogMessage(message) { + handleLogMessage(message: LogItem): void { this.messages.push(message); if (this.editor && this.isLogItemVisible(message)) { @@ -479,24 +513,24 @@ class LogView extends PureComponent { } } - handleMenuItemClick(logLevel) { + handleMenuItemClick(logLevel: string): void { this.toggleLogLevel(logLevel); } - handleResetClick() { + handleResetClick(): void { this.resetLogLevels(); } - handleResize() { + handleResize(): void { this.updateDimensions(); } - handleToggleAllClick() { + handleToggleAllClick(): void { this.toggleAll(); } - render() { - const popperOptions = { placement: 'bottom-end' }; + render(): ReactElement { + const popperOptions = { placement: 'bottom-end' as Placement }; const { shownLogLevels } = this.state; const actions = this.getMenuActions(shownLogLevels); return ( @@ -534,8 +568,4 @@ class LogView extends PureComponent { } } -LogView.propTypes = { - session: APIPropTypes.IdeSession.isRequired, -}; - export default LogView; diff --git a/packages/console/src/monaco/MonacoCompletionProvider.jsx b/packages/console/src/monaco/MonacoCompletionProvider.tsx similarity index 75% rename from packages/console/src/monaco/MonacoCompletionProvider.jsx rename to packages/console/src/monaco/MonacoCompletionProvider.tsx index 9952ba987f..f79f54acde 100644 --- a/packages/console/src/monaco/MonacoCompletionProvider.jsx +++ b/packages/console/src/monaco/MonacoCompletionProvider.tsx @@ -2,25 +2,32 @@ * Completion provider for a code session */ import { PureComponent } from 'react'; -import PropTypes from 'prop-types'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; import Log from '@deephaven/log'; +import { IdeSession } from '@deephaven/jsapi-shim'; const log = Log.module('MonacoCompletionProvider'); +interface MonacoCompletionProviderProps { + model: monaco.editor.ITextModel; + session: IdeSession; + language: string; +} + /** * Registers a completion provider with monaco for the language and session provided. */ -class MonacoCompletionProvider extends PureComponent { - constructor(props) { +class MonacoCompletionProvider extends PureComponent< + MonacoCompletionProviderProps, + Record +> { + constructor(props: MonacoCompletionProviderProps) { super(props); this.handleCompletionRequest = this.handleCompletionRequest.bind(this); - - this.registeredCompletionProvider = null; } - componentDidMount() { + componentDidMount(): void { const { language } = this.props; this.registeredCompletionProvider = monaco.languages.registerCompletionItemProvider( language, @@ -31,11 +38,17 @@ class MonacoCompletionProvider extends PureComponent { ); } - componentWillUnmount() { - this.registeredCompletionProvider.dispose(); + componentWillUnmount(): void { + this.registeredCompletionProvider?.dispose(); } - handleCompletionRequest(model, position, context) { + registeredCompletionProvider?: monaco.IDisposable; + + handleCompletionRequest( + model: monaco.editor.ITextModel, + position: monaco.Position, + context: monaco.languages.CompletionContext + ): monaco.languages.ProviderResult { const { model: propModel, session } = this.props; if (model !== propModel) { return null; @@ -54,11 +67,11 @@ class MonacoCompletionProvider extends PureComponent { context, }; - let completionItems = session.getCompletionItems(params); + const completionItems = session.getCompletionItems(params); log.debug('Completion items received: ', params, completionItems); - completionItems = completionItems + const monacoCompletionItems = completionItems .then(items => { // Annoying that the LSP protocol returns completion items with a range that's slightly different than what Monaco expects // Need to remap the items here @@ -104,23 +117,17 @@ class MonacoCompletionProvider extends PureComponent { suggestions, }; }) - .catch(error => { + .catch((error: unknown) => { log.error('There was an error retrieving completion items', error); return { suggestions: [] }; }); - return completionItems; + return monacoCompletionItems; } - render() { + render(): null { return null; } } -MonacoCompletionProvider.propTypes = { - model: PropTypes.shape({ uri: PropTypes.shape({}) }).isRequired, - session: PropTypes.shape({ getCompletionItems: PropTypes.func }).isRequired, - language: PropTypes.string.isRequired, -}; - export default MonacoCompletionProvider; diff --git a/packages/console/src/monaco/MonacoUtils.js b/packages/console/src/monaco/MonacoUtils.ts similarity index 88% rename from packages/console/src/monaco/MonacoUtils.js rename to packages/console/src/monaco/MonacoUtils.ts index 5e05e13e53..7be77a93dd 100644 --- a/packages/console/src/monaco/MonacoUtils.js +++ b/packages/console/src/monaco/MonacoUtils.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ /** * Exports a function for initializing monaco with the deephaven theme/config */ @@ -5,6 +6,10 @@ // Default list of features here: https://github.com/microsoft/monaco-editor-webpack-plugin // Mapping to paths here: https://github.com/microsoft/monaco-editor-webpack-plugin/blob/main/src/features.ts // Importing this way rather than using the plugin because I don't want to hook up react-app-rewired for the build + +import { Shortcut } from '@deephaven/components'; +import { IdeSession } from '@deephaven/jsapi-shim'; +import { assertNotNull } from '@deephaven/utils'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; import 'monaco-editor/esm/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.js'; import 'monaco-editor/esm/vs/editor/contrib/anchorSelect/anchorSelect.js'; @@ -57,7 +62,9 @@ import 'monaco-editor/esm/vs/editor/contrib/wordHighlighter/wordHighlighter.js'; import 'monaco-editor/esm/vs/editor/contrib/wordOperations/wordOperations.js'; import 'monaco-editor/esm/vs/editor/contrib/wordPartOperations/wordPartOperations.js'; import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution.js'; +// @ts-ignore import { KeyCodeUtils } from 'monaco-editor/esm/vs/base/common/keyCodes.js'; +// @ts-ignore import { KeyMod } from 'monaco-editor/esm/vs/editor/common/standalone/standaloneBase.js'; import Log from '@deephaven/log'; import MonacoTheme from './MonacoTheme.module.scss'; @@ -70,7 +77,7 @@ import LogLang from './lang/log'; const log = Log.module('MonacoUtils'); class MonacoUtils { - static init() { + static init(): void { log.debug('Initializing Monaco...'); const { registerLanguages, removeHashtag } = MonacoUtils; @@ -193,11 +200,11 @@ class MonacoUtils { * Monaco expects colors to be the value only, no hashtag. * @param {String} color The hex color string to remove the hashtag from, eg. '#ffffff' */ - static removeHashtag(color) { + static removeHashtag(color: string): string { return color.substring(1); } - static registerLanguages(languages) { + static registerLanguages(languages): void { // First override the default loader for any language we have a custom definition for // https://github.com/Microsoft/monaco-editor/issues/252#issuecomment-482786867 const languageIds = languages.map(({ id }) => id); @@ -208,7 +215,7 @@ class MonacoUtils { const language = languageParam; log.debug2('Overriding default language loader:', language.id); language.loader = () => ({ - then: () => {}, + then: () => undefined, }); }); @@ -218,7 +225,7 @@ class MonacoUtils { }); } - static registerLanguage(language) { + static registerLanguage(language): void { log.debug2('Registering language: ', language.id); monaco.languages.register(language); @@ -230,21 +237,28 @@ class MonacoUtils { /** * Set EOL preference for the editor - * @param {monaco.editor.IEditor} editor The editor to set the EOL for - * @param {monaco.editor.EndOfLineSequence} eolSequence EOL sequence + * @param editor The editor to set the EOL for + * @param eolSequence EOL sequence */ - static setEOL(editor, eolSequence = monaco.editor.EndOfLineSequence.LF) { - editor.getModel().setEOL(eolSequence); + static setEOL( + editor: monaco.editor.IStandaloneCodeEditor, + eolSequence = monaco.editor.EndOfLineSequence.LF + ): void { + editor.getModel()?.setEOL(eolSequence); } /** * Links an editor with a provided session to provide completion items. - * @param {dh.IdeSession} session The IdeSession to link - * @param {monaco.editor.IEditor} editor The editor to link the session to + * @param session The IdeSession to link + * @param editor The editor to link the session to * @return A cleanup function for disposing of the created listeners */ - static openDocument(editor, session) { + static openDocument( + editor: monaco.editor.IStandaloneCodeEditor, + session: IdeSession + ): monaco.IDisposable { const model = editor.getModel(); + assertNotNull(model); const didOpenDocumentParams = { textDocument: { uri: `${model.uri}`, @@ -298,8 +312,12 @@ class MonacoUtils { return dispose; } - static closeDocument(editor, session) { + static closeDocument( + editor: monaco.editor.IStandaloneCodeEditor, + session: IdeSession + ): void { const model = editor.getModel(); + assertNotNull(model); const didCloseDocumentParams = { textDocument: { uri: `${model.uri}`, @@ -312,13 +330,16 @@ class MonacoUtils { * Register a paste handle to clean up any garbage code pasted. * Most of this comes from copying from Slack, which has a bad habit of injecting their own characters in your code snippets. * I've emailed Slack about the issue and they're working on it. I can't reference a ticket number because their ticket system is entirely internally facing. - * @param {Monaco.editor} editor The editor the register the paste handler for + * @param editor The editor the register the paste handler for */ - static registerPasteHandler(editor) { + static registerPasteHandler( + editor: monaco.editor.IStandaloneCodeEditor + ): void { editor.onDidPaste(pasteEvent => { const smartQuotes = /“|”/g; const invalidChars = /\u200b/g; const editorModel = editor.getModel(); + assertNotNull(editorModel); const pastedText = editorModel.getValueInRange(pasteEvent.range); if (smartQuotes.test(pastedText) || invalidChars.test(pastedText)) { editorModel.applyEdits([ @@ -333,7 +354,7 @@ class MonacoUtils { }); } - static isMacPlatform() { + static isMacPlatform(): boolean { const { platform } = window.navigator; return platform.startsWith('Mac'); } @@ -341,9 +362,11 @@ class MonacoUtils { /** * Remove any keybindings which are used for our own shortcuts. * This allows the key events to bubble up so a component higher up can capture them - * @param {Monaco.editor} editor The editor to remove the keybindings from + * @param editor The editor to remove the keybindings from */ - static removeConflictingKeybindings(editor) { + static removeConflictingKeybindings( + editor: monaco.editor.IStandaloneCodeEditor + ): void { // Multi-mod key events have a specific order // E.g. ctrl+alt+UpArrow is not found, but alt+ctrl+UpArrow is found // meta is WindowsKey on Windows and cmd on Mac @@ -363,7 +386,9 @@ class MonacoUtils { keybindings.forEach(keybinding => MonacoUtils.removeKeybinding( editor, - MonacoUtils.isMacPlatform() ? keybinding.mac : keybinding.windows + (MonacoUtils.isMacPlatform() + ? keybinding.mac + : keybinding.windows) as string ) ); } catch (err) { @@ -381,28 +406,35 @@ class MonacoUtils { * https://github.com/microsoft/monaco-editor/issues/287#issuecomment-331447475 * The issue for an API for this has apparently been open since 2016. Link below * https://github.com/microsoft/monaco-editor/issues/102 - * @param {Monaco.editor} editor The editor to remove the keybinding from - * @param {string} keybinding The key string to remove. E.g. 'ctrl+C' for copy on Windows + * @param editor The editor to remove the keybinding from + * @param keybinding The key string to remove. E.g. 'ctrl+C' for copy on Windows */ - static removeKeybinding(editor, keybinding) { + static removeKeybinding( + editor: monaco.editor.IStandaloneCodeEditor, + keybinding: string + ): void { if (!keybinding) { return; } /* eslint-disable no-underscore-dangle */ // It's possible a single keybinding has multiple commands depending on context + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore const keybindings = editor._standaloneKeybindingService ._getResolver() ._map.get(keybinding); if (keybindings) { - keybindings.forEach(elem => { + keybindings.forEach((elem: { command: unknown }) => { log.debug2( `Removing Monaco keybinding ${keybinding} for ${elem.command}` ); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore editor._standaloneKeybindingService.addDynamicKeybinding( `-${elem.command}`, null, - () => {} + () => undefined ); }); } else { @@ -411,7 +443,7 @@ class MonacoUtils { /* eslint-enable no-underscore-dangle */ } - static getMonacoKeyCodeFromShortcut(shortcut) { + static getMonacoKeyCodeFromShortcut(shortcut: Shortcut): number { const { keyState } = shortcut; const { keyValue } = keyState; if (keyValue === null) { diff --git a/packages/console/src/monaco/index.js b/packages/console/src/monaco/index.ts similarity index 100% rename from packages/console/src/monaco/index.js rename to packages/console/src/monaco/index.ts diff --git a/packages/console/src/monaco/lang/db.js b/packages/console/src/monaco/lang/db.ts similarity index 100% rename from packages/console/src/monaco/lang/db.js rename to packages/console/src/monaco/lang/db.ts diff --git a/packages/console/src/monaco/lang/groovy.js b/packages/console/src/monaco/lang/groovy.ts similarity index 100% rename from packages/console/src/monaco/lang/groovy.js rename to packages/console/src/monaco/lang/groovy.ts diff --git a/packages/console/src/monaco/lang/log.js b/packages/console/src/monaco/lang/log.ts similarity index 100% rename from packages/console/src/monaco/lang/log.js rename to packages/console/src/monaco/lang/log.ts diff --git a/packages/console/src/monaco/lang/python.js b/packages/console/src/monaco/lang/python.ts similarity index 100% rename from packages/console/src/monaco/lang/python.js rename to packages/console/src/monaco/lang/python.ts diff --git a/packages/console/src/monaco/lang/scala.js b/packages/console/src/monaco/lang/scala.ts similarity index 100% rename from packages/console/src/monaco/lang/scala.js rename to packages/console/src/monaco/lang/scala.ts diff --git a/packages/console/src/notebook/Editor.jsx b/packages/console/src/notebook/Editor.tsx similarity index 62% rename from packages/console/src/notebook/Editor.jsx rename to packages/console/src/notebook/Editor.tsx index 451aafee0e..6a80c12808 100644 --- a/packages/console/src/notebook/Editor.jsx +++ b/packages/console/src/notebook/Editor.tsx @@ -1,61 +1,79 @@ /** * Editor editor for large blocks of code */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { Component, ReactElement } from 'react'; import classNames from 'classnames'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; +import { assertNotNull } from '@deephaven/utils'; import MonacoUtils from '../monaco/MonacoUtils'; -class Editor extends Component { - constructor(props) { +interface EditorProps { + className: string; + onEditorInitialized: (editor: monaco.editor.IStandaloneCodeEditor) => void; + onEditorWillDestroy: (editor: monaco.editor.IStandaloneCodeEditor) => void; + settings: Record; +} + +class Editor extends Component> { + static defaultProps = { + className: 'fill-parent-absolute', + onEditorInitialized: (): void => undefined, + onEditorWillDestroy: (): void => undefined, + settings: {}, + }; + + constructor(props: EditorProps) { super(props); this.handleResize = this.handleResize.bind(this); this.container = null; - this.editor = null; - this.state = {}; } - componentDidMount() { + componentDidMount(): void { this.initEditor(); window.addEventListener('resize', this.handleResize); } - componentWillUnmount() { + componentWillUnmount(): void { window.removeEventListener('resize', this.handleResize); this.destroyEditor(); } - setLanguage(language) { + container: HTMLDivElement | null; + + editor?: monaco.editor.IStandaloneCodeEditor; + + setLanguage(language: string): void { if (this.editor) { - monaco.editor.setModelLanguage(this.editor.getModel(), language); + const model = this.editor.getModel(); + assertNotNull(model); + monaco.editor.setModelLanguage(model, language); } } - handleResize() { + handleResize(): void { this.updateDimensions(); } - toggleFind() { + toggleFind(): void { if (this.editor) { // The actions.find action can no longer be triggered when the editor is not in focus, with monaco 0.22.x. // As a workaround, just focus the editor before triggering the action // https://github.com/microsoft/monaco-editor/issues/2355 this.editor.focus(); - this.editor.trigger('toggleFind', 'actions.find'); + this.editor.trigger('toggleFind', 'actions.find', undefined); } } - updateDimensions() { - this.editor.layout(); + updateDimensions(): void { + this.editor?.layout(); } - initEditor() { + initEditor(): void { const { onEditorInitialized } = this.props; let { settings } = this.props; settings = { @@ -73,6 +91,7 @@ class Editor extends Component { wordWrap: 'off', ...settings, }; + assertNotNull(this.container); this.editor = monaco.editor.create(this.container, settings); this.editor.addAction({ id: 'find', @@ -81,14 +100,13 @@ class Editor extends Component { // eslint-disable-next-line no-bitwise monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_F, ], - precondition: null, - keybindingContext: null, + precondition: undefined, + keybindingContext: undefined, contextMenuGroupId: 'navigation', contextMenuOrder: 1.0, run: () => { this.toggleFind(); - return null; }, }); this.editor.layout(); @@ -97,14 +115,15 @@ class Editor extends Component { onEditorInitialized(this.editor); } - destroyEditor() { + destroyEditor(): void { const { onEditorWillDestroy } = this.props; + assertNotNull(this.editor); onEditorWillDestroy(this.editor); this.editor.dispose(); - this.editor = null; + this.editor = undefined; } - render() { + render(): ReactElement { const { className } = this.props; return (
{}, - onEditorWillDestroy: () => {}, - settings: {}, -}; - export default Editor; diff --git a/packages/console/src/notebook/ScriptEditorUtils.js b/packages/console/src/notebook/ScriptEditorUtils.js deleted file mode 100644 index 809913fe97..0000000000 --- a/packages/console/src/notebook/ScriptEditorUtils.js +++ /dev/null @@ -1,36 +0,0 @@ -const LANGUAGES = { groovy: 'Groovy', python: 'Python', scala: 'Scala' }; - -class ScriptEditorUtils { - /** Get PQ script language from Monaco language - * @param {string} language Monaco language - * @returns {string} PQ script language - */ - static normalizeScriptLanguage(language) { - return LANGUAGES[language] || null; - } - - /** - * Get a tooltip for disabled button based on the session status and language - * @param {boolean} isSessionConnected True if console session connected - * @param {boolean} isLanguageMatching True if the script language is matching the session language - * @param {string} scriptLanguageLabel Language label to use in the tooltip message - * @param {string} buttonLabel Button label to use in the tooltip message - * @returns {string} Tooltip message or `null` if the session is connected and language is matching - */ - static getDisabledRunTooltip( - isSessionConnected, - isLanguageMatching, - scriptLanguageLabel, - buttonLabel - ) { - if (!isSessionConnected) { - return `Console session not connected – ${buttonLabel} disabled`; - } - if (!isLanguageMatching) { - return `${scriptLanguageLabel} doesn't match the session language – ${buttonLabel} disabled`; - } - return null; - } -} - -export default ScriptEditorUtils; diff --git a/packages/console/src/notebook/ScriptEditorUtils.ts b/packages/console/src/notebook/ScriptEditorUtils.ts new file mode 100644 index 0000000000..ae0bcc7ab8 --- /dev/null +++ b/packages/console/src/notebook/ScriptEditorUtils.ts @@ -0,0 +1,40 @@ +const LANGUAGES = { + groovy: 'Groovy', + python: 'Python', + scala: 'Scala', +} as const; + +class ScriptEditorUtils { + /** Get PQ script language from Monaco language + * @paramlanguage Monaco language + * @returns PQ script language + */ + static normalizeScriptLanguage(language: keyof typeof LANGUAGES): string { + return LANGUAGES[language] || null; + } + + /** + * Get a tooltip for disabled button based on the session status and language + * @param isSessionConnected True if console session connected + * @param isLanguageMatching True if the script language is matching the session language + * @param scriptLanguageLabel Language label to use in the tooltip message + * @param buttonLabel Button label to use in the tooltip message + * @returns Tooltip message or `null` if the session is connected and language is matching + */ + static getDisabledRunTooltip( + isSessionConnected: boolean, + isLanguageMatching: boolean, + scriptLanguageLabel: string, + buttonLabel: string + ): string | null { + if (!isSessionConnected) { + return `Console session not connected – ${buttonLabel} disabled`; + } + if (!isLanguageMatching) { + return `${scriptLanguageLabel} doesn't match the session language – ${buttonLabel} disabled`; + } + return null; + } +} + +export default ScriptEditorUtils; diff --git a/packages/jsapi-shim/src/dh.types.ts b/packages/jsapi-shim/src/dh.types.ts index c3bcb78867..fce57a9434 100644 --- a/packages/jsapi-shim/src/dh.types.ts +++ b/packages/jsapi-shim/src/dh.types.ts @@ -26,6 +26,7 @@ export interface dh { Column: Column; SearchDisplayMode?: SearchDisplayModeStatic; RangeSet: RangeSet; + IdeSession: IdeSessionStatic; } const VariableType = { @@ -43,12 +44,64 @@ export interface VariableDefinition< T extends VariableTypeUnion = VariableTypeUnion > { type: T; + + /** + * @deprecated + */ name?: string; + title?: string; id?: string; } -export interface IdeSession { +export interface LogItem { + micros: number; + logLevel: string; + message: string; +} + +export interface VariableChanges { + created: VariableDefinition[]; + updated: VariableDefinition[]; + removed: VariableDefinition[]; +} + +export interface CommandResult { + changes: VariableChanges; + error: string; +} + +export interface Position { + line: number; + character: number; +} + +export interface DocumentRange { + start: Position; + end: Position; +} + +export interface TextEdit { + text: string; + range: DocumentRange; +} + +export interface CompletionItem { + label: string; + kind: number; + detail: string; + documentation: string; + sortText: string; + filterText: string; + textEdit: TextEdit; + insertTextFormat: number; +} + +export interface IdeSessionStatic { + EVENT_COMMANDSTARTED: 'commandstarted'; +} + +export interface IdeSession extends Evented { getTable(name: string): Promise; getFigure(name: string): Promise
; getTreeTable(name: string): Promise; @@ -62,6 +115,20 @@ export interface IdeSession { definition: VariableDefinition ): Promise; getObject(definition: VariableDefinition): Promise; + onLogMessage(logHandler: (logItem: LogItem) => void): () => void; + runCode(code: string): Promise; + bindTableToVariable(table: Table, variableName: string): Promise; + mergeTables(tables: Table[]): Promise
; + newTable( + columnNames: string[], + columnTypes: string[], + data: string[][], + userTimeZone: string + ): Promise
; + getCompletionItems(params: unknown): Promise; + closeDocument(params: unknown): void; + openDocument(params: unknown): void; + changeDocument(params: unknown): void; } export interface Evented { From b9a7e8db4f101dcea504f9b63c426752d86e1c12 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Mon, 11 Jul 2022 16:10:54 -0400 Subject: [PATCH 08/26] fixed errors --- packages/console/src/Console.tsx | 12 +++---- packages/console/src/monaco/MonacoUtils.ts | 39 ++++++++++++++++++---- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/packages/console/src/Console.tsx b/packages/console/src/Console.tsx index 9b52923e22..b6fb2cabd2 100644 --- a/packages/console/src/Console.tsx +++ b/packages/console/src/Console.tsx @@ -326,7 +326,7 @@ export class Console extends PureComponent { result: | { message: string; - error: unknown; + error?: string; changes: VariableChanges; } | undefined, @@ -553,11 +553,11 @@ export class Console extends PureComponent { /** * Updates an existing workspace CommandHistoryItem - * @param {object} result The result to store with the history item. Could be empty object for success - * @param {Promise} workspaceItemPromise The workspace data row promise for the workspace item to be updated + * @param result The result to store with the history item. Could be empty object for success + * @param workspaceItemPromise The workspace data row promise for the workspace item to be updated */ updateWorkspaceHistoryItem( - result: { error: unknown }, + result: { error?: string }, workspaceItemPromise: Promise ): void { const promise = this.pending.add(workspaceItemPromise); @@ -731,7 +731,7 @@ export class Console extends PureComponent { }); } - addConsoleHistoryMessage(message: string | null, error?: unknown): void { + addConsoleHistoryMessage(message: string | null, error?: string): void { const { consoleHistory } = this.state; const historyItem = { command: '', @@ -750,7 +750,7 @@ export class Console extends PureComponent { } handleCsvError(error: unknown): void { - this.addConsoleHistoryMessage(null, error); + this.addConsoleHistoryMessage(null, error ? `${error}` : undefined); } handleCsvInProgress(csvUploadInProgress: boolean): void { diff --git a/packages/console/src/monaco/MonacoUtils.ts b/packages/console/src/monaco/MonacoUtils.ts index 7be77a93dd..871b447bbd 100644 --- a/packages/console/src/monaco/MonacoUtils.ts +++ b/packages/console/src/monaco/MonacoUtils.ts @@ -76,6 +76,13 @@ import LogLang from './lang/log'; const log = Log.module('MonacoUtils'); +type Language = { + id: string; + conf: monaco.languages.LanguageConfiguration; + language: + | monaco.languages.IMonarchLanguage + | monaco.Thenable; +}; class MonacoUtils { static init(): void { log.debug('Initializing Monaco...'); @@ -190,7 +197,13 @@ class MonacoUtils { log.debug2('monaco theme: ', MonacoTheme); monaco.editor.setTheme('dh-dark'); - registerLanguages([DbLang, PyLang, GroovyLang, LogLang, ScalaLang]); + registerLanguages(([ + DbLang, + PyLang, + GroovyLang, + LogLang, + ScalaLang, + ] as unknown[]) as Language[]); log.debug('Monaco initialized.'); } @@ -198,13 +211,21 @@ class MonacoUtils { /** * Remove the hashtag prefix from a CSS color string. * Monaco expects colors to be the value only, no hashtag. - * @param {String} color The hex color string to remove the hashtag from, eg. '#ffffff' + * @param color The hex color string to remove the hashtag from, eg. '#ffffff' */ static removeHashtag(color: string): string { return color.substring(1); } - static registerLanguages(languages): void { + static registerLanguages( + languages: { + id: string; + conf: monaco.languages.LanguageConfiguration; + language: + | monaco.languages.IMonarchLanguage + | monaco.Thenable; + }[] + ): void { // First override the default loader for any language we have a custom definition for // https://github.com/Microsoft/monaco-editor/issues/252#issuecomment-482786867 const languageIds = languages.map(({ id }) => id); @@ -214,6 +235,8 @@ class MonacoUtils { .forEach(languageParam => { const language = languageParam; log.debug2('Overriding default language loader:', language.id); + + // @ts-ignore language.loader = () => ({ then: () => undefined, }); @@ -225,7 +248,13 @@ class MonacoUtils { }); } - static registerLanguage(language): void { + static registerLanguage(language: { + id: string; + conf: monaco.languages.LanguageConfiguration; + language: + | monaco.languages.IMonarchLanguage + | monaco.Thenable; + }): void { log.debug2('Registering language: ', language.id); monaco.languages.register(language); @@ -418,7 +447,6 @@ class MonacoUtils { } /* eslint-disable no-underscore-dangle */ // It's possible a single keybinding has multiple commands depending on context - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const keybindings = editor._standaloneKeybindingService ._getResolver() @@ -429,7 +457,6 @@ class MonacoUtils { log.debug2( `Removing Monaco keybinding ${keybinding} for ${elem.command}` ); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore editor._standaloneKeybindingService.addDynamicKeybinding( `-${elem.command}`, From 9c4c323c11a30d405975516cb513e2e35e89b483 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Mon, 11 Jul 2022 16:11:52 -0400 Subject: [PATCH 09/26] added unsaved file --- packages/console/src/monaco/MonacoUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/console/src/monaco/MonacoUtils.ts b/packages/console/src/monaco/MonacoUtils.ts index 871b447bbd..1ae3950c58 100644 --- a/packages/console/src/monaco/MonacoUtils.ts +++ b/packages/console/src/monaco/MonacoUtils.ts @@ -237,9 +237,9 @@ class MonacoUtils { log.debug2('Overriding default language loader:', language.id); // @ts-ignore - language.loader = () => ({ - then: () => undefined, - }); + // language.loader = () => ({ + // then: () => undefined, + // }); }); // Then register our language definitions From 8b9a7726b408160b6a32b3c41a22f8230a2e3b6d Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Mon, 11 Jul 2022 16:12:24 -0400 Subject: [PATCH 10/26] removed extraneous comments --- .../src/command-history/CommandHistoryItemTooltip.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/console/src/command-history/CommandHistoryItemTooltip.tsx b/packages/console/src/command-history/CommandHistoryItemTooltip.tsx index 7aa28089a8..da3c17b886 100644 --- a/packages/console/src/command-history/CommandHistoryItemTooltip.tsx +++ b/packages/console/src/command-history/CommandHistoryItemTooltip.tsx @@ -166,13 +166,7 @@ export class CommandHistoryItemTooltip extends Component< const { result, startTime, endTime } = data ?? {}; const errorMessage = result?.error ?? error; - // let errorMessage = error; - // const tempError = result?.error; - // if (typeof tempError === 'string') { - // errorMessage = tempError; - // } else if ((tempError as { message: string }).message != null) { - // errorMessage = (tempError as { message: string }).message; - // } + const timeString = CommandHistoryItemTooltip.getTimeString( startTime, endTime || currentTime From 84d8a3fbfaf6fe4ebca9532f73009dd130d12484 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Mon, 11 Jul 2022 16:46:51 -0400 Subject: [PATCH 11/26] added declaration file for module.scss --- packages/console/src/declaration.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 packages/console/src/declaration.d.ts diff --git a/packages/console/src/declaration.d.ts b/packages/console/src/declaration.d.ts new file mode 100644 index 0000000000..a10272fac6 --- /dev/null +++ b/packages/console/src/declaration.d.ts @@ -0,0 +1,6 @@ +declare module '*.module.scss' { + const content: Record; + export default content; +} + +declare module '*.scss'; From f4ec862082745bad46c5b4eda38ee66002fc6641 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Mon, 11 Jul 2022 16:50:48 -0400 Subject: [PATCH 12/26] refractored types --- packages/console/src/Console.tsx | 5 ++-- .../src/command-history/CommandHistory.tsx | 28 +++---------------- .../command-history/CommandHistoryActions.tsx | 2 +- .../command-history/CommandHistoryTypes.tsx | 21 ++++++++++++++ .../src/console-history/ConsoleHistory.tsx | 25 ++--------------- .../console-history/ConsoleHistoryItem.tsx | 2 +- .../console-history/ConsoleHistoryTypes.tsx | 23 +++++++++++++++ 7 files changed, 54 insertions(+), 52 deletions(-) create mode 100644 packages/console/src/command-history/CommandHistoryTypes.tsx create mode 100644 packages/console/src/console-history/ConsoleHistoryTypes.tsx diff --git a/packages/console/src/Console.tsx b/packages/console/src/Console.tsx index b6fb2cabd2..9da987378b 100644 --- a/packages/console/src/Console.tsx +++ b/packages/console/src/Console.tsx @@ -22,9 +22,8 @@ import dh, { } from '@deephaven/jsapi-shim'; import Log from '@deephaven/log'; import { assertNotNull, Pending } from '@deephaven/utils'; -import ConsoleHistory, { - ConsoleHistoryActionItem, -} from './console-history/ConsoleHistory'; +import ConsoleHistory from './console-history/ConsoleHistory'; +import { ConsoleHistoryActionItem } from './console-history/ConsoleHistoryTypes'; import SHORTCUTS from './ConsoleShortcuts'; import LogLevel from './log/LogLevel'; import ConsoleInput from './ConsoleInput'; diff --git a/packages/console/src/command-history/CommandHistory.tsx b/packages/console/src/command-history/CommandHistory.tsx index 186128b2ed..4b243d1111 100644 --- a/packages/console/src/command-history/CommandHistory.tsx +++ b/packages/console/src/command-history/CommandHistory.tsx @@ -5,12 +5,10 @@ import { ItemList, SearchInput, GLOBAL_SHORTCUTS, - Shortcut, RenderItemProps, } from '@deephaven/components'; import { ViewportData } from '@deephaven/storage'; import { - IconDefinition, vsFileCode, vsFiles, vsNewFile, @@ -30,36 +28,18 @@ import CommandHistoryStorage, { CommandHistoryStorageItem, CommandHistoryTable, } from './CommandHistoryStorage'; +import { ItemAction, HistoryAction } from './CommandHistoryTypes'; const log = Log.module('CommandHistory'); -type TODOEVENTHUB = { +type Settings = { value: string; language: string; }; -export type ItemAction = { - title: string; - description: string; - icon: IconDefinition; - shortcut?: Shortcut; - action: () => void; - group: number; - order?: number; -}; - -export type HistoryAction = { - action: () => void; - title: string; - description: string; - icon: IconDefinition; - selectionRequired?: boolean; - className?: string; -}; - interface CommandHistoryProps { language: string; sendToConsole: (command: string, focus?: boolean, execute?: boolean) => void; - sendToNotebook: (settings: TODOEVENTHUB, forceNewNotebook?: boolean) => void; + sendToNotebook: (settings: Settings, forceNewNotebook?: boolean) => void; table: CommandHistoryTable; commandHistoryStorage: CommandHistoryStorage; } @@ -248,7 +228,7 @@ class CommandHistory extends Component< /** * Retrieves the text of all the currently selected commands, joined by a new line char - * @returns {Promise} The commands joined by \n char + * @returns The commands joined by \n char */ getSelectedCommandText(): Promise { return this.getSelectedCommands().then(commands => commands.join('\n')); diff --git a/packages/console/src/command-history/CommandHistoryActions.tsx b/packages/console/src/command-history/CommandHistoryActions.tsx index 9afbab05b7..9b9315b816 100644 --- a/packages/console/src/command-history/CommandHistoryActions.tsx +++ b/packages/console/src/command-history/CommandHistoryActions.tsx @@ -4,7 +4,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button } from '@deephaven/components'; import { vsArrowLeft, vsCircleLargeFilled } from '@deephaven/icons'; import './CommandHistoryActions.scss'; -import { HistoryAction } from './CommandHistory'; +import { HistoryAction } from './CommandHistoryTypes'; interface CommandHistoryActionsProps { actions: HistoryAction[]; diff --git a/packages/console/src/command-history/CommandHistoryTypes.tsx b/packages/console/src/command-history/CommandHistoryTypes.tsx new file mode 100644 index 0000000000..0e7d766ecc --- /dev/null +++ b/packages/console/src/command-history/CommandHistoryTypes.tsx @@ -0,0 +1,21 @@ +import { Shortcut } from '@deephaven/components'; +import { IconDefinition } from '@deephaven/icons'; + +export type ItemAction = { + title: string; + description: string; + icon: IconDefinition; + shortcut?: Shortcut; + action: () => void; + group: number; + order?: number; +}; + +export type HistoryAction = { + action: () => void; + title: string; + description: string; + icon: IconDefinition; + selectionRequired?: boolean; + className?: string; +}; diff --git a/packages/console/src/console-history/ConsoleHistory.tsx b/packages/console/src/console-history/ConsoleHistory.tsx index 891bd0f284..a3db5da460 100644 --- a/packages/console/src/console-history/ConsoleHistory.tsx +++ b/packages/console/src/console-history/ConsoleHistory.tsx @@ -2,32 +2,11 @@ * Console display for use in the Iris environment. */ import React, { Component, ReactElement } from 'react'; -import { CancelablePromise } from '@deephaven/utils'; -import { VariableChanges, VariableDefinition } from '@deephaven/jsapi-shim'; +import { VariableDefinition } from '@deephaven/jsapi-shim'; import ConsoleHistoryItem from './ConsoleHistoryItem'; import './ConsoleHistory.scss'; - -export type ConsoleHistoryError = - | string - | { - message: string; - } - | undefined; - -export interface ConsoleHistoryActionItem { - command?: string; - result?: { - message?: string; - error?: unknown; - changes?: VariableChanges; - }; - disabledObjects?: string[]; - startTime?: number; - endTime?: number; - cancelResult?: () => void; - wrappedResult?: CancelablePromise; -} +import { ConsoleHistoryActionItem } from './ConsoleHistoryTypes'; interface ConsoleHistoryProps { items: ConsoleHistoryActionItem[]; diff --git a/packages/console/src/console-history/ConsoleHistoryItem.tsx b/packages/console/src/console-history/ConsoleHistoryItem.tsx index 3b58df2748..2a865c2d84 100644 --- a/packages/console/src/console-history/ConsoleHistoryItem.tsx +++ b/packages/console/src/console-history/ConsoleHistoryItem.tsx @@ -10,7 +10,7 @@ import ConsoleHistoryItemResult from './ConsoleHistoryItemResult'; import ConsoleHistoryResultInProgress from './ConsoleHistoryResultInProgress'; import ConsoleHistoryResultErrorMessage from './ConsoleHistoryResultErrorMessage'; import './ConsoleHistoryItem.scss'; -import { ConsoleHistoryActionItem } from './ConsoleHistory'; +import { ConsoleHistoryActionItem } from './ConsoleHistoryTypes'; const log = Log.module('ConsoleHistoryItem'); diff --git a/packages/console/src/console-history/ConsoleHistoryTypes.tsx b/packages/console/src/console-history/ConsoleHistoryTypes.tsx new file mode 100644 index 0000000000..22a2889a49 --- /dev/null +++ b/packages/console/src/console-history/ConsoleHistoryTypes.tsx @@ -0,0 +1,23 @@ +import { CancelablePromise } from '@deephaven/utils'; +import { VariableChanges } from '@deephaven/jsapi-shim'; + +export type ConsoleHistoryError = + | string + | { + message: string; + } + | undefined; + +export interface ConsoleHistoryActionItem { + command?: string; + result?: { + message?: string; + error?: unknown; + changes?: VariableChanges; + }; + disabledObjects?: string[]; + startTime?: number; + endTime?: number; + cancelResult?: () => void; + wrappedResult?: CancelablePromise; +} From abf6f33f284ce68d1abe843b7cf89acdc9700251 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Mon, 11 Jul 2022 17:17:25 -0400 Subject: [PATCH 13/26] added type to start script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f39e52dfa0..c8f284a2cd 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "build:profile": "lerna run build --stream --profile", "build:necessary": "lerna run build --scope=@deephaven/{icons,golden-layout}", "prestart": "lerna run prestart --stream", - "start": "npm-run-all --parallel watch:types start:*", + "start": "npm-run-all types --parallel watch:types start:*", "start:app": "lerna run start --scope=@deephaven/code-studio --stream", "start:embed-grid": "lerna run start --scope=@deephaven/embed-grid --stream", "start:packages": "lerna run watch --ignore=@deephaven/{code-studio,embed-grid} --stream --parallel", From bb6b5e5bf1747020d96da50e022eae205b5f0fb1 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Tue, 12 Jul 2022 15:13:02 -0400 Subject: [PATCH 14/26] converted test cases --- .../{Console.test.jsx => Console.test.tsx} | 15 +- ...Bar.test.jsx => ConsoleStatusBar.test.tsx} | 7 +- ...story.test.jsx => CommandHistory.test.tsx} | 26 ++- ...est.jsx => CommandHistoryActions.test.tsx} | 0 ...jsx => CommandHistoryItemTooltip.test.tsx} | 7 +- ...soleUtils.test.js => ConsoleUtils.test.ts} | 2 +- ...story.test.jsx => ConsoleHistory.test.tsx} | 2 +- ...m.test.jsx => ConsoleHistoryItem.test.tsx} | 7 +- ...svOverlay.test.jsx => CsvOverlay.test.tsx} | 0 .../{CsvParser.test.js => CsvParser.test.ts} | 78 ++++----- ...peParser.test.js => CsvTypeParser.test.ts} | 0 ....jsx => MonacoCompletionProvider.test.tsx} | 35 ++-- .../{ScriptEditor.jsx => ScriptEditor.tsx} | 161 ++++++++++-------- 13 files changed, 190 insertions(+), 150 deletions(-) rename packages/console/src/{Console.test.jsx => Console.test.tsx} (59%) rename packages/console/src/{ConsoleStatusBar.test.jsx => ConsoleStatusBar.test.tsx} (73%) rename packages/console/src/command-history/{CommandHistory.test.jsx => CommandHistory.test.tsx} (89%) rename packages/console/src/command-history/{CommandHistoryActions.test.jsx => CommandHistoryActions.test.tsx} (100%) rename packages/console/src/command-history/{CommandHistoryItemTooltip.test.jsx => CommandHistoryItemTooltip.test.tsx} (92%) rename packages/console/src/common/{ConsoleUtils.test.js => ConsoleUtils.test.ts} (95%) rename packages/console/src/console-history/{ConsoleHistory.test.jsx => ConsoleHistory.test.tsx} (97%) rename packages/console/src/console-history/{ConsoleHistoryItem.test.jsx => ConsoleHistoryItem.test.tsx} (79%) rename packages/console/src/csv/{CsvOverlay.test.jsx => CsvOverlay.test.tsx} (100%) rename packages/console/src/csv/{CsvParser.test.js => CsvParser.test.ts} (70%) rename packages/console/src/csv/{CsvTypeParser.test.js => CsvTypeParser.test.ts} (100%) rename packages/console/src/monaco/{MonacoCompletionProvider.test.jsx => MonacoCompletionProvider.test.tsx} (72%) rename packages/console/src/notebook/{ScriptEditor.jsx => ScriptEditor.tsx} (73%) diff --git a/packages/console/src/Console.test.jsx b/packages/console/src/Console.test.tsx similarity index 59% rename from packages/console/src/Console.test.jsx rename to packages/console/src/Console.test.tsx index 082b02db7b..a91ddd0143 100644 --- a/packages/console/src/Console.test.jsx +++ b/packages/console/src/Console.test.tsx @@ -2,30 +2,33 @@ import React from 'react'; import dh from '@deephaven/jsapi-shim'; import { render } from '@testing-library/react'; import { Console } from './Console'; +import { CommandHistoryStorage } from './command-history'; -function makeMockCommandHistoryStorage() { +function makeMockCommandHistoryStorage(): CommandHistoryStorage { return { addItem: jest.fn(), getTable: jest.fn(), updateItem: jest.fn(), + listenItem: jest.fn(), }; } jest.mock('./ConsoleInput', () => () => null); jest.mock('./Console', () => ({ - ...jest.requireActual('./Console'), + ...(jest.requireActual('./Console') as Record), commandHistory: jest.fn(), })); function makeConsoleWrapper() { - const session = new dh.IdeSession('test'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const session = new (dh as any).IdeSession('test'); const commandHistoryStorage = makeMockCommandHistoryStorage(); return render( {}} - openObject={() => {}} - closeObject={() => {}} + focusCommandHistory={() => undefined} + openObject={() => undefined} + closeObject={() => undefined} session={session} language="test" /> diff --git a/packages/console/src/ConsoleStatusBar.test.jsx b/packages/console/src/ConsoleStatusBar.test.tsx similarity index 73% rename from packages/console/src/ConsoleStatusBar.test.jsx rename to packages/console/src/ConsoleStatusBar.test.tsx index e1ba7f2aa1..4be9b1df14 100644 --- a/packages/console/src/ConsoleStatusBar.test.jsx +++ b/packages/console/src/ConsoleStatusBar.test.tsx @@ -4,13 +4,12 @@ import dh from '@deephaven/jsapi-shim'; import ConsoleStatusBar from './ConsoleStatusBar'; function makeConsoleStatusBarWrapper() { - const session = new dh.IdeSession('test'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const session = new (dh as any).IdeSession('test'); const wrapper = render( {}} - openObject={() => {}} + openObject={() => undefined} objects={[]} overflowActions={[]} /> diff --git a/packages/console/src/command-history/CommandHistory.test.jsx b/packages/console/src/command-history/CommandHistory.test.tsx similarity index 89% rename from packages/console/src/command-history/CommandHistory.test.jsx rename to packages/console/src/command-history/CommandHistory.test.tsx index 81b16c2959..1960c53b89 100644 --- a/packages/console/src/command-history/CommandHistory.test.jsx +++ b/packages/console/src/command-history/CommandHistory.test.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import CommandHistory from './CommandHistory'; +import { CommandHistoryTable } from './CommandHistoryStorage'; jest.mock('pouchdb-browser'); @@ -24,7 +25,7 @@ jest.mock('./CommandHistoryViewportUpdater', () => }) ); -function makeCommandHistoryTable(itemLength) { +function makeCommandHistoryTable(itemLength): CommandHistoryTable { return { onUpdate: jest.fn(), setSearch: jest.fn(), @@ -32,6 +33,10 @@ function makeCommandHistoryTable(itemLength) { setViewport: jest.fn(), getSnapshot: jest.fn(), size: itemLength, + getViewportData: jest.fn(), + setFilters: jest.fn(), + setSorts: jest.fn(), + close: jest.fn(), }; } @@ -41,9 +46,22 @@ function mountItems(itemLength = 10) { {}} - sendToNotebook={() => {}} - commandHistoryStorage={{ addItem() {}, updateItem() {}, getTable() {} }} + sendToConsole={() => undefined} + sendToNotebook={() => undefined} + commandHistoryStorage={{ + addItem() { + return undefined; + }, + updateItem() { + return undefined; + }, + getTable() { + return undefined; + }, + listenItem() { + return undefined; + }, + }} /> ); const items = makeItems(itemLength); diff --git a/packages/console/src/command-history/CommandHistoryActions.test.jsx b/packages/console/src/command-history/CommandHistoryActions.test.tsx similarity index 100% rename from packages/console/src/command-history/CommandHistoryActions.test.jsx rename to packages/console/src/command-history/CommandHistoryActions.test.tsx diff --git a/packages/console/src/command-history/CommandHistoryItemTooltip.test.jsx b/packages/console/src/command-history/CommandHistoryItemTooltip.test.tsx similarity index 92% rename from packages/console/src/command-history/CommandHistoryItemTooltip.test.jsx rename to packages/console/src/command-history/CommandHistoryItemTooltip.test.tsx index fcfbaa420f..d28aa1d7d3 100644 --- a/packages/console/src/command-history/CommandHistoryItemTooltip.test.jsx +++ b/packages/console/src/command-history/CommandHistoryItemTooltip.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import { CommandHistoryItemTooltip } from './CommandHistoryItemTooltip'; +import { CommandHistoryStorageItem } from './CommandHistoryStorage'; jest.mock('../common/Code', () => () => 'Code'); @@ -13,10 +14,14 @@ function makeCommandHistoryStorage() { }; } -function makeItem(id = 'TestId', name = 'Test command') { +function makeItem( + id = 'TestId', + name = 'Test command' +): CommandHistoryStorageItem { return { id, name, + data: { command: name, startTime: `${Date.now()}` }, }; } diff --git a/packages/console/src/common/ConsoleUtils.test.js b/packages/console/src/common/ConsoleUtils.test.ts similarity index 95% rename from packages/console/src/common/ConsoleUtils.test.js rename to packages/console/src/common/ConsoleUtils.test.ts index 670e159695..f21c2fdadd 100644 --- a/packages/console/src/common/ConsoleUtils.test.js +++ b/packages/console/src/common/ConsoleUtils.test.ts @@ -43,5 +43,5 @@ describe('parsing shell arguments from text', () => { describe('predicates', () => { expect(ConsoleUtils.hasComment('asdf')).toBeFalsy(); - expect(ConsoleUtils.hasComment({ comment: 123 })).toBeTruthy(); + expect(ConsoleUtils.hasComment({ comment: '123' })).toBeTruthy(); }); diff --git a/packages/console/src/console-history/ConsoleHistory.test.jsx b/packages/console/src/console-history/ConsoleHistory.test.tsx similarity index 97% rename from packages/console/src/console-history/ConsoleHistory.test.jsx rename to packages/console/src/console-history/ConsoleHistory.test.tsx index 6637eee0df..ed408e0cb2 100644 --- a/packages/console/src/console-history/ConsoleHistory.test.jsx +++ b/packages/console/src/console-history/ConsoleHistory.test.tsx @@ -5,7 +5,7 @@ import ConsoleHistory from './ConsoleHistory'; function makeHistoryItem( message, result, - cancelResult = () => {}, + cancelResult = () => undefined, disabledObjects = [] ) { return { diff --git a/packages/console/src/console-history/ConsoleHistoryItem.test.jsx b/packages/console/src/console-history/ConsoleHistoryItem.test.tsx similarity index 79% rename from packages/console/src/console-history/ConsoleHistoryItem.test.jsx rename to packages/console/src/console-history/ConsoleHistoryItem.test.tsx index bbdd908130..dcac9473b4 100644 --- a/packages/console/src/console-history/ConsoleHistoryItem.test.jsx +++ b/packages/console/src/console-history/ConsoleHistoryItem.test.tsx @@ -4,12 +4,7 @@ import ConsoleHistoryItem from './ConsoleHistoryItem'; const DEFAULT_ITEM = { message: 'Test item', - result: { - then: callback => { - callback('Command result!'); - }, - }, - cancelResult: () => {}, + cancelResult: () => undefined, disabledObjects: [], }; diff --git a/packages/console/src/csv/CsvOverlay.test.jsx b/packages/console/src/csv/CsvOverlay.test.tsx similarity index 100% rename from packages/console/src/csv/CsvOverlay.test.jsx rename to packages/console/src/csv/CsvOverlay.test.tsx diff --git a/packages/console/src/csv/CsvParser.test.js b/packages/console/src/csv/CsvParser.test.ts similarity index 70% rename from packages/console/src/csv/CsvParser.test.js rename to packages/console/src/csv/CsvParser.test.ts index 9ea1ccd6aa..c560af4901 100644 --- a/packages/console/src/csv/CsvParser.test.js +++ b/packages/console/src/csv/CsvParser.test.ts @@ -5,13 +5,13 @@ const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const makeParser = () => new CsvParser({ - onFileCompleted: () => {}, + onFileCompleted: () => undefined, session: null, file: null, - type: CsvFormats.DEFAULT_TYPE, + type: CsvFormats.TYPES[CsvFormats.DEFAULT_TYPE], readHeaders: true, - onProgress: () => {}, - onError: () => {}, + onProgress: () => undefined, + onError: () => undefined, timeZone: '', isZip: true, }); @@ -54,40 +54,40 @@ it('transposes correctly', () => { // 3x3 expect( csvParser.transpose(3, [ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9], + [`1`, `2`, `3`], + [`4`, `5`, `6`], + [`7`, `8`, `9`], ]) ).toStrictEqual([ - [1, 4, 7], - [2, 5, 8], - [3, 6, 9], + ['1', '4', '7'], + ['2', '5', '8'], + ['3', '6', '9'], ]); // 3x4 expect( csvParser.transpose(4, [ - [1, 2, 3, 4], - [5, 6, 7, 8], - [9, 10, 11, 12], + ['1', '2', '3', '4'], + ['5', '6', '7', '8'], + ['9', '10', '11', '12'], ]) ).toStrictEqual([ - [1, 5, 9], - [2, 6, 10], - [3, 7, 11], - [4, 8, 12], + ['1', '5', '9'], + ['2', '6', '10'], + ['3', '7', '11'], + ['4', '8', '12'], ]); // 4x3 expect( csvParser.transpose(3, [ - [1, 2, 3], - [4, 5, 6], - [7, 8, 9], - [10, 11, 12], + ['1', '2', '3'], + ['4', '5', '6'], + ['7', '8', '9'], + ['10', '11', '12'], ]) ).toStrictEqual([ - [1, 4, 7, 10], - [2, 5, 8, 11], - [3, 6, 9, 12], + ['1', '4', '7', '10'], + ['2', '5', '8', '11'], + ['3', '6', '9', '12'], ]); }); @@ -96,27 +96,27 @@ it('drops extra columns', () => { expect( csvParser.transpose(3, [ ['col1', 'col2', 'col3', 'col4'], - [1, 2, 3, 4], - [1, 2, 3], - [1, 2, 3, 4, 5], + ['1', '2', '3', '4'], + ['1', '2', '3'], + ['1', '2', '3', '4', '5'], ]) ).toStrictEqual([ - ['col1', 1, 1, 1], - ['col2', 2, 2, 2], - ['col3', 3, 3, 3], + ['col1', '1', '1', '1'], + ['col2', '2', '2', '2'], + ['col3', '3', '3', '3'], ]); expect( csvParser.transpose(3, [ ['col1', 'col2', 'col3'], - [1, 2, 3, 4], - [1, 2, 3], - [1, 2, 3, 4, 5], + ['1', '2', '3', '4'], + ['1', '2', '3'], + ['1', '2', '3', '4', '5'], ]) ).toStrictEqual([ - ['col1', 1, 1, 1], - ['col2', 2, 2, 2], - ['col3', 3, 3, 3], + ['col1', `1`, `1`, `1`], + ['col2', '2', '2', `2`], + ['col3', '3', '3', '3'], ]); }); @@ -125,9 +125,9 @@ it('throws an error for insufficient columns', () => { expect(() => csvParser.transpose(3, [ ['col1', 'col2', 'col3'], - [1, 2, 3, 4], - [1, 2, 3], - [1, 2], + ['1', '2', '3', '4'], + ['1', '2', '3'], + ['1', '2'], ]) ).toThrow(new Error('Insufficient columns. Expected 3 but found 2\n1,2')); }); diff --git a/packages/console/src/csv/CsvTypeParser.test.js b/packages/console/src/csv/CsvTypeParser.test.ts similarity index 100% rename from packages/console/src/csv/CsvTypeParser.test.js rename to packages/console/src/csv/CsvTypeParser.test.ts diff --git a/packages/console/src/monaco/MonacoCompletionProvider.test.jsx b/packages/console/src/monaco/MonacoCompletionProvider.test.tsx similarity index 72% rename from packages/console/src/monaco/MonacoCompletionProvider.test.jsx rename to packages/console/src/monaco/MonacoCompletionProvider.test.tsx index fef126cb62..0b448085c5 100644 --- a/packages/console/src/monaco/MonacoCompletionProvider.test.jsx +++ b/packages/console/src/monaco/MonacoCompletionProvider.test.tsx @@ -8,12 +8,13 @@ const DEFAULT_LANGUAGE = 'test'; function makeCompletionProvider( language = DEFAULT_LANGUAGE, - session = new dh.IdeSession(language), + // eslint-disable-next-line @typescript-eslint/no-explicit-any + session = new (dh as any).IdeSession(language), model = { uri: {} } ) { const wrapper = render( @@ -66,7 +67,8 @@ it('provides completion items properly', () => { ]; const promiseItems = Promise.resolve(items); const language = DEFAULT_LANGUAGE; - const session = new dh.IdeSession(language); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const session = new (dh as any).IdeSession(language); session.getCompletionItems = jest.fn(() => promiseItems); const model = { uri: { path: 'test' } }; @@ -74,16 +76,23 @@ it('provides completion items properly', () => { const position = { lineNumber: 1, column: 1 }; expect(myRegister).toHaveBeenCalledTimes(1); expect.assertions(4); - return myRegister.mock.calls[0][1] - .provideCompletionItems(model, position) - .then(resultItems => { - expect(session.getCompletionItems).toHaveBeenCalled(); - const { suggestions } = resultItems; - expect(suggestions.length).toBe(items.length); - expect(suggestions[0]).toMatchObject({ - insertText: newText, - label: newText, - }); + const calls: { + provideCompletionItems: ( + model: unknown, + position: unknown + ) => Promise<{ suggestions: unknown[] }>; + }[] = myRegister.mock.calls[0]; + const fn = calls[1]; + + return fn.provideCompletionItems(model, position).then(resultItems => { + expect(session.getCompletionItems).toHaveBeenCalled(); + + const { suggestions } = resultItems; + expect(suggestions.length).toBe(items.length); + expect(suggestions[0]).toMatchObject({ + insertText: newText, + label: newText, }); + }); }); diff --git a/packages/console/src/notebook/ScriptEditor.jsx b/packages/console/src/notebook/ScriptEditor.tsx similarity index 73% rename from packages/console/src/notebook/ScriptEditor.jsx rename to packages/console/src/notebook/ScriptEditor.tsx index d1fccae662..fea40c3f4c 100644 --- a/packages/console/src/notebook/ScriptEditor.jsx +++ b/packages/console/src/notebook/ScriptEditor.tsx @@ -1,10 +1,12 @@ /** * Script editor for large blocks of code */ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { Component, ReactElement, RefObject } from 'react'; import { LoadingOverlay, ShortcutRegistry } from '@deephaven/components'; import Log from '@deephaven/log'; +import { IdeSession } from '@deephaven/jsapi-shim'; +import { assertNotNull } from '@deephaven/utils'; +import { editor, IDisposable } from 'monaco-editor'; import Editor from './Editor'; import { MonacoCompletionProvider, MonacoUtils } from '../monaco'; import './ScriptEditor.scss'; @@ -12,8 +14,38 @@ import SHORTCUTS from '../ConsoleShortcuts'; const log = Log.module('ScriptEditor'); -class ScriptEditor extends Component { - constructor(props) { +interface ScriptEditorProps { + error?: { message?: string }; + isLoading: boolean; + isLoaded: boolean; + focusOnMount?: boolean; + onChange: (e: editor.IModelContentChangedEvent) => void; + onRunCommand: (command: string | null) => void; + session: IdeSession; + sessionLanguage?: string; + settings?: { + language: string; + value?: string; + }; +} + +interface ScriptEditorState { + model: editor.ITextModel | null; +} + +class ScriptEditor extends Component { + static defaultProps = { + error: null, + isLoading: false, + isLoaded: false, + focusOnMount: true, + onChange: (): void => undefined, + session: null, + sessionLanguage: null, + settings: null, + }; + + constructor(props: ScriptEditorProps) { super(props); this.handleEditorInitialized = this.handleEditorInitialized.bind(this); this.handleEditorWillDestroy = this.handleEditorWillDestroy.bind(this); @@ -22,8 +54,6 @@ class ScriptEditor extends Component { this.updateShortcuts = this.updateShortcuts.bind(this); this.contextActionCleanups = []; - this.completionCleanup = null; - this.editor = null; this.editorComponent = React.createRef(); this.state = { @@ -31,15 +61,17 @@ class ScriptEditor extends Component { }; } - componentDidMount() { + componentDidMount(): void { ShortcutRegistry.addEventListener('onUpdate', this.updateShortcuts); } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: ScriptEditorProps): void { const { sessionLanguage, settings } = this.props; - const { language } = settings; - - const languageChanged = language !== prevProps.settings.language; + let language = ''; + if (settings) { + language = settings.language; + } + const languageChanged = language !== prevProps.settings?.language; if (languageChanged) { log.debug('Set language', language); this.setLanguage(language); @@ -49,7 +81,7 @@ class ScriptEditor extends Component { sessionLanguage == null && prevProps.sessionLanguage != null; const languageMatch = language === sessionLanguage; const prevLanguageMatch = - prevProps.settings.language === prevProps.sessionLanguage; + prevProps.settings?.language === prevProps.sessionLanguage; if ( sessionDisconnected || (sessionLanguage && prevLanguageMatch && !languageMatch) @@ -73,11 +105,19 @@ class ScriptEditor extends Component { } } - componentWillUnmount() { + componentWillUnmount(): void { ShortcutRegistry.removeEventListener('onUpdate', this.updateShortcuts); } - getValue() { + contextActionCleanups: IDisposable[]; + + completionCleanup?: IDisposable; + + editor?: editor.IStandaloneCodeEditor; + + editorComponent: RefObject; + + getValue(): string | null { if (this.editor) { return this.editor.getValue(); } @@ -85,23 +125,25 @@ class ScriptEditor extends Component { return null; } - getSelectedCommand() { - const range = this.editor.getSelection(); - const model = this.editor.getModel(); + getSelectedCommand(): string { + const range = this.editor?.getSelection(); + assertNotNull(range); + const model = this.editor?.getModel(); + assertNotNull(model); const { startLineNumber, endColumn } = range; let { endLineNumber } = range; if (endColumn === 1 && endLineNumber > startLineNumber) { endLineNumber -= 1; } - const startLineMinColumn = model.getLineMinColumn(startLineNumber); - const endLineMaxColumn = model.getLineMaxColumn(endLineNumber); + const startLineMinColumn = model?.getLineMinColumn(startLineNumber); + const endLineMaxColumn = model?.getLineMaxColumn(endLineNumber); const wholeLineRange = range .setStartPosition(startLineNumber, startLineMinColumn) .setEndPosition(endLineNumber, endLineMaxColumn); - return model.getValueInRange(wholeLineRange); + return model?.getValueInRange(wholeLineRange); } - handleEditorInitialized(editor) { + handleEditorInitialized(innerEditor: editor.IStandaloneCodeEditor): void { const { focusOnMount, onChange, @@ -112,44 +154,44 @@ class ScriptEditor extends Component { log.debug('handleEditorInitialized', sessionLanguage, session, settings); - this.editor = editor; + this.editor = innerEditor; this.setState({ model: this.editor.getModel() }); - MonacoUtils.setEOL(editor); - MonacoUtils.registerPasteHandler(editor); + MonacoUtils.setEOL(innerEditor); + MonacoUtils.registerPasteHandler(innerEditor); if (session && settings && sessionLanguage === settings.language) { this.initContextActions(); this.initCodeCompletion(); } - editor.onDidChangeModelContent(onChange); + innerEditor.onDidChangeModelContent(onChange); if (focusOnMount) { - editor.focus(); + innerEditor.focus(); } } - handleEditorWillDestroy() { + handleEditorWillDestroy(): void { log.debug('handleEditorWillDestroy'); this.deInitContextActions(); this.deInitCodeCompletion(); this.setState({ model: null }); - this.editor = null; + this.editor = undefined; } - handleRun() { + handleRun(): void { const { onRunCommand } = this.props; const command = this.getValue(); onRunCommand(command); } - handleRunSelected() { + handleRunSelected(): void { const { onRunCommand } = this.props; const command = this.getSelectedCommand(); onRunCommand(command); } - initContextActions() { + initContextActions(): void { if (this.contextActionCleanups.length > 0) { log.error('Context actions already initialized.'); return; @@ -167,15 +209,11 @@ class ScriptEditor extends Component { keybindings: [ MonacoUtils.getMonacoKeyCodeFromShortcut(SHORTCUTS.NOTEBOOK.RUN), ], - precondition: null, - - keybindingContext: null, contextMenuGroupId: 'navigation', contextMenuOrder: 1.5, run: () => { this.handleRun(); - return null; }, }) ); @@ -189,14 +227,11 @@ class ScriptEditor extends Component { SHORTCUTS.NOTEBOOK.RUN_SELECTED ), ], - precondition: null, - keybindingContext: null, contextMenuGroupId: 'navigation', contextMenuOrder: 1.5, run: () => { this.handleRunSelected(); - return null; }, }) ); @@ -204,19 +239,19 @@ class ScriptEditor extends Component { this.contextActionCleanups = cleanups; } - deInitContextActions() { + deInitContextActions(): void { if (this.contextActionCleanups.length > 0) { this.contextActionCleanups.forEach(cleanup => cleanup.dispose()); this.contextActionCleanups = []; } } - updateShortcuts() { + updateShortcuts(): void { this.deInitContextActions(); this.initContextActions(); } - initCodeCompletion() { + initCodeCompletion(): void { if (this.completionCleanup != null) { log.error('Code completion already initialized.'); return; @@ -228,20 +263,22 @@ class ScriptEditor extends Component { } } - deInitCodeCompletion() { + deInitCodeCompletion(): void { const { session } = this.props; log.debug('deInitCodeCompletion', this.editor, session); if (this.completionCleanup) { this.completionCleanup.dispose(); - this.completionCleanup = null; + this.completionCleanup = undefined; } if (this.editor && session) { MonacoUtils.closeDocument(this.editor, session); } } - append(text, focus = true) { + append(text: string, focus = true): void { + assertNotNull(this.editor); const model = this.editor.getModel(); + assertNotNull(model); const currentText = model.getValue(); if (currentText) { model.setValue(`${currentText}\n${text}`); @@ -259,33 +296,33 @@ class ScriptEditor extends Component { } } - updateDimensions() { + updateDimensions(): void { log.debug('updateDimensions'); if (this.editor) { this.editor.layout(); } } - focus() { + focus(): void { log.debug('focus'); if (this.editor) { this.editor.focus(); } } - toggleFind() { + toggleFind(): void { if (this.editorComponent.current) { this.editorComponent.current.toggleFind(); } } - setLanguage(language) { + setLanguage(language: string): void { if (this.editorComponent.current) { this.editorComponent.current.setLanguage(language); } } - render() { + render(): ReactElement { const { error, isLoaded, @@ -338,30 +375,4 @@ class ScriptEditor extends Component { } } -ScriptEditor.propTypes = { - error: PropTypes.shape({ message: PropTypes.string }), - isLoading: PropTypes.bool, - isLoaded: PropTypes.bool, - focusOnMount: PropTypes.bool, - onChange: PropTypes.func, - onRunCommand: PropTypes.func.isRequired, - session: PropTypes.shape({}), - sessionLanguage: PropTypes.string, - settings: PropTypes.shape({ - language: PropTypes.string, - value: PropTypes.string, - }), -}; - -ScriptEditor.defaultProps = { - error: null, - isLoading: false, - isLoaded: false, - focusOnMount: true, - onChange: () => {}, - session: null, - sessionLanguage: null, - settings: null, -}; - export default ScriptEditor; From bd16e71ec3a1bf21500e8bb6ed8a6104ae006310 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Wed, 13 Jul 2022 14:29:27 -0400 Subject: [PATCH 15/26] first batch code review changes --- package-lock.json | 4 +- packages/console/package.json | 2 +- packages/console/src/Console.tsx | 19 ++++++---- .../CommandHistoryItemTooltip.tsx | 8 ++-- .../command-history/CommandHistoryTypes.tsx | 7 ++-- .../console/src/common/ConsoleUtils.test.ts | 4 ++ packages/console/src/common/ConsoleUtils.ts | 37 +++++++++++-------- packages/console/src/csv/CsvOverlay.tsx | 7 ++-- packages/jsapi-shim/src/dh.types.ts | 2 +- 9 files changed, 51 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa1b123188..5b1981f626 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39243,7 +39243,7 @@ "memoize-one": "^5.1.1", "memoizee": "^0.4.15", "monaco-editor": "^0.27.0", - "papaparse": "^5.2.0", + "papaparse": "^5.3.2", "popper.js": "^1.16.1", "prop-types": "^15.7.2", "shell-quote": "^1.7.2" @@ -41045,7 +41045,7 @@ "memoize-one": "^5.1.1", "memoizee": "^0.4.15", "monaco-editor": "^0.27.0", - "papaparse": "^5.2.0", + "papaparse": "^5.3.2", "popper.js": "^1.16.1", "prop-types": "^15.7.2", "shell-quote": "^1.7.2" diff --git a/packages/console/package.json b/packages/console/package.json index 2053e85fa6..4fa232ce91 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -43,7 +43,7 @@ "memoize-one": "^5.1.1", "memoizee": "^0.4.15", "monaco-editor": "^0.27.0", - "papaparse": "^5.2.0", + "papaparse": "^5.3.2", "popper.js": "^1.16.1", "prop-types": "^15.7.2", "shell-quote": "^1.7.2" diff --git a/packages/console/src/Console.tsx b/packages/console/src/Console.tsx index 9da987378b..65170949a7 100644 --- a/packages/console/src/Console.tsx +++ b/packages/console/src/Console.tsx @@ -21,7 +21,7 @@ import dh, { VariableDefinition, } from '@deephaven/jsapi-shim'; import Log from '@deephaven/log'; -import { assertNotNull, Pending } from '@deephaven/utils'; +import { assertNotNull, Pending, PromiseUtils } from '@deephaven/utils'; import ConsoleHistory from './console-history/ConsoleHistory'; import { ConsoleHistoryActionItem } from './console-history/ConsoleHistoryTypes'; import SHORTCUTS from './ConsoleShortcuts'; @@ -165,6 +165,7 @@ export class Console extends PureComponent { this.handleTogglePrintStdout = this.handleTogglePrintStdout.bind(this); this.handleUploadCsv = this.handleUploadCsv.bind(this); this.handleHideCsv = this.handleHideCsv.bind(this); + this.handleCsvFileCanceled = this.handleCsvFileCanceled.bind(this); this.handleCsvFileOpened = this.handleCsvFileOpened.bind(this); this.handleCsvPaste = this.handleCsvPaste.bind(this); this.handleDragEnter = this.handleDragEnter.bind(this); @@ -175,7 +176,6 @@ export class Console extends PureComponent { this.handleCsvError = this.handleCsvError.bind(this); this.handleCsvInProgress = this.handleCsvInProgress.bind(this); - this.cancelListener = undefined; this.consolePane = React.createRef(); this.consoleInput = React.createRef(); this.consoleHistoryScrollPane = React.createRef(); @@ -362,7 +362,7 @@ export class Console extends PureComponent { historyItem.wrappedResult = undefined; historyItem.cancelResult = undefined; - if (error && ((error as unknown) as { isCanceled: boolean }).isCanceled) { + if (PromiseUtils.isCanceled(error)) { log.debug('Called handleCommandError on a cancelled promise result'); return; } @@ -644,7 +644,11 @@ export class Console extends PureComponent { }); } - handleCsvFileOpened(file: File | null): void { + handleCsvFileCanceled(): void { + this.setState({ csvFile: null, csvPaste: null }); + } + + handleCsvFileOpened(file: File): void { this.setState({ csvFile: file, csvPaste: null }); } @@ -730,13 +734,13 @@ export class Console extends PureComponent { }); } - addConsoleHistoryMessage(message: string | null, error?: string): void { + addConsoleHistoryMessage(message?: string, error?: string): void { const { consoleHistory } = this.state; const historyItem = { command: '', startTime: Date.now(), endTime: Date.now(), - result: { message: message ?? undefined, error }, + result: { message, error }, }; const history = consoleHistory.concat(historyItem); this.setState({ @@ -749,7 +753,7 @@ export class Console extends PureComponent { } handleCsvError(error: unknown): void { - this.addConsoleHistoryMessage(null, error ? `${error}` : undefined); + this.addConsoleHistoryMessage(undefined, error ? `${error}` : undefined); } handleCsvInProgress(csvUploadInProgress: boolean): void { @@ -949,6 +953,7 @@ export class Console extends PureComponent { > {showCsvOverlay && ( void; group: number; order?: number; }; -export type HistoryAction = { +export type HistoryAction = ContextAction & { action: () => void; title: string; description: string; diff --git a/packages/console/src/common/ConsoleUtils.test.ts b/packages/console/src/common/ConsoleUtils.test.ts index f21c2fdadd..937764cafa 100644 --- a/packages/console/src/common/ConsoleUtils.test.ts +++ b/packages/console/src/common/ConsoleUtils.test.ts @@ -44,4 +44,8 @@ describe('parsing shell arguments from text', () => { describe('predicates', () => { expect(ConsoleUtils.hasComment('asdf')).toBeFalsy(); expect(ConsoleUtils.hasComment({ comment: '123' })).toBeTruthy(); + expect(ConsoleUtils.hasOp('asd')).toBeFalsy(); + expect(ConsoleUtils.hasOp({ op: '||' })).toBeTruthy(); + expect(ConsoleUtils.hasPattern('asd')).toBeFalsy(); + expect(ConsoleUtils.hasPattern({ op: 'glob', pattern: '||' })).toBeTruthy(); }); diff --git a/packages/console/src/common/ConsoleUtils.ts b/packages/console/src/common/ConsoleUtils.ts index f9dddbfe09..65f633b8f9 100644 --- a/packages/console/src/common/ConsoleUtils.ts +++ b/packages/console/src/common/ConsoleUtils.ts @@ -1,5 +1,8 @@ import ShellQuote, { ParseEntry, ControlOperator } from 'shell-quote'; -import dh from '@deephaven/jsapi-shim'; +import dh, { VariableTypeUnion } from '@deephaven/jsapi-shim'; +import Log from '@deephaven/log'; + +const log = Log.module('FigureChartModel'); class ConsoleUtils { static hasComment(arg: ParseEntry): arg is { comment: string } { @@ -29,13 +32,13 @@ class ConsoleUtils { return ShellQuote.parse(str as string) .filter(arg => !this.hasComment(arg)) .map(arg => { - let ret = `${arg}`; if (this.hasPattern(arg)) { - ret = arg.pattern; - } else if (this.hasOp(arg)) { - ret = arg.op; + return arg.pattern; + } + if (this.hasOp(arg)) { + return arg.op; } - return ret; + return `${arg}`; }); } @@ -53,22 +56,24 @@ class ConsoleUtils { } static defaultHost(): string { - let defaultHost = null; + let defaultHost = window.location.hostname; const apiUrl = process.env.REACT_APP_CORE_API_URL; if (apiUrl != null) { - const url = new URL(apiUrl); - defaultHost = url.hostname; - } else { - defaultHost = window.location.hostname; + try { + const url = new URL(apiUrl); + defaultHost = url.hostname; + } catch (error: unknown) { + log.error('API_URL failed', error); + } } return defaultHost; } - static isTableType(type: unknown): boolean { + static isTableType(type: VariableTypeUnion): boolean { return type === dh.VariableType.TABLE || type === dh.VariableType.TREETABLE; } - static isWidgetType(type: unknown): boolean { + static isWidgetType(type: VariableTypeUnion): boolean { return ( type === dh.VariableType.FIGURE || type === dh.VariableType.OTHERWIDGET || @@ -76,15 +81,15 @@ class ConsoleUtils { ); } - static isOpenableType(type: unknown): boolean { + static isOpenableType(type: VariableTypeUnion): boolean { return ConsoleUtils.isTableType(type) || ConsoleUtils.isWidgetType(type); } - static isFigureType(type: unknown): boolean { + static isFigureType(type: VariableTypeUnion): boolean { return type === dh.VariableType.FIGURE; } - static isPandas(type: unknown): boolean { + static isPandas(type: VariableTypeUnion): boolean { return type === dh.VariableType.PANDAS; } } diff --git a/packages/console/src/csv/CsvOverlay.tsx b/packages/console/src/csv/CsvOverlay.tsx index 8667e20ea0..a953b05597 100644 --- a/packages/console/src/csv/CsvOverlay.tsx +++ b/packages/console/src/csv/CsvOverlay.tsx @@ -28,7 +28,8 @@ import { assertNotNull, TextUtils } from '@deephaven/utils'; interface CsvOverlayProps { allowZip: boolean; - onFileOpened: (file: File | null) => void; + onFileOpened: (file: File) => void; + onCancel: () => void; onPaste: (clipText: string) => void; clearDragError: () => void; dragError: string | null; @@ -169,10 +170,10 @@ class CsvOverlay extends Component { } unstageFile(event: MouseEvent): void { - const { onFileOpened } = this.props; + const { onCancel } = this.props; event.stopPropagation(); event.preventDefault(); - onFileOpened(null); + onCancel(); this.setState({ selectedFileName: '', dropError: null, diff --git a/packages/jsapi-shim/src/dh.types.ts b/packages/jsapi-shim/src/dh.types.ts index fce57a9434..25e6237444 100644 --- a/packages/jsapi-shim/src/dh.types.ts +++ b/packages/jsapi-shim/src/dh.types.ts @@ -38,7 +38,7 @@ const VariableType = { TREETABLE: 'TreeTable', } as const; -type VariableTypeUnion = typeof VariableType[keyof typeof VariableType]; +export type VariableTypeUnion = typeof VariableType[keyof typeof VariableType]; export interface VariableDefinition< T extends VariableTypeUnion = VariableTypeUnion From 5055335f5a7afbd4833cfabe8da3ecdf7d787983 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Fri, 15 Jul 2022 14:57:22 -0400 Subject: [PATCH 16/26] WIP console --- .../console-history/ConsoleHistoryResultInProgress.tsx | 5 ++--- packages/console/src/csv/CsvFormats.ts | 2 +- packages/console/src/csv/CsvOverlay.tsx | 9 ++++----- packages/console/src/log/LogView.tsx | 6 ++++-- packages/console/src/monaco/MonacoUtils.ts | 8 +------- packages/console/src/monaco/lang/db.ts | 3 ++- 6 files changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/console/src/console-history/ConsoleHistoryResultInProgress.tsx b/packages/console/src/console-history/ConsoleHistoryResultInProgress.tsx index fc05d7ea26..703b2a788b 100644 --- a/packages/console/src/console-history/ConsoleHistoryResultInProgress.tsx +++ b/packages/console/src/console-history/ConsoleHistoryResultInProgress.tsx @@ -30,7 +30,6 @@ class ConsoleHistoryResultInProgress extends Component< this.updateElapsed = this.updateElapsed.bind(this); - this.timer = null; this.startTime = Date.now(); this.state = { @@ -47,10 +46,10 @@ class ConsoleHistoryResultInProgress extends Component< clearInterval(this.timer); } - this.timer = null; + this.timer = undefined; } - timer: NodeJS.Timer | null; + timer?: NodeJS.Timer; startTime: number; diff --git a/packages/console/src/csv/CsvFormats.ts b/packages/console/src/csv/CsvFormats.ts index a0b93cdb67..67ec46df7e 100644 --- a/packages/console/src/csv/CsvFormats.ts +++ b/packages/console/src/csv/CsvFormats.ts @@ -125,7 +125,7 @@ class CsvFormats { AUTODETECT: { name: 'autodetect', - delimiter: '', // autodetect + delimiter: undefined, // autodetect newline: undefined, // autodetect escapeChar: '"', shouldTrim: true, diff --git a/packages/console/src/csv/CsvOverlay.tsx b/packages/console/src/csv/CsvOverlay.tsx index a953b05597..bd6a9c07fa 100644 --- a/packages/console/src/csv/CsvOverlay.tsx +++ b/packages/console/src/csv/CsvOverlay.tsx @@ -39,7 +39,7 @@ interface CsvOverlayProps { interface CsvOverlayState { selectedFileName: string; - dropError: string | null; + dropError?: string; } const PASTED_VALUES = 'pasted values'; @@ -111,7 +111,6 @@ class CsvOverlay extends Component { this.state = { selectedFileName: '', - dropError: null, }; } @@ -176,7 +175,7 @@ class CsvOverlay extends Component { onCancel(); this.setState({ selectedFileName: '', - dropError: null, + dropError: undefined, }); } @@ -185,7 +184,7 @@ class CsvOverlay extends Component { onFileOpened(file); this.setState({ selectedFileName: file.name, - dropError: null, + dropError: undefined, }); } @@ -200,7 +199,7 @@ class CsvOverlay extends Component { onPaste(clipText); this.setState({ selectedFileName: PASTED_VALUES, - dropError: null, + dropError: undefined, }); }) .catch((e: unknown) => onError(e)); diff --git a/packages/console/src/log/LogView.tsx b/packages/console/src/log/LogView.tsx index 7cbb2bbeee..46ba303a28 100644 --- a/packages/console/src/log/LogView.tsx +++ b/packages/console/src/log/LogView.tsx @@ -117,7 +117,7 @@ class LogView extends PureComponent { editorContainer: HTMLDivElement | null; - logLevelMenuItems: Record; + logLevelMenuItems: Record; flushTimer?: ReturnType; @@ -151,7 +151,9 @@ class LogView extends PureComponent { on={on} onClick={this.handleMenuItemClick} ref={element => { - this.logLevelMenuItems[logLevel] = element; + if (element != null) { + this.logLevelMenuItems[logLevel] = element; + } }} /> ), diff --git a/packages/console/src/monaco/MonacoUtils.ts b/packages/console/src/monaco/MonacoUtils.ts index 1ae3950c58..e435e5c7be 100644 --- a/packages/console/src/monaco/MonacoUtils.ts +++ b/packages/console/src/monaco/MonacoUtils.ts @@ -197,13 +197,7 @@ class MonacoUtils { log.debug2('monaco theme: ', MonacoTheme); monaco.editor.setTheme('dh-dark'); - registerLanguages(([ - DbLang, - PyLang, - GroovyLang, - LogLang, - ScalaLang, - ] as unknown[]) as Language[]); + registerLanguages([DbLang, PyLang, GroovyLang, LogLang, ScalaLang]); log.debug('Monaco initialized.'); } diff --git a/packages/console/src/monaco/lang/db.ts b/packages/console/src/monaco/lang/db.ts index 03e18f15ee..d84e2a3b99 100644 --- a/packages/console/src/monaco/lang/db.ts +++ b/packages/console/src/monaco/lang/db.ts @@ -286,4 +286,5 @@ const language = { }, }; -export default { id, conf, language }; +const lang: Language = { id, conf, language } as const; +export default lang; From 14d9efc1d1820f66e313e960b38ae758e0006ff9 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Fri, 15 Jul 2022 15:15:16 -0400 Subject: [PATCH 17/26] language --- packages/console/src/monaco/MonacoUtils.ts | 8 -------- packages/console/src/monaco/lang/Language.ts | 9 +++++++++ packages/console/src/monaco/lang/db.ts | 2 ++ 3 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 packages/console/src/monaco/lang/Language.ts diff --git a/packages/console/src/monaco/MonacoUtils.ts b/packages/console/src/monaco/MonacoUtils.ts index e435e5c7be..0102de03ba 100644 --- a/packages/console/src/monaco/MonacoUtils.ts +++ b/packages/console/src/monaco/MonacoUtils.ts @@ -75,14 +75,6 @@ import DbLang from './lang/db'; import LogLang from './lang/log'; const log = Log.module('MonacoUtils'); - -type Language = { - id: string; - conf: monaco.languages.LanguageConfiguration; - language: - | monaco.languages.IMonarchLanguage - | monaco.Thenable; -}; class MonacoUtils { static init(): void { log.debug('Initializing Monaco...'); diff --git a/packages/console/src/monaco/lang/Language.ts b/packages/console/src/monaco/lang/Language.ts new file mode 100644 index 0000000000..51e0db4f44 --- /dev/null +++ b/packages/console/src/monaco/lang/Language.ts @@ -0,0 +1,9 @@ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; + +export type Language = { + id: string; + conf: monaco.languages.LanguageConfiguration; + language: + | monaco.languages.IMonarchLanguage + | monaco.Thenable; +}; diff --git a/packages/console/src/monaco/lang/db.ts b/packages/console/src/monaco/lang/db.ts index d84e2a3b99..b198430399 100644 --- a/packages/console/src/monaco/lang/db.ts +++ b/packages/console/src/monaco/lang/db.ts @@ -1,3 +1,5 @@ +import { Language } from './Language'; + /* eslint no-useless-escape: "off" */ const id = 'deephavenDb'; From a5dbdfcd6c6b25dcce4e362f82fc1f3d604ebc1c Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Mon, 18 Jul 2022 15:37:49 -0400 Subject: [PATCH 18/26] fixed changes --- .../console-history/ConsoleHistoryItem.tsx | 9 ++-- packages/console/src/monaco/MonacoUtils.ts | 47 ++++++++----------- packages/console/src/monaco/lang/db.ts | 5 +- .../console/src/notebook/ScriptEditor.tsx | 17 +++---- 4 files changed, 33 insertions(+), 45 deletions(-) diff --git a/packages/console/src/console-history/ConsoleHistoryItem.tsx b/packages/console/src/console-history/ConsoleHistoryItem.tsx index 2a865c2d84..a981946843 100644 --- a/packages/console/src/console-history/ConsoleHistoryItem.tsx +++ b/packages/console/src/console-history/ConsoleHistoryItem.tsx @@ -94,16 +94,15 @@ class ConsoleHistoryItem extends PureComponent< } // If the error has an associated command, we'll actually get a separate ERROR item printed out, so only print an error if there isn't an associated command - let errorMessage = error; - if (typeof error !== 'string' && error && !item.command) { - errorMessage = (error as { message: string }).message; + if (error && !item.command) { + let errorMessage = `${(error as { message: string }).message ?? error}`; if (!errorMessage) { - errorMessage = error; + errorMessage = error as string; } const element = ( ); resultElements.push(element); diff --git a/packages/console/src/monaco/MonacoUtils.ts b/packages/console/src/monaco/MonacoUtils.ts index 0102de03ba..9af53d47ab 100644 --- a/packages/console/src/monaco/MonacoUtils.ts +++ b/packages/console/src/monaco/MonacoUtils.ts @@ -73,6 +73,7 @@ import GroovyLang from './lang/groovy'; import ScalaLang from './lang/scala'; import DbLang from './lang/db'; import LogLang from './lang/log'; +import { Language } from './lang/Language'; const log = Log.module('MonacoUtils'); class MonacoUtils { @@ -189,7 +190,13 @@ class MonacoUtils { log.debug2('monaco theme: ', MonacoTheme); monaco.editor.setTheme('dh-dark'); - registerLanguages([DbLang, PyLang, GroovyLang, LogLang, ScalaLang]); + registerLanguages([ + DbLang, + PyLang, + GroovyLang, + LogLang, + ScalaLang, + ] as Language[]); log.debug('Monaco initialized.'); } @@ -203,15 +210,7 @@ class MonacoUtils { return color.substring(1); } - static registerLanguages( - languages: { - id: string; - conf: monaco.languages.LanguageConfiguration; - language: - | monaco.languages.IMonarchLanguage - | monaco.Thenable; - }[] - ): void { + static registerLanguages(languages: Language[]): void { // First override the default loader for any language we have a custom definition for // https://github.com/Microsoft/monaco-editor/issues/252#issuecomment-482786867 const languageIds = languages.map(({ id }) => id); @@ -221,11 +220,6 @@ class MonacoUtils { .forEach(languageParam => { const language = languageParam; log.debug2('Overriding default language loader:', language.id); - - // @ts-ignore - // language.loader = () => ({ - // then: () => undefined, - // }); }); // Then register our language definitions @@ -234,13 +228,7 @@ class MonacoUtils { }); } - static registerLanguage(language: { - id: string; - conf: monaco.languages.LanguageConfiguration; - language: - | monaco.languages.IMonarchLanguage - | monaco.Thenable; - }): void { + static registerLanguage(language: Language): void { log.debug2('Registering language: ', language.id); monaco.languages.register(language); @@ -398,14 +386,20 @@ class MonacoUtils { ]; try { - keybindings.forEach(keybinding => + keybindings.forEach(keybinding => { + if ( + (MonacoUtils.isMacPlatform() && keybinding.mac === '') || + (!MonacoUtils.isMacPlatform() && keybinding.windows === '') + ) { + return; + } MonacoUtils.removeKeybinding( editor, (MonacoUtils.isMacPlatform() ? keybinding.mac : keybinding.windows) as string - ) - ); + ); + }); } catch (err) { // This is probably only caused by Monaco changing private methods used here log.error(err); @@ -428,9 +422,6 @@ class MonacoUtils { editor: monaco.editor.IStandaloneCodeEditor, keybinding: string ): void { - if (!keybinding) { - return; - } /* eslint-disable no-underscore-dangle */ // It's possible a single keybinding has multiple commands depending on context // @ts-ignore diff --git a/packages/console/src/monaco/lang/db.ts b/packages/console/src/monaco/lang/db.ts index b198430399..03e18f15ee 100644 --- a/packages/console/src/monaco/lang/db.ts +++ b/packages/console/src/monaco/lang/db.ts @@ -1,5 +1,3 @@ -import { Language } from './Language'; - /* eslint no-useless-escape: "off" */ const id = 'deephavenDb'; @@ -288,5 +286,4 @@ const language = { }, }; -const lang: Language = { id, conf, language } as const; -export default lang; +export default { id, conf, language }; diff --git a/packages/console/src/notebook/ScriptEditor.tsx b/packages/console/src/notebook/ScriptEditor.tsx index fea40c3f4c..118c153785 100644 --- a/packages/console/src/notebook/ScriptEditor.tsx +++ b/packages/console/src/notebook/ScriptEditor.tsx @@ -20,7 +20,7 @@ interface ScriptEditorProps { isLoaded: boolean; focusOnMount?: boolean; onChange: (e: editor.IModelContentChangedEvent) => void; - onRunCommand: (command: string | null) => void; + onRunCommand: (command: string) => void; session: IdeSession; sessionLanguage?: string; settings?: { @@ -67,10 +67,9 @@ class ScriptEditor extends Component { componentDidUpdate(prevProps: ScriptEditorProps): void { const { sessionLanguage, settings } = this.props; - let language = ''; - if (settings) { - language = settings.language; - } + + const language = settings?.language; + const languageChanged = language !== prevProps.settings?.language; if (languageChanged) { log.debug('Set language', language); @@ -182,7 +181,9 @@ class ScriptEditor extends Component { handleRun(): void { const { onRunCommand } = this.props; const command = this.getValue(); - onRunCommand(command); + if (command != null) { + onRunCommand(command); + } } handleRunSelected(): void { @@ -316,8 +317,8 @@ class ScriptEditor extends Component { } } - setLanguage(language: string): void { - if (this.editorComponent.current) { + setLanguage(language?: string): void { + if (this.editorComponent.current && language) { this.editorComponent.current.setLanguage(language); } } From 71f01860ab5999e0004e0e8b81b87a93fa39692f Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Tue, 19 Jul 2022 15:59:54 -0400 Subject: [PATCH 19/26] WIP --- packages/console/src/monaco/lang/db.ts | 20 +++++++++++++------- packages/console/src/monaco/lang/groovy.ts | 14 +++++++++----- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/console/src/monaco/lang/db.ts b/packages/console/src/monaco/lang/db.ts index 03e18f15ee..61d4d85981 100644 --- a/packages/console/src/monaco/lang/db.ts +++ b/packages/console/src/monaco/lang/db.ts @@ -1,7 +1,10 @@ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; +import { Language } from './Language'; + /* eslint no-useless-escape: "off" */ const id = 'deephavenDb'; -const conf = { +const conf: monaco.languages.LanguageConfiguration = { comments: { lineComment: '#', blockComment: ["'''", "'''"], @@ -42,7 +45,9 @@ const conf = { }, }; -const language = { +const language: + | monaco.languages.IMonarchLanguage + | monaco.Thenable = { tokenPostfix: '.js', keywords: [ @@ -150,9 +155,9 @@ const language = { // define our own brackets as '<' and '>' do not match in javascript brackets: [ - ['(', ')', 'bracket.parenthesis'], - ['{', '}', 'bracket.curly'], - ['[', ']', 'bracket.square'], + { open: '{', close: '}', token: 'delimiter.curly' }, + { open: '[', close: ']', token: 'delimiter.bracket' }, + { open: '(', close: ')', token: 'delimiter.parenthesis' }, ], // common regular expressions @@ -257,7 +262,7 @@ const language = { [ /(\[)(\^?)(?=(?:[^\]\\\/]|\\.)+)/, [ - '@brackets.regexp.escape.control', + { token: '@brackets.regexp.escape.control' }, { token: 'regexp.escape.control', next: '@regexrange', @@ -286,4 +291,5 @@ const language = { }, }; -export default { id, conf, language }; +const lang: Language = { id, conf, language }; +export default lang; diff --git a/packages/console/src/monaco/lang/groovy.ts b/packages/console/src/monaco/lang/groovy.ts index cdbb49706e..f95efd151f 100644 --- a/packages/console/src/monaco/lang/groovy.ts +++ b/packages/console/src/monaco/lang/groovy.ts @@ -1,6 +1,9 @@ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; +import { Language } from './Language'; + const id = 'groovy'; -const conf = { +const conf: monaco.languages.LanguageConfiguration = { comments: { lineComment: '//', blockComment: ['/*', '*/'], @@ -120,9 +123,9 @@ const language = { ], brackets: [ - ['(', ')', 'delimiter.parenthesis'], - ['{', '}', 'delimiter.curly'], - ['[', ']', 'delimiter.square'], + { open: '{', close: '}', token: 'delimiter.curly' }, + { open: '[', close: ']', token: 'delimiter.square' }, + { open: '(', close: ')', token: 'delimiter.parenthesis' }, ], // operator symbols @@ -339,4 +342,5 @@ const language = { }, }; -export default { id, conf, language }; +const lang: Language = { id, conf, language }; +export default lang; From 8d0c648b5b4a37461de255015cb7b8d6425a9a98 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Tue, 19 Jul 2022 17:45:43 -0400 Subject: [PATCH 20/26] added new changes --- packages/console/src/Console.tsx | 16 ++++++++-------- packages/console/src/monaco/MonacoUtils.ts | 8 +------- packages/console/src/monaco/lang/groovy.ts | 4 +++- packages/console/src/monaco/lang/log.ts | 9 +++++++-- packages/console/src/monaco/lang/python.ts | 19 ++++++++++++------- packages/console/src/monaco/lang/scala.ts | 18 ++++++++++++------ 6 files changed, 43 insertions(+), 31 deletions(-) diff --git a/packages/console/src/Console.tsx b/packages/console/src/Console.tsx index 65170949a7..0ac9314bd7 100644 --- a/packages/console/src/Console.tsx +++ b/packages/console/src/Console.tsx @@ -122,7 +122,7 @@ export class Console extends PureComponent { /** * Check if the provided log level is an error type * @param logLevel The LogLevel being checked - * @returns= true if the log level is an error level log + * @returns true if the log level is an error level log */ static isErrorLevel( logLevel: typeof LogLevel[keyof typeof LogLevel] @@ -136,7 +136,7 @@ export class Console extends PureComponent { /** * Check if the provided log level is output level - * @para logLevel The LogLevel being checked + * @param logLevel The LogLevel being checked * @return true if the log level should be output to the console */ static isOutputLevel(logLevel: string): boolean { @@ -513,13 +513,13 @@ export class Console extends PureComponent { const oldIndex = objectHistoryMap.get(title); // oldIndex can be -1 if a object is active but doesn't have a command in consoleHistory // this can happen after clearing the console using 'clear' or 'cls' command - assertNotNull(oldIndex); - if (oldIndex >= 0) { + + if (oldIndex != null && oldIndex >= 0) { // disable outdated object variable in the old consoleHistory item - history[oldIndex].disabledObjects = history[ - oldIndex - ].disabledObjects?.concat(title); - history[oldIndex] = { ...history[oldIndex] }; + history[oldIndex].disabledObjects = [ + ...(history[oldIndex].disabledObjects ?? []), + title, + ]; } objectHistoryMap.set(title, itemIndex); if (isRemoved) { diff --git a/packages/console/src/monaco/MonacoUtils.ts b/packages/console/src/monaco/MonacoUtils.ts index 9af53d47ab..35e9c57465 100644 --- a/packages/console/src/monaco/MonacoUtils.ts +++ b/packages/console/src/monaco/MonacoUtils.ts @@ -190,13 +190,7 @@ class MonacoUtils { log.debug2('monaco theme: ', MonacoTheme); monaco.editor.setTheme('dh-dark'); - registerLanguages([ - DbLang, - PyLang, - GroovyLang, - LogLang, - ScalaLang, - ] as Language[]); + registerLanguages([DbLang, PyLang, GroovyLang, LogLang, ScalaLang]); log.debug('Monaco initialized.'); } diff --git a/packages/console/src/monaco/lang/groovy.ts b/packages/console/src/monaco/lang/groovy.ts index f95efd151f..4eeda8f2de 100644 --- a/packages/console/src/monaco/lang/groovy.ts +++ b/packages/console/src/monaco/lang/groovy.ts @@ -44,7 +44,9 @@ const conf: monaco.languages.LanguageConfiguration = { }, }; -const language = { +const language: + | monaco.languages.IMonarchLanguage + | monaco.Thenable = { // Set defaultToken to invalid to see what you do not tokenize yet // defaultToken: 'invalid', diff --git a/packages/console/src/monaco/lang/log.ts b/packages/console/src/monaco/lang/log.ts index e0fb89eccc..948ede509e 100644 --- a/packages/console/src/monaco/lang/log.ts +++ b/packages/console/src/monaco/lang/log.ts @@ -1,10 +1,14 @@ /* eslint no-useless-escape: "off" */ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; +import { Language } from './Language'; const id = 'log'; const conf = {}; -const language = { +const language: + | monaco.languages.IMonarchLanguage + | monaco.Thenable = { tokenizer: { root: [ [/ FATAL[\s\S]*/, { token: 'error', next: '@error' }], @@ -30,4 +34,5 @@ const language = { }, }; -export default { id, conf, language }; +const lang: Language = { id, conf, language }; +export default lang; diff --git a/packages/console/src/monaco/lang/python.ts b/packages/console/src/monaco/lang/python.ts index bcee3d69d4..39a4c359a1 100644 --- a/packages/console/src/monaco/lang/python.ts +++ b/packages/console/src/monaco/lang/python.ts @@ -1,6 +1,9 @@ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; +import { Language } from './Language'; + const id = 'python'; -const conf = { +const conf: monaco.languages.LanguageConfiguration = { comments: { lineComment: '#', blockComment: ["'''", "'''"], @@ -41,7 +44,9 @@ const conf = { }, }; -const language = { +const language: + | monaco.languages.IMonarchLanguage + | monaco.Thenable = { // Set defaultToken to invalid to see what you do not tokenize yet // defaultToken: 'invalid', @@ -116,11 +121,10 @@ const language = { '<<=', '**=', ], - brackets: [ - ['(', ')', 'delimiter.parenthesis'], - ['{', '}', 'delimiter.curly'], - ['[', ']', 'delimiter.square'], + { open: '{', close: '}', token: 'delimiter.curly' }, + { open: '[', close: ']', token: 'delimiter.square' }, + { open: '(', close: ')', token: 'delimiter.parenthesis' }, ], // operator symbols @@ -347,4 +351,5 @@ const language = { }, }; -export default { id, conf, language }; +const lang: Language = { id, conf, language }; +export default lang; diff --git a/packages/console/src/monaco/lang/scala.ts b/packages/console/src/monaco/lang/scala.ts index dec9ead0b3..a09235b5a2 100644 --- a/packages/console/src/monaco/lang/scala.ts +++ b/packages/console/src/monaco/lang/scala.ts @@ -26,9 +26,12 @@ * - https://github.com/microsoft/monaco-languages/blob/main/src/scala/scala.ts *--------------------------------------------------------------------------------------------*/ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; +import { Language } from './Language'; + const id = 'scala'; -const conf = { +const conf: monaco.languages.LanguageConfiguration = { /* * `...` is allowed as an identifier. * $ is allowed in identifiers. @@ -67,7 +70,9 @@ const conf = { }, }; -const language = { +const language: + | monaco.languages.IMonarchLanguage + | monaco.Thenable = { // tokenPostfix: '.scala', // We can't easily add everything from Dotty, but we can at least add some of its keywords @@ -194,7 +199,7 @@ const language = { [ /(\.)(@name|@symbols)/, [ - 'operator', + { token: 'operator' }, { token: '@rematch', next: '@allowMethod', @@ -249,8 +254,8 @@ const language = { [ /(')(@escapes)(')/, [ - 'string', - 'string.escape', + { token: 'string' }, + { token: 'string.escape' }, { token: 'string', next: '@allowMethod', @@ -482,4 +487,5 @@ const language = { }, }; -export default { id, conf, language }; +const lang: Language = { id, conf, language }; +export default lang; From fcf795b1d69b24013a537fbcc5b251f3a4885833 Mon Sep 17 00:00:00 2001 From: Tony Zhou <75397821+SparkyFnay@users.noreply.github.com> Date: Wed, 20 Jul 2022 14:10:00 -0400 Subject: [PATCH 21/26] Update packages/console/src/monaco/lang/log.ts Co-authored-by: Mike Bender --- packages/console/src/monaco/lang/log.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/console/src/monaco/lang/log.ts b/packages/console/src/monaco/lang/log.ts index 948ede509e..2d68ce7617 100644 --- a/packages/console/src/monaco/lang/log.ts +++ b/packages/console/src/monaco/lang/log.ts @@ -6,9 +6,7 @@ const id = 'log'; const conf = {}; -const language: - | monaco.languages.IMonarchLanguage - | monaco.Thenable = { +const language: monaco.languages.IMonarchLanguage = { tokenizer: { root: [ [/ FATAL[\s\S]*/, { token: 'error', next: '@error' }], From 7bca9dbf2ee60a099de696e5dd8e2b97ce70356b Mon Sep 17 00:00:00 2001 From: Tony Zhou <75397821+SparkyFnay@users.noreply.github.com> Date: Wed, 20 Jul 2022 14:10:21 -0400 Subject: [PATCH 22/26] Update packages/console/src/monaco/lang/groovy.ts Co-authored-by: Mike Bender --- packages/console/src/monaco/lang/groovy.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/console/src/monaco/lang/groovy.ts b/packages/console/src/monaco/lang/groovy.ts index 4eeda8f2de..d16c116191 100644 --- a/packages/console/src/monaco/lang/groovy.ts +++ b/packages/console/src/monaco/lang/groovy.ts @@ -44,9 +44,7 @@ const conf: monaco.languages.LanguageConfiguration = { }, }; -const language: - | monaco.languages.IMonarchLanguage - | monaco.Thenable = { +const language: monaco.languages.IMonarchLanguage = { // Set defaultToken to invalid to see what you do not tokenize yet // defaultToken: 'invalid', From 7b6e5df9746428b7a5dee03dbbdff39dd48310e1 Mon Sep 17 00:00:00 2001 From: Tony Zhou <75397821+SparkyFnay@users.noreply.github.com> Date: Wed, 20 Jul 2022 14:10:30 -0400 Subject: [PATCH 23/26] Update packages/console/src/monaco/lang/scala.ts Co-authored-by: Mike Bender --- packages/console/src/monaco/lang/scala.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/console/src/monaco/lang/scala.ts b/packages/console/src/monaco/lang/scala.ts index a09235b5a2..c0127caaa2 100644 --- a/packages/console/src/monaco/lang/scala.ts +++ b/packages/console/src/monaco/lang/scala.ts @@ -70,9 +70,7 @@ const conf: monaco.languages.LanguageConfiguration = { }, }; -const language: - | monaco.languages.IMonarchLanguage - | monaco.Thenable = { +const language: monaco.languages.IMonarchLanguage = { // tokenPostfix: '.scala', // We can't easily add everything from Dotty, but we can at least add some of its keywords From 2a076fad680cb23a339e90a856029486e0a710f4 Mon Sep 17 00:00:00 2001 From: Tony Zhou <75397821+SparkyFnay@users.noreply.github.com> Date: Wed, 20 Jul 2022 14:10:38 -0400 Subject: [PATCH 24/26] Update packages/console/src/monaco/lang/python.ts Co-authored-by: Mike Bender --- packages/console/src/monaco/lang/python.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/console/src/monaco/lang/python.ts b/packages/console/src/monaco/lang/python.ts index 39a4c359a1..dc5c6eb0cc 100644 --- a/packages/console/src/monaco/lang/python.ts +++ b/packages/console/src/monaco/lang/python.ts @@ -44,9 +44,7 @@ const conf: monaco.languages.LanguageConfiguration = { }, }; -const language: - | monaco.languages.IMonarchLanguage - | monaco.Thenable = { +const language: monaco.languages.IMonarchLanguage = { // Set defaultToken to invalid to see what you do not tokenize yet // defaultToken: 'invalid', From 320c8ebf35fb8db6c303dc243180fccb2862a09b Mon Sep 17 00:00:00 2001 From: Tony Zhou <75397821+SparkyFnay@users.noreply.github.com> Date: Wed, 20 Jul 2022 14:10:47 -0400 Subject: [PATCH 25/26] Update packages/console/src/monaco/lang/db.ts Co-authored-by: Mike Bender --- packages/console/src/monaco/lang/db.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/console/src/monaco/lang/db.ts b/packages/console/src/monaco/lang/db.ts index 61d4d85981..44423be0c2 100644 --- a/packages/console/src/monaco/lang/db.ts +++ b/packages/console/src/monaco/lang/db.ts @@ -45,9 +45,7 @@ const conf: monaco.languages.LanguageConfiguration = { }, }; -const language: - | monaco.languages.IMonarchLanguage - | monaco.Thenable = { +const language: monaco.languages.IMonarchLanguage = { tokenPostfix: '.js', keywords: [ From 15be02ea9d145aca06953ea68e9a8325b0eacc51 Mon Sep 17 00:00:00 2001 From: Tony Zhou Date: Wed, 20 Jul 2022 17:30:52 -0400 Subject: [PATCH 26/26] added space --- packages/console/src/ConsoleInput.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/console/src/ConsoleInput.tsx b/packages/console/src/ConsoleInput.tsx index e4fb1c5248..0ee2f09845 100644 --- a/packages/console/src/ConsoleInput.tsx +++ b/packages/console/src/ConsoleInput.tsx @@ -40,6 +40,7 @@ interface ConsoleInputState { isFocused: boolean; model: monaco.editor.ITextModel | null; } + /** * Component for input in a console session. Handles loading the recent command history */