From f719e24e85554035e99915e4a729c62de0a2106e Mon Sep 17 00:00:00 2001 From: Matthew Runyon Date: Mon, 13 Mar 2023 14:47:40 -0500 Subject: [PATCH 1/3] Fix fast refresh invalidations --- package-lock.json | 19 ++++++++++++++++++- packages/code-studio/src/index.tsx | 1 + packages/code-studio/src/main/AppInit.tsx | 4 +++- .../code-studio/src/main/AppMainContainer.tsx | 4 +++- .../settings/ColumnSpecificSectionContent.tsx | 8 +++++--- .../src/settings/FormattingSectionContent.tsx | 8 +++++--- .../src/settings/ShortcutsSectionContent.tsx | 4 +++- .../src/styleguide/StyleGuideInit.tsx | 4 +++- packages/code-studio/src/styleguide/index.tsx | 1 + packages/components/src/DateTimeInput.tsx | 4 ++-- packages/components/src/MaskedInput.tsx | 17 +++-------------- packages/components/src/MaskedInputUtils.ts | 12 ++++++++++++ packages/components/src/TimeInput.tsx | 6 ++---- .../command-history/{index.tsx => index.ts} | 0 .../console-history/{index.tsx => index.ts} | 0 .../src/ConsolePlugin.tsx | 2 +- .../dashboard-core-plugins/src/GridPlugin.tsx | 2 +- .../src/linker/Linker.tsx | 4 +++- .../{LinkerUtils.tsx => LinkerUtils.ts} | 0 .../src/panels/ChartPanel.tsx | 10 ++++------ .../src/panels/ChartPanelUtils.ts | 8 ++++++++ .../src/panels/CommandHistoryPanel.tsx | 8 +++++--- .../src/panels/ConsolePanel.tsx | 8 +++++--- .../src/panels/DropdownFilterPanel.tsx | 4 +++- .../src/panels/FileExplorerPanel.tsx | 4 +++- .../src/panels/FilterSetManagerPanel.tsx | 4 +++- .../src/panels/InputFilterPanel.tsx | 8 +++++--- .../src/panels/IrisGridPanel.tsx | 8 +++++--- .../src/panels/LogPanel.tsx | 8 +++++--- .../src/panels/MarkdownPanel.tsx | 8 +++++--- .../src/panels/NotebookPanel.tsx | 4 +++- .../src/panels/index.ts | 1 + packages/embed-chart/src/index.tsx | 1 + packages/embed-grid/src/App.tsx | 2 +- packages/embed-grid/src/index.tsx | 1 + packages/eslint-config/index.js | 10 +++++++++- packages/eslint-config/package.json | 3 ++- packages/file-explorer/src/FileExplorer.tsx | 2 +- packages/file-explorer/src/FileList.tsx | 19 +++++++++---------- .../file-explorer/src/FileListContainer.tsx | 8 ++++---- packages/file-explorer/src/FileListUtils.ts | 2 ++ packages/grid/src/CellInputField.tsx | 4 +--- packages/iris-grid/src/GotoRow.tsx | 2 +- .../src/sidebar/aggregations/Aggregations.tsx | 2 +- 44 files changed, 154 insertions(+), 85 deletions(-) create mode 100644 packages/components/src/MaskedInputUtils.ts rename packages/console/src/command-history/{index.tsx => index.ts} (100%) rename packages/console/src/console-history/{index.tsx => index.ts} (100%) rename packages/dashboard-core-plugins/src/linker/{LinkerUtils.tsx => LinkerUtils.ts} (100%) create mode 100644 packages/dashboard-core-plugins/src/panels/ChartPanelUtils.ts create mode 100644 packages/file-explorer/src/FileListUtils.ts diff --git a/package-lock.json b/package-lock.json index 91e9c8d764..7d8b65b0a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11715,6 +11715,15 @@ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" } }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.3.4.tgz", + "integrity": "sha512-E0ViBglxSQAERBp6eTj5fPgtCRtDonnbCFiVQBhf4Dto2blJRxg1dFUMdMh7N6ljTI4UwPhHwYDQ3Dyo4m6bwA==", + "peer": true, + "peerDependencies": { + "eslint": ">=7" + } + }, "node_modules/eslint-plugin-react/node_modules/doctrine": { "version": "2.1.0", "license": "Apache-2.0", @@ -26342,7 +26351,8 @@ "eslint": "^8.29.0", "eslint-import-resolver-typescript": "^3.5.0", "eslint-plugin-es": "^4.1.0", - "eslint-plugin-prettier": "^3.3.1" + "eslint-plugin-prettier": "^3.3.1", + "eslint-plugin-react-refresh": "0.3.4" } }, "packages/file-explorer": { @@ -35791,6 +35801,13 @@ "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", "requires": {} }, + "eslint-plugin-react-refresh": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.3.4.tgz", + "integrity": "sha512-E0ViBglxSQAERBp6eTj5fPgtCRtDonnbCFiVQBhf4Dto2blJRxg1dFUMdMh7N6ljTI4UwPhHwYDQ3Dyo4m6bwA==", + "peer": true, + "requires": {} + }, "eslint-plugin-testing-library": { "version": "5.9.1", "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.9.1.tgz", diff --git a/packages/code-studio/src/index.tsx b/packages/code-studio/src/index.tsx index 0988791298..a6015db335 100644 --- a/packages/code-studio/src/index.tsx +++ b/packages/code-studio/src/index.tsx @@ -8,6 +8,7 @@ import logInit from './log/LogInit'; logInit(); +// eslint-disable-next-line react-refresh/only-export-components const AppRoot = React.lazy(() => import('./AppRoot')); ReactDOM.render( diff --git a/packages/code-studio/src/main/AppInit.tsx b/packages/code-studio/src/main/AppInit.tsx index 403ddd8ba6..a468bc6b6d 100644 --- a/packages/code-studio/src/main/AppInit.tsx +++ b/packages/code-studio/src/main/AppInit.tsx @@ -377,7 +377,7 @@ const mapStateToProps = (state: RootState) => ({ workspaceStorage: getWorkspaceStorage(state), }); -export default connect(mapStateToProps, { +const ConnectedAppInit = connect(mapStateToProps, { setActiveTool: setActiveToolAction, setCommandHistoryStorage: setCommandHistoryStorageAction, setDashboardData: setDashboardDataAction, @@ -391,3 +391,5 @@ export default connect(mapStateToProps, { setWorkspaceStorage: setWorkspaceStorageAction, setServerConfigValues: setServerConfigValuesAction, })(AppInit); + +export default ConnectedAppInit; diff --git a/packages/code-studio/src/main/AppMainContainer.tsx b/packages/code-studio/src/main/AppMainContainer.tsx index 79dab51710..11f0cd3488 100644 --- a/packages/code-studio/src/main/AppMainContainer.tsx +++ b/packages/code-studio/src/main/AppMainContainer.tsx @@ -904,8 +904,10 @@ const mapStateToProps = (state: RootState) => ({ serverConfigValues: getServerConfigValues(state), }); -export default connect(mapStateToProps, { +const ConnectedAppMainContainer = connect(mapStateToProps, { setActiveTool: setActiveToolAction, updateDashboardData: updateDashboardDataAction, updateWorkspaceData: updateWorkspaceDataAction, })(withRouter(AppMainContainer)); + +export default ConnectedAppMainContainer; diff --git a/packages/code-studio/src/settings/ColumnSpecificSectionContent.tsx b/packages/code-studio/src/settings/ColumnSpecificSectionContent.tsx index 802e9e43de..a4f6a2b0ca 100644 --- a/packages/code-studio/src/settings/ColumnSpecificSectionContent.tsx +++ b/packages/code-studio/src/settings/ColumnSpecificSectionContent.tsx @@ -645,6 +645,8 @@ const mapStateToProps = (state: RootState) => ({ settings: getSettings(state), }); -export default connect(mapStateToProps, { saveSettings: saveSettingsAction })( - ColumnSpecificSectionContent -); +const ConnectedColumnSpecificSectionContent = connect(mapStateToProps, { + saveSettings: saveSettingsAction, +})(ColumnSpecificSectionContent); + +export default ConnectedColumnSpecificSectionContent; diff --git a/packages/code-studio/src/settings/FormattingSectionContent.tsx b/packages/code-studio/src/settings/FormattingSectionContent.tsx index 7ba47ec4ff..e7db4ba3ed 100644 --- a/packages/code-studio/src/settings/FormattingSectionContent.tsx +++ b/packages/code-studio/src/settings/FormattingSectionContent.tsx @@ -632,6 +632,8 @@ const mapStateToProps = (state: RootState) => ({ settings: getSettings(state), }); -export default connect(mapStateToProps, { saveSettings: saveSettingsAction })( - FormattingSectionContent -); +const ConnectedFormattingSectionContent = connect(mapStateToProps, { + saveSettings: saveSettingsAction, +})(FormattingSectionContent); + +export default ConnectedFormattingSectionContent; diff --git a/packages/code-studio/src/settings/ShortcutsSectionContent.tsx b/packages/code-studio/src/settings/ShortcutsSectionContent.tsx index 6b5f8e8913..70b594bc93 100644 --- a/packages/code-studio/src/settings/ShortcutsSectionContent.tsx +++ b/packages/code-studio/src/settings/ShortcutsSectionContent.tsx @@ -143,7 +143,9 @@ const mapStateToProps = (state: RootState) => ({ const mapDispatchToProps = { saveSettings: saveSettingsAction }; -export default connect( +const ConnectedShortcutSectionContent = connect( mapStateToProps, mapDispatchToProps )(ShortcutSectionContent); + +export default ConnectedShortcutSectionContent; diff --git a/packages/code-studio/src/styleguide/StyleGuideInit.tsx b/packages/code-studio/src/styleguide/StyleGuideInit.tsx index eb6ac1dff8..bceb90b490 100644 --- a/packages/code-studio/src/styleguide/StyleGuideInit.tsx +++ b/packages/code-studio/src/styleguide/StyleGuideInit.tsx @@ -44,6 +44,8 @@ const mapStateToProps = (state: RootState) => ({ workspace: getWorkspace(state), }); -export default connect(mapStateToProps, { +const ConnectedStyleGuideInit = connect(mapStateToProps, { setWorkspace: setWorkspaceAction, })(StyleGuideInit); + +export default ConnectedStyleGuideInit; diff --git a/packages/code-studio/src/styleguide/index.tsx b/packages/code-studio/src/styleguide/index.tsx index 47575ca9c6..c60aed01dd 100644 --- a/packages/code-studio/src/styleguide/index.tsx +++ b/packages/code-studio/src/styleguide/index.tsx @@ -8,6 +8,7 @@ import logInit from '../log/LogInit'; logInit(); +// eslint-disable-next-line react-refresh/only-export-components const StyleGuideRoot = React.lazy(() => import('./StyleGuideRoot')); ReactDOM.render( diff --git a/packages/components/src/DateTimeInput.tsx b/packages/components/src/DateTimeInput.tsx index 154e4fd718..3d70fcd44d 100644 --- a/packages/components/src/DateTimeInput.tsx +++ b/packages/components/src/DateTimeInput.tsx @@ -26,7 +26,7 @@ type DateTimeInputProps = { 'data-testid'?: string; }; -export function fixIncompleteValue(value: string): string { +function fixIncompleteValue(value: string): string { if (value != null && value.length >= DATE_VALUE_STRING.length) { return `${value.substring(0, DATE_VALUE_STRING.length)}${value .substring(DATE_VALUE_STRING.length) @@ -35,7 +35,7 @@ export function fixIncompleteValue(value: string): string { return value; } -export function addSeparators(value: string): string { +function addSeparators(value: string): string { const dateTimeMillis = value.substring(0, 23); const micros = value.substring(23, 26); const nanos = value.substring(26); diff --git a/packages/components/src/MaskedInput.tsx b/packages/components/src/MaskedInput.tsx index 24183ac814..806085289f 100644 --- a/packages/components/src/MaskedInput.tsx +++ b/packages/components/src/MaskedInput.tsx @@ -2,6 +2,7 @@ import React, { useMemo, useEffect, useCallback } from 'react'; import classNames from 'classnames'; import Log from '@deephaven/log'; import { useForwardedRef } from '@deephaven/react-hooks'; +import { DEFAULT_GET_PREFERRED_REPLACEMENT_STRING } from './MaskedInputUtils'; import './MaskedInput.scss'; const log = Log.module('MaskedInput'); @@ -18,18 +19,6 @@ const SELECTION_DIRECTION = { */ const FIXED_WIDTH_SPACE = '\u2007'; -export function DEFAULT_GET_PREFERRED_REPLACEMENT_STRING( - value: string, - replaceIndex: number, - newChar: string -): string { - return ( - value.substring(0, replaceIndex) + - newChar + - value.substring(replaceIndex + 1) - ); -} - /** * Fill the string on the right side with the example value to the given length * @param checkValue Initial string to pad @@ -37,7 +26,7 @@ export function DEFAULT_GET_PREFERRED_REPLACEMENT_STRING( * @param length Target length * @returns String padded with the given example value */ -export function fillToLength( +function fillToLength( checkValue: string, exampleValue: string, length: number @@ -53,7 +42,7 @@ export function fillToLength( * @param emptyMask Empty mask * @returns Trimmed string */ -export function trimTrailingMask(value: string, emptyMask: string): string { +function trimTrailingMask(value: string, emptyMask: string): string { let { length } = value; for (let i = value.length - 1; i >= 0; i -= 1) { if (emptyMask[i] === value[i]) { diff --git a/packages/components/src/MaskedInputUtils.ts b/packages/components/src/MaskedInputUtils.ts new file mode 100644 index 0000000000..1fa644d2ac --- /dev/null +++ b/packages/components/src/MaskedInputUtils.ts @@ -0,0 +1,12 @@ +/* eslint-disable import/prefer-default-export */ +export function DEFAULT_GET_PREFERRED_REPLACEMENT_STRING( + value: string, + replaceIndex: number, + newChar: string +): string { + return ( + value.substring(0, replaceIndex) + + newChar + + value.substring(replaceIndex + 1) + ); +} diff --git a/packages/components/src/TimeInput.tsx b/packages/components/src/TimeInput.tsx index c05a770194..c7f7b61c17 100644 --- a/packages/components/src/TimeInput.tsx +++ b/packages/components/src/TimeInput.tsx @@ -7,10 +7,8 @@ import React, { } from 'react'; import Log from '@deephaven/log'; import { TimeUtils } from '@deephaven/utils'; -import MaskedInput, { - DEFAULT_GET_PREFERRED_REPLACEMENT_STRING, - SelectionSegment, -} from './MaskedInput'; +import MaskedInput, { SelectionSegment } from './MaskedInput'; +import { DEFAULT_GET_PREFERRED_REPLACEMENT_STRING } from './MaskedInputUtils'; export type { SelectionSegment } from './MaskedInput'; diff --git a/packages/console/src/command-history/index.tsx b/packages/console/src/command-history/index.ts similarity index 100% rename from packages/console/src/command-history/index.tsx rename to packages/console/src/command-history/index.ts diff --git a/packages/console/src/console-history/index.tsx b/packages/console/src/console-history/index.ts similarity index 100% rename from packages/console/src/console-history/index.tsx rename to packages/console/src/console-history/index.ts diff --git a/packages/dashboard-core-plugins/src/ConsolePlugin.tsx b/packages/dashboard-core-plugins/src/ConsolePlugin.tsx index 650543f388..1fd32f171e 100644 --- a/packages/dashboard-core-plugins/src/ConsolePlugin.tsx +++ b/packages/dashboard-core-plugins/src/ConsolePlugin.tsx @@ -44,7 +44,7 @@ export type ConsolePluginProps = DashboardPluginComponentProps & { notebooksUrl: string; }; -export function assertIsConsolePluginProps( +function assertIsConsolePluginProps( props: Partial ): asserts props is ConsolePluginProps { assertIsDashboardPluginProps(props); diff --git a/packages/dashboard-core-plugins/src/GridPlugin.tsx b/packages/dashboard-core-plugins/src/GridPlugin.tsx index acd844183a..f16dd00652 100644 --- a/packages/dashboard-core-plugins/src/GridPlugin.tsx +++ b/packages/dashboard-core-plugins/src/GridPlugin.tsx @@ -12,7 +12,7 @@ import { Table, VariableDefinition } from '@deephaven/jsapi-shim'; import shortid from 'shortid'; import { IrisGridPanel, IrisGridPanelProps } from './panels'; -export const SUPPORTED_TYPES: string[] = [ +const SUPPORTED_TYPES: string[] = [ dh.VariableType.TABLE, dh.VariableType.TREETABLE, dh.VariableType.HIERARCHICALTABLE, diff --git a/packages/dashboard-core-plugins/src/linker/Linker.tsx b/packages/dashboard-core-plugins/src/linker/Linker.tsx index eb7eb6c259..32a70b8542 100644 --- a/packages/dashboard-core-plugins/src/linker/Linker.tsx +++ b/packages/dashboard-core-plugins/src/linker/Linker.tsx @@ -748,4 +748,6 @@ export class Linker extends Component { } } -export default connector(Linker); +const ConnectedLinker = connector(Linker); + +export default ConnectedLinker; diff --git a/packages/dashboard-core-plugins/src/linker/LinkerUtils.tsx b/packages/dashboard-core-plugins/src/linker/LinkerUtils.ts similarity index 100% rename from packages/dashboard-core-plugins/src/linker/LinkerUtils.tsx rename to packages/dashboard-core-plugins/src/linker/LinkerUtils.ts diff --git a/packages/dashboard-core-plugins/src/panels/ChartPanel.tsx b/packages/dashboard-core-plugins/src/panels/ChartPanel.tsx index 77b0bbe4d4..3b7b5ea4e5 100644 --- a/packages/dashboard-core-plugins/src/panels/ChartPanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/ChartPanel.tsx @@ -65,6 +65,7 @@ import ChartColumnSelectorOverlay, { import './ChartPanel.scss'; import { Link } from '../linker/LinkerUtils'; import { PanelState as IrisGridPanelState } from './IrisGridPanel'; +import { isChartPanelTableMetadata } from './ChartPanelUtils'; import { ColumnSelectionValidator } from '../linker/ColumnSelectionValidator'; const log = Log.module('ChartPanel'); @@ -76,11 +77,6 @@ export type FilterMap = Map; export type LinkedColumnMap = Map; -export function isChartPanelTableMetadata( - metadata: ChartPanelMetadata -): metadata is ChartPanelTableMetadata { - return (metadata as ChartPanelTableMetadata).settings !== undefined; -} export type ChartPanelFigureMetadata = { figure: string; }; @@ -1198,7 +1194,7 @@ const mapStateToProps = ( }; }; -export default connect( +const ConnectedChartPanel = connect( mapStateToProps, { setActiveTool: setActiveToolAction, @@ -1207,3 +1203,5 @@ export default connect( null, { forwardRef: true } )(ChartPanel); + +export default ConnectedChartPanel; diff --git a/packages/dashboard-core-plugins/src/panels/ChartPanelUtils.ts b/packages/dashboard-core-plugins/src/panels/ChartPanelUtils.ts new file mode 100644 index 0000000000..232b73e94d --- /dev/null +++ b/packages/dashboard-core-plugins/src/panels/ChartPanelUtils.ts @@ -0,0 +1,8 @@ +import type { ChartPanelMetadata, ChartPanelTableMetadata } from './ChartPanel'; + +// eslint-disable-next-line import/prefer-default-export +export function isChartPanelTableMetadata( + metadata: ChartPanelMetadata +): metadata is ChartPanelTableMetadata { + return (metadata as ChartPanelTableMetadata).settings !== undefined; +} diff --git a/packages/dashboard-core-plugins/src/panels/CommandHistoryPanel.tsx b/packages/dashboard-core-plugins/src/panels/CommandHistoryPanel.tsx index 7dd2435489..3555d9b00f 100644 --- a/packages/dashboard-core-plugins/src/panels/CommandHistoryPanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/CommandHistoryPanel.tsx @@ -238,6 +238,8 @@ const mapStateToProps = ( }; }; -export default connect(mapStateToProps, null, null, { forwardRef: true })( - CommandHistoryPanel -); +const ConnectedCommandHistoryPanel = connect(mapStateToProps, null, null, { + forwardRef: true, +})(CommandHistoryPanel); + +export default ConnectedCommandHistoryPanel; diff --git a/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx b/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx index ec4ad31c19..6fdb7ba231 100644 --- a/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx @@ -382,6 +382,8 @@ const mapStateToProps = ( timeZone: getTimeZone(state), }); -export default connect(mapStateToProps, null, null, { forwardRef: true })( - ConsolePanel -); +const ConnectedConsolePanel = connect(mapStateToProps, null, null, { + forwardRef: true, +})(ConsolePanel); + +export default ConnectedConsolePanel; diff --git a/packages/dashboard-core-plugins/src/panels/DropdownFilterPanel.tsx b/packages/dashboard-core-plugins/src/panels/DropdownFilterPanel.tsx index 50811a9716..6668ea2227 100644 --- a/packages/dashboard-core-plugins/src/panels/DropdownFilterPanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/DropdownFilterPanel.tsx @@ -793,4 +793,6 @@ export class DropdownFilterPanel extends Component< } } -export default connector(DropdownFilterPanel); +const ConnectedDropdownFilterPanel = connector(DropdownFilterPanel); + +export default ConnectedDropdownFilterPanel; diff --git a/packages/dashboard-core-plugins/src/panels/FileExplorerPanel.tsx b/packages/dashboard-core-plugins/src/panels/FileExplorerPanel.tsx index aba3b30bd3..4525175a27 100644 --- a/packages/dashboard-core-plugins/src/panels/FileExplorerPanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/FileExplorerPanel.tsx @@ -279,4 +279,6 @@ export class FileExplorerPanel extends React.Component< } } -export default connector(FileExplorerPanel); +const ConnectedFileExplorerPanel = connector(FileExplorerPanel); + +export default ConnectedFileExplorerPanel; diff --git a/packages/dashboard-core-plugins/src/panels/FilterSetManagerPanel.tsx b/packages/dashboard-core-plugins/src/panels/FilterSetManagerPanel.tsx index 2884543967..588a8361d8 100644 --- a/packages/dashboard-core-plugins/src/panels/FilterSetManagerPanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/FilterSetManagerPanel.tsx @@ -423,7 +423,7 @@ const mapStateToProps = ( }; }; -export default connect( +const ConnectedFilterSetManagerPanel = connect( mapStateToProps, { setDashboardFilterSets: setDashboardFilterSetsAction, @@ -431,3 +431,5 @@ export default connect( null, { forwardRef: true } )(FilterSetManagerPanel); + +export default ConnectedFilterSetManagerPanel; diff --git a/packages/dashboard-core-plugins/src/panels/InputFilterPanel.tsx b/packages/dashboard-core-plugins/src/panels/InputFilterPanel.tsx index 0f3ddfbdbd..8741b7d3b3 100644 --- a/packages/dashboard-core-plugins/src/panels/InputFilterPanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/InputFilterPanel.tsx @@ -247,6 +247,8 @@ const mapStateToProps = ( }; }; -export default connect(mapStateToProps, null, null, { forwardRef: true })( - InputFilterPanel -); +const ConnectedInputFilterPanel = connect(mapStateToProps, null, null, { + forwardRef: true, +})(InputFilterPanel); + +export default ConnectedInputFilterPanel; diff --git a/packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx b/packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx index 3e3bf0898a..4845423cfd 100644 --- a/packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/IrisGridPanel.tsx @@ -1360,6 +1360,8 @@ const mapStateToProps = ( settings: getSettings(state), }); -export default connect(mapStateToProps, null, null, { forwardRef: true })( - IrisGridPanel -); +const ConnectedIrisGridPanel = connect(mapStateToProps, null, null, { + forwardRef: true, +})(IrisGridPanel); + +export default ConnectedIrisGridPanel; diff --git a/packages/dashboard-core-plugins/src/panels/LogPanel.tsx b/packages/dashboard-core-plugins/src/panels/LogPanel.tsx index 2ba31e29bb..d7909323dd 100644 --- a/packages/dashboard-core-plugins/src/panels/LogPanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/LogPanel.tsx @@ -131,6 +131,8 @@ const mapStateToProps = ( ?.session, }); -export default connect(mapStateToProps, null, null, { forwardRef: true })( - LogPanel -); +const ConnectedLogPanel = connect(mapStateToProps, null, null, { + forwardRef: true, +})(LogPanel); + +export default ConnectedLogPanel; diff --git a/packages/dashboard-core-plugins/src/panels/MarkdownPanel.tsx b/packages/dashboard-core-plugins/src/panels/MarkdownPanel.tsx index 70e8229460..927e6849b1 100644 --- a/packages/dashboard-core-plugins/src/panels/MarkdownPanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/MarkdownPanel.tsx @@ -255,6 +255,8 @@ const mapStateToProps = ( }; }; -export default connect(mapStateToProps, null, null, { forwardRef: true })( - MarkdownPanel -); +const ConnectedMarkdownPanel = connect(mapStateToProps, null, null, { + forwardRef: true, +})(MarkdownPanel); + +export default ConnectedMarkdownPanel; diff --git a/packages/dashboard-core-plugins/src/panels/NotebookPanel.tsx b/packages/dashboard-core-plugins/src/panels/NotebookPanel.tsx index 4815a76518..d070296fc6 100644 --- a/packages/dashboard-core-plugins/src/panels/NotebookPanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/NotebookPanel.tsx @@ -1317,9 +1317,11 @@ const mapStateToProps = ( }; }; -export default connect( +const ConnectedNotebookPanel = connect( mapStateToProps, { saveSettings: saveSettingsAction }, null, { forwardRef: true } )(NotebookPanel); + +export default ConnectedNotebookPanel; diff --git a/packages/dashboard-core-plugins/src/panels/index.ts b/packages/dashboard-core-plugins/src/panels/index.ts index bc61f6e621..af64cb2499 100644 --- a/packages/dashboard-core-plugins/src/panels/index.ts +++ b/packages/dashboard-core-plugins/src/panels/index.ts @@ -1,5 +1,6 @@ export { default as ChartPanel } from './ChartPanel'; export * from './ChartPanel'; +export * from './ChartPanelUtils'; export { default as CommandHistoryPanel } from './CommandHistoryPanel'; export { default as ConsolePanel } from './ConsolePanel'; export { default as DropdownFilterPanel } from './DropdownFilterPanel'; diff --git a/packages/embed-chart/src/index.tsx b/packages/embed-chart/src/index.tsx index 5efc61cf42..d45f2ec217 100644 --- a/packages/embed-chart/src/index.tsx +++ b/packages/embed-chart/src/index.tsx @@ -11,6 +11,7 @@ import { LoadingOverlay } from '@deephaven/components'; import { ApiBootstrap } from '@deephaven/jsapi-bootstrap'; import './index.scss'; +// eslint-disable-next-line react-refresh/only-export-components const App = React.lazy(() => import('./App')); ReactDOM.render( diff --git a/packages/embed-grid/src/App.tsx b/packages/embed-grid/src/App.tsx index 757babf336..0e634a22ff 100644 --- a/packages/embed-grid/src/App.tsx +++ b/packages/embed-grid/src/App.tsx @@ -15,7 +15,7 @@ Log.setLogLevel(parseInt(import.meta.env.VITE_LOG_LEVEL ?? '', 10)); const log = Log.module('EmbedGrid.App'); -export const SUPPORTED_TYPES: string[] = [ +const SUPPORTED_TYPES: string[] = [ dh.VariableType.TABLE, dh.VariableType.TREETABLE, dh.VariableType.HIERARCHICALTABLE, diff --git a/packages/embed-grid/src/index.tsx b/packages/embed-grid/src/index.tsx index 5efc61cf42..d45f2ec217 100644 --- a/packages/embed-grid/src/index.tsx +++ b/packages/embed-grid/src/index.tsx @@ -11,6 +11,7 @@ import { LoadingOverlay } from '@deephaven/components'; import { ApiBootstrap } from '@deephaven/jsapi-bootstrap'; import './index.scss'; +// eslint-disable-next-line react-refresh/only-export-components const App = React.lazy(() => import('./App')); ReactDOM.render( diff --git a/packages/eslint-config/index.js b/packages/eslint-config/index.js index a04aff8591..a8192db5f0 100644 --- a/packages/eslint-config/index.js +++ b/packages/eslint-config/index.js @@ -5,7 +5,14 @@ module.exports = { es6: true, }, extends: ['react-app', 'airbnb', 'plugin:react/recommended', 'prettier'], - plugins: ['es', 'prettier', 'react', 'react-hooks', 'import'], + plugins: [ + 'es', + 'prettier', + 'react', + 'react-hooks', + 'import', + 'react-refresh', + ], rules: { 'prettier/prettier': ['error'], 'react/forbid-prop-types': 'off', @@ -45,6 +52,7 @@ module.exports = { 'react/default-props-match-prop-types': 'off', 'react/require-default-props': 'off', 'react/jsx-no-bind': 'off', + 'react-refresh/only-export-components': 'warn', }, parserOptions: { ecmaFeatures: { diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 63c2b58f03..2ce8d7bf84 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -20,7 +20,8 @@ "eslint": "^8.29.0", "eslint-import-resolver-typescript": "^3.5.0", "eslint-plugin-es": "^4.1.0", - "eslint-plugin-prettier": "^3.3.1" + "eslint-plugin-prettier": "^3.3.1", + "eslint-plugin-react-refresh": "0.3.4" }, "publishConfig": { "access": "public" diff --git a/packages/file-explorer/src/FileExplorer.tsx b/packages/file-explorer/src/FileExplorer.tsx index cd8d55071d..0178e48d3d 100644 --- a/packages/file-explorer/src/FileExplorer.tsx +++ b/packages/file-explorer/src/FileExplorer.tsx @@ -2,7 +2,7 @@ import { BasicModal } from '@deephaven/components'; import Log from '@deephaven/log'; import { CancelablePromise, PromiseUtils } from '@deephaven/utils'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { DEFAULT_ROW_HEIGHT } from './FileList'; +import { DEFAULT_ROW_HEIGHT } from './FileListUtils'; import FileStorage, { FileStorageItem, FileStorageTable, diff --git a/packages/file-explorer/src/FileList.tsx b/packages/file-explorer/src/FileList.tsx index 373d9c2186..bda3d9bb6a 100644 --- a/packages/file-explorer/src/FileList.tsx +++ b/packages/file-explorer/src/FileList.tsx @@ -20,6 +20,7 @@ import React, { import { FileStorageItem, FileStorageTable, isDirectory } from './FileStorage'; import './FileList.scss'; import FileUtils, { MIME_TYPE } from './FileUtils'; +import { DEFAULT_ROW_HEIGHT } from './FileListUtils'; const log = Log.module('FileList'); @@ -66,21 +67,19 @@ export interface FileListProps { overscanCount?: number; } -export const getPathFromItem = (file: FileStorageItem): string => +const getPathFromItem = (file: FileStorageItem): string => isDirectory(file) ? FileUtils.makePath(file.filename) : FileUtils.getPath(file.filename); -export const DEFAULT_ROW_HEIGHT = 26; - // How long you need to hover over a directory before it expands -export const DRAG_HOVER_TIMEOUT = 500; +const DRAG_HOVER_TIMEOUT = 500; const ITEM_LIST_CLASS_NAME = 'item-list-scroll-pane'; -export const renderFileListItem = ( +export function RenderFileListItem( props: FileListRenderItemProps -): JSX.Element => { +): JSX.Element { const { children, draggedItems, @@ -156,14 +155,14 @@ export const renderFileListItem = ( ); -}; +} /** * Get the icon definition for a file or folder item * @param item Item to get the icon for * @returns Icon definition to pass in the FontAwesomeIcon icon prop */ -export function getItemIcon(item: FileStorageItem): IconDefinition { +function getItemIcon(item: FileStorageItem): IconDefinition { if (isDirectory(item)) { return item.isExpanded ? vsFolderOpened : vsFolder; } @@ -179,7 +178,7 @@ export function getItemIcon(item: FileStorageItem): IconDefinition { /** * Get the move operation for the current selection and the given target. Throws if the operation is invalid. */ -export function getMoveOperation( +function getMoveOperation( draggedItems: FileStorageItem[], targetItem: FileStorageItem ): { files: FileStorageItem[]; targetPath: string } { @@ -220,7 +219,7 @@ export function FileList(props: FileListProps): JSX.Element { onMove, onSelect, onSelectionChange = () => undefined, - renderItem = renderFileListItem, + renderItem = RenderFileListItem, rowHeight = DEFAULT_ROW_HEIGHT, overscanCount = ItemList.DEFAULT_OVERSCAN, } = props; diff --git a/packages/file-explorer/src/FileListContainer.tsx b/packages/file-explorer/src/FileListContainer.tsx index 8e51781119..fd99395e8e 100644 --- a/packages/file-explorer/src/FileListContainer.tsx +++ b/packages/file-explorer/src/FileListContainer.tsx @@ -2,10 +2,10 @@ import { ContextAction, ContextActions } from '@deephaven/components'; import { assertNotNull } from '@deephaven/utils'; import React, { useCallback, useMemo, useState } from 'react'; import FileList, { - renderFileListItem, - DEFAULT_ROW_HEIGHT, + RenderFileListItem, FileListRenderItemProps, } from './FileList'; +import { DEFAULT_ROW_HEIGHT } from './FileListUtils'; import { FileStorageItem, FileStorageTable, isDirectory } from './FileStorage'; import SHORTCUTS from './FileExplorerShortcuts'; import './FileExplorer.scss'; @@ -191,7 +191,7 @@ export function FileListContainer(props: FileListContainerProps): JSX.Element { (itemProps: FileListRenderItemProps): JSX.Element => { const { item } = itemProps; if (renameItem && renameItem.filename === item.filename) { - return renderFileListItem({ + return RenderFileListItem({ ...itemProps, children: ( { +const directionForKey = (key: string): SELECTION_DIRECTION | undefined => { switch (key) { case 'ArrowDown': return SELECTION_DIRECTION.DOWN; diff --git a/packages/iris-grid/src/GotoRow.tsx b/packages/iris-grid/src/GotoRow.tsx index 9ea5e63b4b..d26c9ed47e 100644 --- a/packages/iris-grid/src/GotoRow.tsx +++ b/packages/iris-grid/src/GotoRow.tsx @@ -22,7 +22,7 @@ import IrisGridProxyModel from './IrisGridProxyModel'; import IrisGridBottomBar from './IrisGridBottomBar'; import { ColumnName } from './CommonTypes'; -export function isIrisGridProxyModel( +function isIrisGridProxyModel( model: IrisGridModel ): model is IrisGridProxyModel { return (model as IrisGridProxyModel).model !== undefined; diff --git a/packages/iris-grid/src/sidebar/aggregations/Aggregations.tsx b/packages/iris-grid/src/sidebar/aggregations/Aggregations.tsx index 3917de0553..4eb40ad1e5 100644 --- a/packages/iris-grid/src/sidebar/aggregations/Aggregations.tsx +++ b/packages/iris-grid/src/sidebar/aggregations/Aggregations.tsx @@ -50,7 +50,7 @@ export type AggregationsProps = { onEdit: (aggregation: Aggregation) => void; }; -export const SELECTABLE_OPTIONS = [ +const SELECTABLE_OPTIONS = [ AggregationOperation.SUM, AggregationOperation.ABS_SUM, AggregationOperation.MIN, From e38ebbf768ad84f2cde7ed38bc2e4e19ae17cef5 Mon Sep 17 00:00:00 2001 From: Matthew Runyon Date: Wed, 15 Mar 2023 00:14:59 -0500 Subject: [PATCH 2/3] Fix tests --- .../components/src/DateTimeInput.test.tsx | 3 +- packages/components/src/DateTimeInput.tsx | 8 +--- packages/components/src/DateTimeInputUtils.ts | 7 +++ packages/components/src/MaskedInput.test.tsx | 3 +- packages/components/src/MaskedInput.tsx | 41 +++-------------- packages/components/src/MaskedInputUtils.ts | 35 +++++++++++++++ packages/components/src/index.ts | 1 + packages/file-explorer/src/FileList.test.tsx | 3 +- packages/file-explorer/src/FileList.tsx | 44 +++---------------- packages/file-explorer/src/FileListUtils.ts | 44 ++++++++++++++++++- packages/file-explorer/src/index.ts | 1 + .../sidebar/aggregations/AggregationUtils.ts | 16 +++++++ .../aggregations/Aggregations.test.tsx | 3 +- .../src/sidebar/aggregations/Aggregations.tsx | 18 +------- 14 files changed, 123 insertions(+), 104 deletions(-) create mode 100644 packages/components/src/DateTimeInputUtils.ts diff --git a/packages/components/src/DateTimeInput.test.tsx b/packages/components/src/DateTimeInput.test.tsx index 5e177ba3c7..53d296f57a 100644 --- a/packages/components/src/DateTimeInput.test.tsx +++ b/packages/components/src/DateTimeInput.test.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import DateTimeInput, { addSeparators } from './DateTimeInput'; +import DateTimeInput from './DateTimeInput'; +import { addSeparators } from './DateTimeInputUtils'; const DEFAULT_DATE_TIME = '2022-02-22 00:00:00.000000000'; // Zero width space diff --git a/packages/components/src/DateTimeInput.tsx b/packages/components/src/DateTimeInput.tsx index 3d70fcd44d..79164fbeb8 100644 --- a/packages/components/src/DateTimeInput.tsx +++ b/packages/components/src/DateTimeInput.tsx @@ -3,6 +3,7 @@ import classNames from 'classnames'; import Log from '@deephaven/log'; import MaskedInput, { SelectionSegment } from './MaskedInput'; import { getNextSegmentValue } from './DateInputUtils'; +import { addSeparators } from './DateTimeInputUtils'; const log = Log.module('DateTimeInput'); @@ -35,13 +36,6 @@ function fixIncompleteValue(value: string): string { return value; } -function addSeparators(value: string): string { - const dateTimeMillis = value.substring(0, 23); - const micros = value.substring(23, 26); - const nanos = value.substring(26); - return [dateTimeMillis, micros, nanos].filter(v => v !== '').join('\u200B'); -} - const removeSeparators = (value: string) => value.replace(/\u200B/g, ''); const EXAMPLES = [addSeparators(DEFAULT_VALUE_STRING)]; diff --git a/packages/components/src/DateTimeInputUtils.ts b/packages/components/src/DateTimeInputUtils.ts new file mode 100644 index 0000000000..dd8ec3d7b4 --- /dev/null +++ b/packages/components/src/DateTimeInputUtils.ts @@ -0,0 +1,7 @@ +/* eslint-disable import/prefer-default-export */ +export function addSeparators(value: string): string { + const dateTimeMillis = value.substring(0, 23); + const micros = value.substring(23, 26); + const nanos = value.substring(26); + return [dateTimeMillis, micros, nanos].filter(v => v !== '').join('\u200B'); +} diff --git a/packages/components/src/MaskedInput.test.tsx b/packages/components/src/MaskedInput.test.tsx index b0172d19cf..1ab2310477 100644 --- a/packages/components/src/MaskedInput.test.tsx +++ b/packages/components/src/MaskedInput.test.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { render } from '@testing-library/react'; -import MaskedInput, { fillToLength, trimTrailingMask } from './MaskedInput'; +import MaskedInput from './MaskedInput'; +import { fillToLength, trimTrailingMask } from './MaskedInputUtils'; const TIME_PATTERN = '([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]'; diff --git a/packages/components/src/MaskedInput.tsx b/packages/components/src/MaskedInput.tsx index 806085289f..23a76e6ef6 100644 --- a/packages/components/src/MaskedInput.tsx +++ b/packages/components/src/MaskedInput.tsx @@ -2,7 +2,11 @@ import React, { useMemo, useEffect, useCallback } from 'react'; import classNames from 'classnames'; import Log from '@deephaven/log'; import { useForwardedRef } from '@deephaven/react-hooks'; -import { DEFAULT_GET_PREFERRED_REPLACEMENT_STRING } from './MaskedInputUtils'; +import { + DEFAULT_GET_PREFERRED_REPLACEMENT_STRING, + fillToLength, + trimTrailingMask, +} from './MaskedInputUtils'; import './MaskedInput.scss'; const log = Log.module('MaskedInput'); @@ -19,41 +23,6 @@ const SELECTION_DIRECTION = { */ const FIXED_WIDTH_SPACE = '\u2007'; -/** - * Fill the string on the right side with the example value to the given length - * @param checkValue Initial string to pad - * @param exampleValue Example value - * @param length Target length - * @returns String padded with the given example value - */ -function fillToLength( - checkValue: string, - exampleValue: string, - length: number -): string { - return checkValue.length < length - ? `${checkValue}${exampleValue.substring(checkValue.length, length)}` - : checkValue; -} - -/** - * Trim all characters matching the empty mask on the right side of the given value - * @param value String to trim - * @param emptyMask Empty mask - * @returns Trimmed string - */ -function trimTrailingMask(value: string, emptyMask: string): string { - let { length } = value; - for (let i = value.length - 1; i >= 0; i -= 1) { - if (emptyMask[i] === value[i]) { - length = i; - } else { - break; - } - } - return value.substring(0, length); -} - export type SelectionSegment = { selectionStart: number; selectionEnd: number; diff --git a/packages/components/src/MaskedInputUtils.ts b/packages/components/src/MaskedInputUtils.ts index 1fa644d2ac..4a3b8a7276 100644 --- a/packages/components/src/MaskedInputUtils.ts +++ b/packages/components/src/MaskedInputUtils.ts @@ -10,3 +10,38 @@ export function DEFAULT_GET_PREFERRED_REPLACEMENT_STRING( value.substring(replaceIndex + 1) ); } + +/** + * Fill the string on the right side with the example value to the given length + * @param checkValue Initial string to pad + * @param exampleValue Example value + * @param length Target length + * @returns String padded with the given example value + */ +export function fillToLength( + checkValue: string, + exampleValue: string, + length: number +): string { + return checkValue.length < length + ? `${checkValue}${exampleValue.substring(checkValue.length, length)}` + : checkValue; +} + +/** + * Trim all characters matching the empty mask on the right side of the given value + * @param value String to trim + * @param emptyMask Empty mask + * @returns Trimmed string + */ +export function trimTrailingMask(value: string, emptyMask: string): string { + let { length } = value; + for (let i = value.length - 1; i >= 0; i -= 1) { + if (emptyMask[i] === value[i]) { + length = i; + } else { + break; + } + } + return value.substring(0, length); +} diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index b60f8dc2ae..2c6cb38813 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -29,6 +29,7 @@ export { default as DropdownMenu } from './menu-actions'; export * from './menu-actions'; export { default as MaskedInput } from './MaskedInput'; export * from './MaskedInput'; +export * from './MaskedInputUtils'; export * from './navigation'; export { default as Option } from './Option'; export * from './popper'; diff --git a/packages/file-explorer/src/FileList.test.tsx b/packages/file-explorer/src/FileList.test.tsx index 5b1f935db8..811c10e7a4 100644 --- a/packages/file-explorer/src/FileList.test.tsx +++ b/packages/file-explorer/src/FileList.test.tsx @@ -9,7 +9,7 @@ import { FileStorageItem, FileStorageTable, } from './FileStorage'; -import FileList, { FileListProps, getMoveOperation } from './FileList'; +import FileList, { FileListProps } from './FileList'; import { makeDirectories, makeDirectory, @@ -17,6 +17,7 @@ import { makeFiles, makeNested, } from './FileTestUtils'; +import { getMoveOperation } from './FileListUtils'; const renderFileList = ({ table = {} as FileStorageTable, diff --git a/packages/file-explorer/src/FileList.tsx b/packages/file-explorer/src/FileList.tsx index bda3d9bb6a..190afdf01f 100644 --- a/packages/file-explorer/src/FileList.tsx +++ b/packages/file-explorer/src/FileList.tsx @@ -20,7 +20,11 @@ import React, { import { FileStorageItem, FileStorageTable, isDirectory } from './FileStorage'; import './FileList.scss'; import FileUtils, { MIME_TYPE } from './FileUtils'; -import { DEFAULT_ROW_HEIGHT } from './FileListUtils'; +import { + DEFAULT_ROW_HEIGHT, + getMoveOperation, + getPathFromItem, +} from './FileListUtils'; const log = Log.module('FileList'); @@ -67,11 +71,6 @@ export interface FileListProps { overscanCount?: number; } -const getPathFromItem = (file: FileStorageItem): string => - isDirectory(file) - ? FileUtils.makePath(file.filename) - : FileUtils.getPath(file.filename); - // How long you need to hover over a directory before it expands const DRAG_HOVER_TIMEOUT = 500; @@ -175,39 +174,6 @@ function getItemIcon(item: FileStorageItem): IconDefinition { } } -/** - * Get the move operation for the current selection and the given target. Throws if the operation is invalid. - */ -function getMoveOperation( - draggedItems: FileStorageItem[], - targetItem: FileStorageItem -): { files: FileStorageItem[]; targetPath: string } { - if (draggedItems.length === 0 || targetItem == null) { - throw new Error('No items to move'); - } - - const targetPath = getPathFromItem(targetItem); - if ( - draggedItems.some( - ({ filename }) => FileUtils.getPath(filename) === targetPath - ) - ) { - // Cannot drop if target is one of the dragged items is already in the target folder - throw new Error('File already in the destination folder'); - } - if ( - draggedItems.some( - item => - isDirectory(item) && - targetPath.startsWith(FileUtils.makePath(item.filename)) - ) - ) { - // Cannot drop if target is a child of one of the directories being moved - throw new Error('Destination folder cannot be a child of a dragged folder'); - } - return { files: draggedItems, targetPath }; -} - /** * Component that displays and allows interaction with the file system in the provided FileStorageTable. */ diff --git a/packages/file-explorer/src/FileListUtils.ts b/packages/file-explorer/src/FileListUtils.ts index 6ceec9da8c..e13ca2ac4c 100644 --- a/packages/file-explorer/src/FileListUtils.ts +++ b/packages/file-explorer/src/FileListUtils.ts @@ -1,2 +1,44 @@ -// eslint-disable-next-line import/prefer-default-export +import { isDirectory } from './FileStorage'; +import type { FileStorageItem } from './FileStorage'; +import FileUtils from './FileUtils'; + export const DEFAULT_ROW_HEIGHT = 26; + +export function getPathFromItem(file: FileStorageItem): string { + return isDirectory(file) + ? FileUtils.makePath(file.filename) + : FileUtils.getPath(file.filename); +} + +/** + * Get the move operation for the current selection and the given target. Throws if the operation is invalid. + */ +export function getMoveOperation( + draggedItems: FileStorageItem[], + targetItem: FileStorageItem +): { files: FileStorageItem[]; targetPath: string } { + if (draggedItems.length === 0 || targetItem == null) { + throw new Error('No items to move'); + } + + const targetPath = getPathFromItem(targetItem); + if ( + draggedItems.some( + ({ filename }) => FileUtils.getPath(filename) === targetPath + ) + ) { + // Cannot drop if target is one of the dragged items is already in the target folder + throw new Error('File already in the destination folder'); + } + if ( + draggedItems.some( + item => + isDirectory(item) && + targetPath.startsWith(FileUtils.makePath(item.filename)) + ) + ) { + // Cannot drop if target is a child of one of the directories being moved + throw new Error('Destination folder cannot be a child of a dragged folder'); + } + return { files: draggedItems, targetPath }; +} diff --git a/packages/file-explorer/src/index.ts b/packages/file-explorer/src/index.ts index 60600a6c3e..3a414c6e18 100644 --- a/packages/file-explorer/src/index.ts +++ b/packages/file-explorer/src/index.ts @@ -3,6 +3,7 @@ import FileExplorer from './FileExplorer'; export * from './FileExplorer'; export * from './FileListContainer'; export * from './FileList'; +export * from './FileListUtils'; export * from './FileStorage'; export { default as FileExistsError } from './FileExistsError'; export { default as FileNotFoundError } from './FileNotFoundError'; diff --git a/packages/iris-grid/src/sidebar/aggregations/AggregationUtils.ts b/packages/iris-grid/src/sidebar/aggregations/AggregationUtils.ts index 85ebfeb42b..04a7819841 100644 --- a/packages/iris-grid/src/sidebar/aggregations/AggregationUtils.ts +++ b/packages/iris-grid/src/sidebar/aggregations/AggregationUtils.ts @@ -2,6 +2,22 @@ import type { Column } from '@deephaven/jsapi-shim'; import { TableUtils } from '@deephaven/jsapi-utils'; import AggregationOperation from './AggregationOperation'; +export const SELECTABLE_OPTIONS = [ + AggregationOperation.SUM, + AggregationOperation.ABS_SUM, + AggregationOperation.MIN, + AggregationOperation.MAX, + AggregationOperation.VAR, + AggregationOperation.AVG, + AggregationOperation.STD, + AggregationOperation.FIRST, + AggregationOperation.LAST, + AggregationOperation.COUNT_DISTINCT, + AggregationOperation.DISTINCT, + AggregationOperation.COUNT, + AggregationOperation.UNIQUE, +]; + /** * Check if an operation requires a rollup/table grouping * @param type The operation to check diff --git a/packages/iris-grid/src/sidebar/aggregations/Aggregations.test.tsx b/packages/iris-grid/src/sidebar/aggregations/Aggregations.test.tsx index 120d1cc2af..3d081ec3a6 100644 --- a/packages/iris-grid/src/sidebar/aggregations/Aggregations.test.tsx +++ b/packages/iris-grid/src/sidebar/aggregations/Aggregations.test.tsx @@ -1,8 +1,9 @@ import React from 'react'; import { render, RenderResult, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import Aggregations, { Aggregation, SELECTABLE_OPTIONS } from './Aggregations'; +import Aggregations, { Aggregation } from './Aggregations'; import AggregationOperation from './AggregationOperation'; +import { SELECTABLE_OPTIONS } from './AggregationUtils'; function makeAggregation({ operation = AggregationOperation.SUM, diff --git a/packages/iris-grid/src/sidebar/aggregations/Aggregations.tsx b/packages/iris-grid/src/sidebar/aggregations/Aggregations.tsx index 4eb40ad1e5..4d4d4622d5 100644 --- a/packages/iris-grid/src/sidebar/aggregations/Aggregations.tsx +++ b/packages/iris-grid/src/sidebar/aggregations/Aggregations.tsx @@ -27,7 +27,7 @@ import { import type { DraggableRenderItemProps, Range } from '@deephaven/components'; import { ModelIndex } from '@deephaven/grid'; import AggregationOperation from './AggregationOperation'; -import AggregationUtils from './AggregationUtils'; +import AggregationUtils, { SELECTABLE_OPTIONS } from './AggregationUtils'; import './Aggregations.scss'; const log = Log.module('Aggregations'); @@ -50,22 +50,6 @@ export type AggregationsProps = { onEdit: (aggregation: Aggregation) => void; }; -const SELECTABLE_OPTIONS = [ - AggregationOperation.SUM, - AggregationOperation.ABS_SUM, - AggregationOperation.MIN, - AggregationOperation.MAX, - AggregationOperation.VAR, - AggregationOperation.AVG, - AggregationOperation.STD, - AggregationOperation.FIRST, - AggregationOperation.LAST, - AggregationOperation.COUNT_DISTINCT, - AggregationOperation.DISTINCT, - AggregationOperation.COUNT, - AggregationOperation.UNIQUE, -]; - function Aggregations({ isRollup, settings, From e1fcc6b536318129612fface8d30d583ad49f3ec Mon Sep 17 00:00:00 2001 From: Matthew Runyon Date: Wed, 15 Mar 2023 13:20:26 -0500 Subject: [PATCH 3/3] Address review comments --- packages/components/src/DateTimeInput.tsx | 4 +- packages/file-explorer/src/FileList.tsx | 131 +----------------- .../file-explorer/src/FileListContainer.tsx | 19 ++- packages/file-explorer/src/FileListItem.tsx | 121 ++++++++++++++++ packages/file-explorer/src/index.ts | 1 + 5 files changed, 138 insertions(+), 138 deletions(-) create mode 100644 packages/file-explorer/src/FileListItem.tsx diff --git a/packages/components/src/DateTimeInput.tsx b/packages/components/src/DateTimeInput.tsx index 79164fbeb8..3a168950f2 100644 --- a/packages/components/src/DateTimeInput.tsx +++ b/packages/components/src/DateTimeInput.tsx @@ -36,7 +36,9 @@ function fixIncompleteValue(value: string): string { return value; } -const removeSeparators = (value: string) => value.replace(/\u200B/g, ''); +function removeSeparators(value: string): string { + return value.replace(/\u200B/g, ''); +} const EXAMPLES = [addSeparators(DEFAULT_VALUE_STRING)]; diff --git a/packages/file-explorer/src/FileList.tsx b/packages/file-explorer/src/FileList.tsx index 190afdf01f..15d8796642 100644 --- a/packages/file-explorer/src/FileList.tsx +++ b/packages/file-explorer/src/FileList.tsx @@ -1,14 +1,6 @@ -import { - ItemList, - Range, - RenderItemProps, - Tooltip, -} from '@deephaven/components'; -import { dhPython, vsCode, vsFolder, vsFolderOpened } from '@deephaven/icons'; +import { ItemList, Range } from '@deephaven/components'; import Log from '@deephaven/log'; import { RangeUtils } from '@deephaven/utils'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { IconDefinition } from '@fortawesome/fontawesome-svg-core'; import classNames from 'classnames'; import React, { useCallback, @@ -19,12 +11,8 @@ import React, { } from 'react'; import { FileStorageItem, FileStorageTable, isDirectory } from './FileStorage'; import './FileList.scss'; -import FileUtils, { MIME_TYPE } from './FileUtils'; -import { - DEFAULT_ROW_HEIGHT, - getMoveOperation, - getPathFromItem, -} from './FileListUtils'; +import { DEFAULT_ROW_HEIGHT, getMoveOperation } from './FileListUtils'; +import { FileListItem, FileListRenderItemProps } from './FileListItem'; const log = Log.module('FileList'); @@ -39,19 +27,6 @@ export type ListViewport = { bottom: number; }; -export type FileListRenderItemProps = RenderItemProps & { - children?: JSX.Element; - dropTargetItem?: FileStorageItem; - draggedItems?: FileStorageItem[]; - isDragInProgress: boolean; - isDropTargetValid: boolean; - - onDragStart(index: number, e: React.DragEvent): void; - onDragOver(index: number, e: React.DragEvent): void; - onDragEnd(index: number, e: React.DragEvent): void; - onDrop(index: number, e: React.DragEvent): void; -}; - export interface FileListProps { table: FileStorageTable; @@ -76,104 +51,6 @@ const DRAG_HOVER_TIMEOUT = 500; const ITEM_LIST_CLASS_NAME = 'item-list-scroll-pane'; -export function RenderFileListItem( - props: FileListRenderItemProps -): JSX.Element { - const { - children, - draggedItems, - isDragInProgress, - isDropTargetValid, - isSelected, - item, - itemIndex, - dropTargetItem, - onDragStart, - onDragOver, - onDragEnd, - onDrop, - } = props; - - const isDragged = - draggedItems?.some(draggedItem => draggedItem.id === item.id) ?? false; - const itemPath = getPathFromItem(item); - const dropTargetPath = - isDragInProgress && dropTargetItem ? getPathFromItem(dropTargetItem) : null; - - const isExactDropTarget = - isDragInProgress && - isDropTargetValid && - isDirectory(item) && - dropTargetPath === itemPath; - const isInDropTarget = - isDragInProgress && isDropTargetValid && dropTargetPath === itemPath; - const isInvalidDropTarget = - isDragInProgress && !isDropTargetValid && dropTargetPath === itemPath; - - const icon = getItemIcon(item); - const depth = FileUtils.getDepth(item.filename); - const depthLines = Array(depth) - .fill(null) - .map((value, index) => ( - // eslint-disable-next-line react/no-array-index-key - - )); - - return ( -
onDragStart(itemIndex, e)} - onDragOver={e => onDragOver(itemIndex, e)} - onDragEnd={e => onDragEnd(itemIndex, e)} - onDrop={e => onDrop(itemIndex, e)} - draggable - role="presentation" - aria-label={item.basename} - > - {depthLines}{' '} - {' '} - - {children ?? item.basename} - - {children ?? item.basename} - - -
- ); -} - -/** - * Get the icon definition for a file or folder item - * @param item Item to get the icon for - * @returns Icon definition to pass in the FontAwesomeIcon icon prop - */ -function getItemIcon(item: FileStorageItem): IconDefinition { - if (isDirectory(item)) { - return item.isExpanded ? vsFolderOpened : vsFolder; - } - const mimeType = FileUtils.getMimeType(item.basename); - switch (mimeType) { - case MIME_TYPE.PYTHON: - return dhPython; - default: - return vsCode; - } -} - /** * Component that displays and allows interaction with the file system in the provided FileStorageTable. */ @@ -185,7 +62,7 @@ export function FileList(props: FileListProps): JSX.Element { onMove, onSelect, onSelectionChange = () => undefined, - renderItem = RenderFileListItem, + renderItem = FileListItem, rowHeight = DEFAULT_ROW_HEIGHT, overscanCount = ItemList.DEFAULT_OVERSCAN, } = props; diff --git a/packages/file-explorer/src/FileListContainer.tsx b/packages/file-explorer/src/FileListContainer.tsx index fd99395e8e..6a68d7b346 100644 --- a/packages/file-explorer/src/FileListContainer.tsx +++ b/packages/file-explorer/src/FileListContainer.tsx @@ -1,10 +1,8 @@ import { ContextAction, ContextActions } from '@deephaven/components'; import { assertNotNull } from '@deephaven/utils'; import React, { useCallback, useMemo, useState } from 'react'; -import FileList, { - RenderFileListItem, - FileListRenderItemProps, -} from './FileList'; +import FileList from './FileList'; +import { FileListItem, FileListRenderItemProps } from './FileListItem'; import { DEFAULT_ROW_HEIGHT } from './FileListUtils'; import { FileStorageItem, FileStorageTable, isDirectory } from './FileStorage'; import SHORTCUTS from './FileExplorerShortcuts'; @@ -191,19 +189,20 @@ export function FileListContainer(props: FileListContainerProps): JSX.Element { (itemProps: FileListRenderItemProps): JSX.Element => { const { item } = itemProps; if (renameItem && renameItem.filename === item.filename) { - return RenderFileListItem({ - ...itemProps, - children: ( + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + - ), - }); + + ); } - return RenderFileListItem(itemProps); + // eslint-disable-next-line react/jsx-props-no-spreading + return ; }, [handleRenameCancel, handleRenameSubmit, renameItem, validateRenameItem] ); diff --git a/packages/file-explorer/src/FileListItem.tsx b/packages/file-explorer/src/FileListItem.tsx new file mode 100644 index 0000000000..6cba083a24 --- /dev/null +++ b/packages/file-explorer/src/FileListItem.tsx @@ -0,0 +1,121 @@ +import React from 'react'; +import { Tooltip, RenderItemProps } from '@deephaven/components'; +import { dhPython, vsCode, vsFolder, vsFolderOpened } from '@deephaven/icons'; +import { IconDefinition } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import classNames from 'classnames'; +import { FileStorageItem, isDirectory } from './FileStorage'; +import './FileList.scss'; +import FileUtils, { MIME_TYPE } from './FileUtils'; +import { getPathFromItem } from './FileListUtils'; + +/** + * Get the icon definition for a file or folder item + * @param item Item to get the icon for + * @returns Icon definition to pass in the FontAwesomeIcon icon prop + */ +function getItemIcon(item: FileStorageItem): IconDefinition { + if (isDirectory(item)) { + return item.isExpanded ? vsFolderOpened : vsFolder; + } + const mimeType = FileUtils.getMimeType(item.basename); + switch (mimeType) { + case MIME_TYPE.PYTHON: + return dhPython; + default: + return vsCode; + } +} + +export type FileListRenderItemProps = RenderItemProps & { + children?: JSX.Element; + dropTargetItem?: FileStorageItem; + draggedItems?: FileStorageItem[]; + isDragInProgress: boolean; + isDropTargetValid: boolean; + + onDragStart(index: number, e: React.DragEvent): void; + onDragOver(index: number, e: React.DragEvent): void; + onDragEnd(index: number, e: React.DragEvent): void; + onDrop(index: number, e: React.DragEvent): void; +}; + +export function FileListItem(props: FileListRenderItemProps): JSX.Element { + const { + children, + draggedItems, + isDragInProgress, + isDropTargetValid, + isSelected, + item, + itemIndex, + dropTargetItem, + onDragStart, + onDragOver, + onDragEnd, + onDrop, + } = props; + + const isDragged = + draggedItems?.some(draggedItem => draggedItem.id === item.id) ?? false; + const itemPath = getPathFromItem(item); + const dropTargetPath = + isDragInProgress && dropTargetItem ? getPathFromItem(dropTargetItem) : null; + + const isExactDropTarget = + isDragInProgress && + isDropTargetValid && + isDirectory(item) && + dropTargetPath === itemPath; + const isInDropTarget = + isDragInProgress && isDropTargetValid && dropTargetPath === itemPath; + const isInvalidDropTarget = + isDragInProgress && !isDropTargetValid && dropTargetPath === itemPath; + + const icon = getItemIcon(item); + const depth = FileUtils.getDepth(item.filename); + const depthLines = Array(depth) + .fill(null) + .map((value, index) => ( + // eslint-disable-next-line react/no-array-index-key + + )); + + return ( +
onDragStart(itemIndex, e)} + onDragOver={e => onDragOver(itemIndex, e)} + onDragEnd={e => onDragEnd(itemIndex, e)} + onDrop={e => onDrop(itemIndex, e)} + draggable + role="presentation" + aria-label={item.basename} + > + {depthLines}{' '} + {' '} + + {children ?? item.basename} + + {children ?? item.basename} + + +
+ ); +} + +export default FileListItem; diff --git a/packages/file-explorer/src/index.ts b/packages/file-explorer/src/index.ts index 3a414c6e18..270532e952 100644 --- a/packages/file-explorer/src/index.ts +++ b/packages/file-explorer/src/index.ts @@ -3,6 +3,7 @@ import FileExplorer from './FileExplorer'; export * from './FileExplorer'; export * from './FileListContainer'; export * from './FileList'; +export * from './FileListItem'; export * from './FileListUtils'; export * from './FileStorage'; export { default as FileExistsError } from './FileExistsError';