From 57e639f63c0c8e0e18c37044d5bbb2063a6e4c19 Mon Sep 17 00:00:00 2001 From: mikebender Date: Tue, 14 May 2024 13:54:03 -0400 Subject: [PATCH 1/6] feat: Refactor console objects menu - Split ConsoleMenu up so objects is it's own component ConsoleObjectsMenu - Convert to functional component - Display all widgets in the menu - Add a flag to hide the menu, which we set for DHC, but will not be set in DHE so current behaviour is not broken there --- packages/console/src/Console.tsx | 21 +- packages/console/src/ConsoleMenu.tsx | 271 ------------------ packages/console/src/ConsoleObjectsMenu.tsx | 89 ++++++ .../console/src/ConsoleStatusBar.test.tsx | 29 +- packages/console/src/ConsoleStatusBar.tsx | 206 ++++++------- packages/console/src/index.ts | 1 - .../src/panels/ConsolePanel.tsx | 1 + 7 files changed, 211 insertions(+), 407 deletions(-) delete mode 100644 packages/console/src/ConsoleMenu.tsx create mode 100644 packages/console/src/ConsoleObjectsMenu.tsx diff --git a/packages/console/src/Console.tsx b/packages/console/src/Console.tsx index 02bf7792b8..9a721907e0 100644 --- a/packages/console/src/Console.tsx +++ b/packages/console/src/Console.tsx @@ -34,6 +34,7 @@ import { CommandHistoryStorage, CommandHistoryStorageItem, } from './command-history'; +import ConsoleObjectsMenu from './ConsoleObjectsMenu'; const log = Log.module('Console'); @@ -51,7 +52,13 @@ const DEFAULT_SETTINGS: Settings = { interface ConsoleProps { dh: typeof DhType; + + /** Additional children to show in the objects menu */ statusBarChildren: ReactNode; + + /** Hide the objects menu in the status bar. Defaults to false. */ + hideObjectsMenu?: boolean; + settings: Partial; focusCommandHistory: () => void; @@ -905,9 +912,8 @@ export class Console extends PureComponent { } getObjects = memoize( - (objectMap: Map) => [ - ...objectMap.values(), - ] + (objectMap: Map) => + Array.from(objectMap.values()) ); getContextActions = memoize( @@ -990,6 +996,7 @@ export class Console extends PureComponent { unzip, supportsType, iconForType, + hideObjectsMenu, } = this.props; const { consoleHeight, @@ -1016,10 +1023,14 @@ export class Console extends PureComponent { dh={dh} session={session} overflowActions={this.handleOverflowActions} - openObject={openObject} - objects={consoleMenuObjects} > {statusBarChildren} + {hideObjectsMenu !== true && ( + + )}
void; - objects: DhType.ide.VariableDefinition[]; - overflowActions: () => DropdownAction[]; -} - -interface ConsoleMenuState { - tableFilterText: string; - widgetFilterText: string; -} - -class ConsoleMenu extends PureComponent { - static makeItemActions( - objects: DhType.ide.VariableDefinition[], - filterText: string, - refCallback: (ref: SearchInput) => void, - changeCallback: ChangeEventHandler, - openCallback: (object: DhType.ide.VariableDefinition) => void - ): DropdownAction[] { - if (objects.length === 0) { - return []; - } - - const searchAction = { - menuElement: ( - - ), - }; - let filteredItems = objects; - if (filterText) { - filteredItems = filteredItems.filter( - ({ title }: { title?: string }) => - title != null && - title.toLowerCase().indexOf(filterText.toLowerCase()) >= 0 - ); - } - const openActions = filteredItems.map(object => ({ - title: object.title, - action: () => { - openCallback(object); - }, - })); - - return [searchAction, ...openActions]; - } - - constructor(props: ConsoleMenuProps) { - super(props); - - this.handleTableFilterChange = this.handleTableFilterChange.bind(this); - this.handleTableMenuClosed = this.handleTableMenuClosed.bind(this); - this.handleTableMenuOpened = this.handleTableMenuOpened.bind(this); - this.handleWidgetFilterChange = this.handleWidgetFilterChange.bind(this); - this.handleWidgetMenuClosed = this.handleWidgetMenuClosed.bind(this); - this.handleWidgetMenuOpened = this.handleWidgetMenuOpened.bind(this); - - this.state = { - tableFilterText: '', - widgetFilterText: '', - }; - } - - tableSearchField?: SearchInput; - - widgetSearchField?: SearchInput; - - makeTableActions = memoize( - ( - objects: DhType.ide.VariableDefinition[], - filterText: string, - openObject: (object: DhType.ide.VariableDefinition) => void - ): DropdownAction[] => { - const { dh } = this.props; - const tables = objects.filter(object => - ConsoleUtils.isTableType(dh, object.type) - ); - return ConsoleMenu.makeItemActions( - tables, - filterText, - searchField => { - this.tableSearchField = searchField; - }, - this.handleTableFilterChange, - openObject - ); - } - ); - - makeWidgetActions = memoize( - ( - objects: DhType.ide.VariableDefinition[], - filterText: string, - openObject: (object: DhType.ide.VariableDefinition) => void - ): DropdownAction[] => { - const { dh } = this.props; - const widgets = objects.filter(object => - ConsoleUtils.isWidgetType(dh, 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(): void { - this.setState({ tableFilterText: '' }); - } - - handleTableMenuOpened(): void { - this.tableSearchField?.focus(); - } - - handleWidgetFilterChange(e: ChangeEvent): void { - log.debug('filtering widgets...'); - this.setState({ widgetFilterText: e.target.value }); - } - - handleWidgetMenuClosed(): void { - this.setState({ widgetFilterText: '' }); - } - - handleWidgetMenuOpened(): void { - this.widgetSearchField?.focus(); - } - - render(): ReactElement { - const { overflowActions, objects, openObject } = this.props; - const { tableFilterText, widgetFilterText } = this.state; - const tableActions = this.makeTableActions( - objects, - tableFilterText, - openObject - ); - const widgetActions = this.makeWidgetActions( - objects, - widgetFilterText, - openObject - ); - const popperOptions: PopperOptions = { placement: 'bottom-end' }; - - const disableTableActions = tableActions.length === 0; - const disableWidgetActions = widgetActions.length === 0; - - return ( - <> - - - - - ); - } -} - -export default ConsoleMenu; diff --git a/packages/console/src/ConsoleObjectsMenu.tsx b/packages/console/src/ConsoleObjectsMenu.tsx new file mode 100644 index 0000000000..52e7b82cb9 --- /dev/null +++ b/packages/console/src/ConsoleObjectsMenu.tsx @@ -0,0 +1,89 @@ +import React, { ReactElement, useMemo, useRef, useState } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { + Button, + DropdownActions, + DropdownMenu, + SearchInput, +} from '@deephaven/components'; +import { vsGraph, vsTriangleDown } from '@deephaven/icons'; +import type { dh as DhType } from '@deephaven/jsapi-types'; +import { ObjectIcon } from './common'; + +interface ConsoleObjectsMenuProps { + openObject: (object: DhType.ide.VariableDefinition) => void; + objects: readonly DhType.ide.VariableDefinition[]; +} + +function ConsoleObjectsMenu({ + openObject, + objects, +}: ConsoleObjectsMenuProps): ReactElement { + const [filterText, setFilterText] = useState(''); + const searchRef = useRef(null); + + const actions: DropdownActions = useMemo(() => { + if (objects.length === 0) { + return []; + } + const searchActions = { + menuElement: ( + { + setFilterText(e.target.value); + }} + className="console-menu" + ref={searchRef} + /> + ), + }; + const filteredObjects = filterText + ? objects.filter( + ({ title }: { title?: string }) => + title != null && + title.toLowerCase().indexOf(filterText.toLowerCase()) >= 0 + ) + : objects; + const objectActions = filteredObjects.map(object => ({ + title: object.title, + action: () => { + openObject(object); + }, + icon: , + })); + return [searchActions, ...objectActions]; + }, [objects, filterText, openObject]); + + return ( + + ); +} + +export default ConsoleObjectsMenu; diff --git a/packages/console/src/ConsoleStatusBar.test.tsx b/packages/console/src/ConsoleStatusBar.test.tsx index a42ad8454c..8d668c3a7d 100644 --- a/packages/console/src/ConsoleStatusBar.test.tsx +++ b/packages/console/src/ConsoleStatusBar.test.tsx @@ -1,43 +1,44 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import dh from '@deephaven/jsapi-shim'; -import { ContextActions, DropdownAction } from '@deephaven/components'; +import { ContextActions, DropdownActions } from '@deephaven/components'; import { vsCheck } from '@deephaven/icons'; +import { TestUtils } from '@deephaven/utils'; import userEvent from '@testing-library/user-event'; import ConsoleStatusBar from './ConsoleStatusBar'; jest.useFakeTimers(); -function makeConsoleStatusBarWrapper( - overflowActions: () => DropdownAction[] = () => [] -) { +function makeConsoleStatusBarWrapper(actions: DropdownActions = []) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const session = new (dh as any).IdeSession('test'); const wrapper = render( - undefined} - objects={[]} - overflowActions={overflowActions} - /> + ); return wrapper; } beforeEach(() => { - jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => cb()); + jest.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => { + cb(0); + return 1; + }); }); afterEach(() => { - window.requestAnimationFrame.mockRestore(); + TestUtils.asMock(window.requestAnimationFrame).mockRestore(); }); it('renders without crashing', () => { makeConsoleStatusBarWrapper(); }); +it('does not show a dropdown menu when there are no actions', async () => { + makeConsoleStatusBarWrapper(); + expect(screen.queryByRole('button', { name: 'More Actions...' })).toBeNull(); +}); + it('dropdown menu disappears on toggle', async () => { const user = userEvent.setup({ delay: null }); const mockClick = jest.fn(); @@ -51,7 +52,7 @@ it('dropdown menu disappears on toggle', async () => { order: 10, }, ]); - const button = screen.getByLabelText('More Actions...'); + const button = screen.getByRole('button', { name: 'More Actions...' }); await user.click(button); let dropdown: HTMLElement | null = screen.getByText(title); expect(dropdown).toBeTruthy(); diff --git a/packages/console/src/ConsoleStatusBar.tsx b/packages/console/src/ConsoleStatusBar.tsx index 56f6a6ed94..715e7744f2 100644 --- a/packages/console/src/ConsoleStatusBar.tsx +++ b/packages/console/src/ConsoleStatusBar.tsx @@ -1,136 +1,110 @@ -import React, { PureComponent, ReactElement, ReactNode } from 'react'; +import React, { + ReactElement, + ReactNode, + useCallback, + useEffect, + useState, +} from 'react'; import classNames from 'classnames'; +import { vsKebabVertical } from '@deephaven/icons'; import type { dh as DhType } from '@deephaven/jsapi-types'; -import { DropdownAction, Tooltip } from '@deephaven/components'; -import { CanceledPromiseError, Pending } from '@deephaven/utils'; -import ConsoleMenu from './ConsoleMenu'; +import { + Button, + DropdownActions, + DropdownMenu, + PopperOptions, + Tooltip, +} from '@deephaven/components'; import './ConsoleStatusBar.scss'; +const POPPER_OPTIONS: PopperOptions = { placement: 'bottom-end' }; + interface ConsoleStatusBarProps { - children: ReactNode; + children?: ReactNode; dh: typeof DhType; session: DhType.IdeSession; - openObject: (object: DhType.ide.VariableDefinition) => void; - objects: DhType.ide.VariableDefinition[]; - overflowActions: () => DropdownAction[]; -} - -interface ConsoleStatusBarState { - isDisconnected: boolean; - isCommandRunning: boolean; + overflowActions?: DropdownActions; } -export class ConsoleStatusBar extends PureComponent< - ConsoleStatusBarProps, - ConsoleStatusBarState -> { - static defaultProps = { - children: null, - }; - - constructor(props: ConsoleStatusBarProps) { - super(props); - - this.handleCommandStarted = this.handleCommandStarted.bind(this); - this.handleCommandCompleted = this.handleCommandCompleted.bind(this); - - this.pending = new Pending(); - - this.state = { - isDisconnected: false, - isCommandRunning: false, - }; - } - - componentDidMount(): void { - this.startListening(); - } +function ConsoleStatusBar({ + children, + dh, + session, + overflowActions, +}: ConsoleStatusBarProps): ReactElement { + const [isCommandRunning, setIsCommandRunning] = useState(false); - componentWillUnmount(): void { - this.stopListening(); - this.cancelPendingPromises(); - } - - pending: Pending; - - startListening(): void { - const { dh, session } = this.props; - session.addEventListener( - dh.IdeSession.EVENT_COMMANDSTARTED, - this.handleCommandStarted - ); - } + const handleCommandStarted = useCallback(async (event: CustomEvent) => { + setIsCommandRunning(true); - stopListening(): void { - const { dh, session } = this.props; - session.removeEventListener( - dh.IdeSession.EVENT_COMMANDSTARTED, - this.handleCommandStarted - ); - } - - cancelPendingPromises(): void { - this.pending.cancel(); - } - - handleCommandStarted(event: CustomEvent): void { const { result } = event.detail; - - this.pending - .add(result) - .then(() => this.handleCommandCompleted(null)) - .catch(error => this.handleCommandCompleted(error)); - - this.setState({ - isCommandRunning: true, - }); - } - - handleCommandCompleted(error: unknown): void { - // Don't update state if the promise was canceled - if (!(error instanceof CanceledPromiseError)) { - this.setState({ - isCommandRunning: this.pending.pending.length > 0, - }); + try { + await result; + } catch (error) { + // No-op, fall through } - } - - render(): ReactElement { - const { children, dh, openObject, overflowActions, objects } = this.props; - const { isDisconnected, isCommandRunning } = this.state; - let statusIconClass = null; - let tooltipText = null; + setIsCommandRunning(false); + }, []); + + useEffect( + function startListening() { + session.addEventListener( + dh.IdeSession.EVENT_COMMANDSTARTED, + handleCommandStarted + ); + + return function stopListening() { + session.removeEventListener( + dh.IdeSession.EVENT_COMMANDSTARTED, + handleCommandStarted + ); + }; + }, + [dh, handleCommandStarted, session] + ); + + let statusIconClass = null; + let tooltipText = null; + if (isCommandRunning) { + // Connected, Pending + statusIconClass = 'console-status-icon-pending'; + tooltipText = 'Worker is busy'; + } else { + // Connected, Idle + statusIconClass = 'console-status-icon-idle'; + tooltipText = 'Worker is idle'; + } - if (isDisconnected) { - statusIconClass = 'console-status-icon-disconnected'; - tooltipText = 'Worker is disconnected'; - } else if (isCommandRunning) { - // Connected, Pending - statusIconClass = 'console-status-icon-pending'; - tooltipText = 'Worker is busy'; - } else { - // Connected, Idle - statusIconClass = 'console-status-icon-idle'; - tooltipText = 'Worker is idle'; - } + const hasActions = + overflowActions != null && + (!Array.isArray(overflowActions) || overflowActions.length > 0); - return ( -
-
-
- {tooltipText} -
- {children} - + return ( +
+
+
+ {tooltipText}
- ); - } + {children} + {hasActions && ( + + )} +
+ ); } export default ConsoleStatusBar; diff --git a/packages/console/src/index.ts b/packages/console/src/index.ts index e7d9db329a..b059a4449d 100644 --- a/packages/console/src/index.ts +++ b/packages/console/src/index.ts @@ -3,7 +3,6 @@ import Console from './Console'; export default Console; export { Console }; export { default as ConsoleInput } from './ConsoleInput'; -export { default as ConsoleMenu } from './ConsoleMenu'; export { default as SHORTCUTS } from './ConsoleShortcuts'; export { default as ConsoleStatusBar } from './ConsoleStatusBar'; export * from './monaco/MonacoThemeProvider'; diff --git a/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx b/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx index f923935757..20219de5d0 100644 --- a/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx @@ -440,6 +440,7 @@ export class ConsolePanel extends PureComponent< /> } + hideObjectsMenu scope={sessionId} timeZone={timeZone} objectMap={objectMap} From 878a9ed9a585b644970716321eafd4c4cb1855c8 Mon Sep 17 00:00:00 2001 From: Mike Bender Date: Wed, 15 May 2024 11:03:32 -0400 Subject: [PATCH 2/6] Update packages/console/src/ConsoleObjectsMenu.tsx Co-authored-by: Matthew Runyon --- packages/console/src/ConsoleObjectsMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/console/src/ConsoleObjectsMenu.tsx b/packages/console/src/ConsoleObjectsMenu.tsx index 52e7b82cb9..828b70808c 100644 --- a/packages/console/src/ConsoleObjectsMenu.tsx +++ b/packages/console/src/ConsoleObjectsMenu.tsx @@ -42,7 +42,7 @@ function ConsoleObjectsMenu({ ? objects.filter( ({ title }: { title?: string }) => title != null && - title.toLowerCase().indexOf(filterText.toLowerCase()) >= 0 + title.toLowerCase().includes(filterText.toLowerCase()) ) : objects; const objectActions = filteredObjects.map(object => ({ From fd0b2361603bfe516a62306911ececa731143702 Mon Sep 17 00:00:00 2001 From: mikebender Date: Tue, 6 Aug 2024 15:36:14 -0400 Subject: [PATCH 3/6] Clean up based on review --- packages/console/src/Console.tsx | 9 +++++---- packages/console/src/ConsoleObjectsMenu.tsx | 11 ++++++----- packages/console/src/ConsoleStatusBar.tsx | 15 +++++++-------- .../src/panels/ConsolePanel.tsx | 2 +- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/console/src/Console.tsx b/packages/console/src/Console.tsx index 9a721907e0..b708e3ee6e 100644 --- a/packages/console/src/Console.tsx +++ b/packages/console/src/Console.tsx @@ -56,8 +56,8 @@ interface ConsoleProps { /** Additional children to show in the objects menu */ statusBarChildren: ReactNode; - /** Hide the objects menu in the status bar. Defaults to false. */ - hideObjectsMenu?: boolean; + /** Show the objects menu in the status bar. Defaults to true. */ + showObjectsMenu?: boolean; settings: Partial; focusCommandHistory: () => void; @@ -148,6 +148,7 @@ export class Console extends PureComponent { unzip: null, supportsType: defaultSupportsType, iconForType: defaultIconForType, + showObjectsMenu: true, }; static LOG_THROTTLE = 500; @@ -996,7 +997,7 @@ export class Console extends PureComponent { unzip, supportsType, iconForType, - hideObjectsMenu, + showObjectsMenu, } = this.props; const { consoleHeight, @@ -1025,7 +1026,7 @@ export class Console extends PureComponent { overflowActions={this.handleOverflowActions} > {statusBarChildren} - {hideObjectsMenu !== true && ( + {showObjectsMenu === true && ( + ({ title }) => title != null && title.toLowerCase().includes(filterText.toLowerCase()) ) @@ -60,9 +61,7 @@ function ConsoleObjectsMenu({ aria-label="Objects" kind="ghost" disabled={objects.length === 0} - onClick={() => { - // no-op: click is handled in `DropdownMenu` - }} + onClick={EMPTY_FUNCTION} tooltip={objects.length === 0 ? 'No objects available' : 'Objects'} icon={
@@ -78,7 +77,9 @@ function ConsoleObjectsMenu({ searchRef.current?.focus()} - onMenuClosed={() => setFilterText('')} + onMenuClosed={() => { + searchRef.current?.focus(); + }} options={{ initialKeyboardIndex: 1 }} popperOptions={{ placement: 'bottom-end' }} /> diff --git a/packages/console/src/ConsoleStatusBar.tsx b/packages/console/src/ConsoleStatusBar.tsx index 715e7744f2..65582faaab 100644 --- a/packages/console/src/ConsoleStatusBar.tsx +++ b/packages/console/src/ConsoleStatusBar.tsx @@ -16,6 +16,7 @@ import { Tooltip, } from '@deephaven/components'; import './ConsoleStatusBar.scss'; +import { EMPTY_FUNCTION } from '@deephaven/utils'; const POPPER_OPTIONS: PopperOptions = { placement: 'bottom-end' }; @@ -32,19 +33,19 @@ function ConsoleStatusBar({ session, overflowActions, }: ConsoleStatusBarProps): ReactElement { - const [isCommandRunning, setIsCommandRunning] = useState(false); + const [pendingCommandCount, setPendingCommandCount] = useState(0); const handleCommandStarted = useCallback(async (event: CustomEvent) => { - setIsCommandRunning(true); + setPendingCommandCount(count => count + 1); - const { result } = event.detail; try { + const { result } = event.detail; await result; } catch (error) { // No-op, fall through } - setIsCommandRunning(false); + setPendingCommandCount(count => count - 1); }, []); useEffect( @@ -66,7 +67,7 @@ function ConsoleStatusBar({ let statusIconClass = null; let tooltipText = null; - if (isCommandRunning) { + if (pendingCommandCount > 0) { // Connected, Pending statusIconClass = 'console-status-icon-pending'; tooltipText = 'Worker is busy'; @@ -93,9 +94,7 @@ function ConsoleStatusBar({ icon={vsKebabVertical} tooltip="More Actions..." aria-label="More Actions..." - onClick={() => { - // no-op: click is handled in `DropdownMenu` - }} + onClick={EMPTY_FUNCTION} > } - hideObjectsMenu + showObjectsMenu={false} scope={sessionId} timeZone={timeZone} objectMap={objectMap} From b412ba1483c543eed5af286cf1fda513f77729fc Mon Sep 17 00:00:00 2001 From: Mike Bender Date: Tue, 13 Aug 2024 08:52:04 -0400 Subject: [PATCH 4/6] Update packages/console/src/Console.tsx Co-authored-by: Matthew Runyon --- packages/console/src/Console.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/console/src/Console.tsx b/packages/console/src/Console.tsx index b708e3ee6e..d78e5386b6 100644 --- a/packages/console/src/Console.tsx +++ b/packages/console/src/Console.tsx @@ -53,7 +53,7 @@ const DEFAULT_SETTINGS: Settings = { interface ConsoleProps { dh: typeof DhType; - /** Additional children to show in the objects menu */ + /** Additional children to show in the status bar*/ statusBarChildren: ReactNode; /** Show the objects menu in the status bar. Defaults to true. */ From 31e0aea9038f8ee77a6db04753962ecd818c8b60 Mon Sep 17 00:00:00 2001 From: mikebender Date: Tue, 13 Aug 2024 09:34:53 -0400 Subject: [PATCH 5/6] Clean up based on review - Add some comments - Reset text in search field after a delay - Select the text on menu open so you can just start typing --- packages/components/src/SearchInput.tsx | 4 +++ packages/console/src/Console.tsx | 2 +- packages/console/src/ConsoleObjectsMenu.tsx | 31 ++++++++++++++++--- packages/console/src/ConsoleStatusBar.tsx | 10 ++---- .../src/panels/ConsolePanel.tsx | 2 +- 5 files changed, 34 insertions(+), 15 deletions(-) diff --git a/packages/components/src/SearchInput.tsx b/packages/components/src/SearchInput.tsx index 1d5c0104c5..feef3e7b7c 100644 --- a/packages/components/src/SearchInput.tsx +++ b/packages/components/src/SearchInput.tsx @@ -55,6 +55,10 @@ class SearchInput extends PureComponent { this.inputField.current?.focus(); } + select(): void { + this.inputField.current?.select(); + } + inputField: React.RefObject; searchChangeSelection: React.RefObject; diff --git a/packages/console/src/Console.tsx b/packages/console/src/Console.tsx index d78e5386b6..9ccb4ebe5f 100644 --- a/packages/console/src/Console.tsx +++ b/packages/console/src/Console.tsx @@ -53,7 +53,7 @@ const DEFAULT_SETTINGS: Settings = { interface ConsoleProps { dh: typeof DhType; - /** Additional children to show in the status bar*/ + /** Additional children to show in the status bar */ statusBarChildren: ReactNode; /** Show the objects menu in the status bar. Defaults to true. */ diff --git a/packages/console/src/ConsoleObjectsMenu.tsx b/packages/console/src/ConsoleObjectsMenu.tsx index a62b0d030b..fa5a124396 100644 --- a/packages/console/src/ConsoleObjectsMenu.tsx +++ b/packages/console/src/ConsoleObjectsMenu.tsx @@ -1,4 +1,10 @@ -import React, { ReactElement, useMemo, useRef, useState } from 'react'; +import React, { + ReactElement, + useCallback, + useMemo, + useRef, + useState, +} from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button, @@ -8,6 +14,7 @@ import { } from '@deephaven/components'; import { vsGraph, vsTriangleDown } from '@deephaven/icons'; import type { dh as DhType } from '@deephaven/jsapi-types'; +import { useDebouncedCallback } from '@deephaven/react-hooks'; import { EMPTY_FUNCTION } from '@deephaven/utils'; import { ObjectIcon } from './common'; @@ -16,6 +23,8 @@ interface ConsoleObjectsMenuProps { objects: readonly DhType.ide.VariableDefinition[]; } +const RESET_FILTER_DELAY = 5000; + function ConsoleObjectsMenu({ openObject, objects, @@ -23,6 +32,19 @@ function ConsoleObjectsMenu({ const [filterText, setFilterText] = useState(''); const searchRef = useRef(null); + const resetFilter = useDebouncedCallback(() => { + setFilterText(''); + }, RESET_FILTER_DELAY); + + const handleMenuOpen = useCallback(() => { + resetFilter.cancel(); + searchRef.current?.select(); + }, [resetFilter]); + + const handleMenuClose = useCallback(() => { + resetFilter(); + }, [resetFilter]); + const actions: DropdownActions = useMemo(() => { if (objects.length === 0) { return []; @@ -61,6 +83,7 @@ function ConsoleObjectsMenu({ aria-label="Objects" kind="ghost" disabled={objects.length === 0} + // no-op: click is handled in `DropdownMenu` onClick={EMPTY_FUNCTION} tooltip={objects.length === 0 ? 'No objects available' : 'Objects'} icon={ @@ -76,10 +99,8 @@ function ConsoleObjectsMenu({ > searchRef.current?.focus()} - onMenuClosed={() => { - searchRef.current?.focus(); - }} + onMenuOpened={handleMenuOpen} + onMenuClosed={handleMenuClose} options={{ initialKeyboardIndex: 1 }} popperOptions={{ placement: 'bottom-end' }} /> diff --git a/packages/console/src/ConsoleStatusBar.tsx b/packages/console/src/ConsoleStatusBar.tsx index 65582faaab..b95a6ea289 100644 --- a/packages/console/src/ConsoleStatusBar.tsx +++ b/packages/console/src/ConsoleStatusBar.tsx @@ -50,17 +50,10 @@ function ConsoleStatusBar({ useEffect( function startListening() { - session.addEventListener( + return session.addEventListener( dh.IdeSession.EVENT_COMMANDSTARTED, handleCommandStarted ); - - return function stopListening() { - session.removeEventListener( - dh.IdeSession.EVENT_COMMANDSTARTED, - handleCommandStarted - ); - }; }, [dh, handleCommandStarted, session] ); @@ -94,6 +87,7 @@ function ConsoleStatusBar({ icon={vsKebabVertical} tooltip="More Actions..." aria-label="More Actions..." + // no-op: click is handled in `DropdownMenu` onClick={EMPTY_FUNCTION} > } - showObjectsMenu={false} + // showObjectsMenu={false} scope={sessionId} timeZone={timeZone} objectMap={objectMap} From 7ef71b84e67daea0211a169bac243bc4968c8f8d Mon Sep 17 00:00:00 2001 From: mikebender Date: Tue, 13 Aug 2024 14:40:38 -0400 Subject: [PATCH 6/6] Uncomment out accidentally commented out code --- packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx b/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx index 70170cb27d..b99cd541d1 100644 --- a/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx +++ b/packages/dashboard-core-plugins/src/panels/ConsolePanel.tsx @@ -440,7 +440,7 @@ export class ConsolePanel extends PureComponent< /> } - // showObjectsMenu={false} + showObjectsMenu={false} scope={sessionId} timeZone={timeZone} objectMap={objectMap}