Skip to content

Commit

Permalink
Add store data to ArgumentSelector components
Browse files Browse the repository at this point in the history
  • Loading branch information
paul-tavares committed Jan 24, 2023
1 parent 55816db commit 304bea4
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export interface ArgumentSelectorWrapperProps {
export const ArgumentSelectorWrapper = memo<ArgumentSelectorWrapperProps>(
({ argName, argInstance, argDefinition: { SelectorComponent } }) => {
const dispatch = useConsoleStateDispatch();
const { valueText, value } = useWithCommandArgumentState(argName, argInstance);
const { valueText, value, store } = useWithCommandArgumentState(argName, argInstance);

const handleSelectorComponentOnChange = useCallback<
CommandArgumentValueSelectorProps['onChange']
Expand Down Expand Up @@ -124,6 +124,7 @@ export const ArgumentSelectorWrapper = memo<ArgumentSelectorWrapperProps>(
valueText={valueText ?? ''}
argName={argName}
argInstance={argInstance}
store={store}
onChange={handleSelectorComponentOnChange}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,14 @@ export interface ConsoleDataState {
}

/** State that is provided/received to Argument Value Selectors */
export interface ArgSelectorState {
export interface ArgSelectorState<TState = any> {
value: any;
valueText: string | undefined;
/**
* A store (data) for the Argument Selector Component so that it can persist state between
* re-renders or between console being opened/closed
*/
store?: TState;
}

export interface EnteredCommand {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { ComponentType, ReactNode } from 'react';
import type { CommonProps } from '@elastic/eui';
import type { ParsedArgData, ParsedCommandInterface, PossibleArgDataTypes } from './service/types';
import type { CommandExecutionResultComponent } from './components/command_execution_result';
import type { CommandExecutionState } from './components/console_state/types';
import type { CommandExecutionState, ArgSelectorState } from './components/console_state/types';
import type { Immutable, MaybeImmutable } from '../../../../common/endpoint/types';

/**
Expand Down Expand Up @@ -216,7 +216,7 @@ export type CommandExecutionComponent<
/**
* The component props for an argument `SelectorComponent`
*/
export interface CommandArgumentValueSelectorProps<TSelection = any> {
export interface CommandArgumentValueSelectorProps<TSelection = any, TState = any> {
/**
* The current value that was selected. This will not be displayed in the UI, but will
* be passed on to the command execution as part of the argument's value
Expand All @@ -241,13 +241,19 @@ export interface CommandArgumentValueSelectorProps<TSelection = any> {
*/
argInstance: number;

/**
* A store for the Argument Selector. Should be used for any component state that needs to be
* persisted across re-renders by the console.
*/
store: TState;

/**
* callback for the Value Selector to call and provide the selection value.
* This selection value will then be passed along with the argument to the command execution
* component.
* @param newData
*/
onChange: (newData: { value: TSelection | undefined; valueText: string }) => void;
onChange: (newData: ArgSelectorState<TState>) => void;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { memo, useCallback, useMemo, useState } from 'react';
import React, { memo, useCallback, useMemo } from 'react';
import {
EuiButtonIcon,
EuiFilePicker,
Expand Down Expand Up @@ -33,77 +33,99 @@ const NO_FILE_SELECTED = i18n.translate(
{ defaultMessage: 'No file selected' }
);

interface ArgumentFileSelectorState {
isPopoverOpen: boolean;
}

/**
* A Console Argument Selector component that enables the user to select a file from the local machine
*/
export const ArgumentFileSelector = memo<CommandArgumentValueSelectorProps<File>>(
({ value, valueText, onChange }) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(!value);
export const ArgumentFileSelector = memo<
CommandArgumentValueSelectorProps<File, ArgumentFileSelectorState>
>(({ value, valueText, onChange, store: _store }) => {
const state = useMemo<ArgumentFileSelectorState>(() => {
return _store ?? { isPopoverOpen: true };
}, [_store]);

const filePickerUUID = useMemo(() => {
return htmlIdGenerator('console')();
}, []);
const setIsPopoverOpen = useCallback(
(newValue: boolean) => {
onChange({
value,
valueText,
store: {
...state,
isPopoverOpen: newValue,
},
});
},
[onChange, state, value, valueText]
);

const handleOpenPopover = useCallback(() => {
setIsPopoverOpen((prevState) => !prevState);
}, []);
const filePickerUUID = useMemo(() => {
return htmlIdGenerator('console')();
}, []);

const handleClosePopover = useCallback(() => {
setIsPopoverOpen(false);
}, []);
const handleOpenPopover = useCallback(() => {
setIsPopoverOpen(true);
}, [setIsPopoverOpen]);

const handleFileSelection: EuiFilePickerProps['onChange'] = useCallback(
(selectedFiles) => {
// Get only the first file selected
const file = selectedFiles?.item(0);
const handleClosePopover = useCallback(() => {
setIsPopoverOpen(false);
}, [setIsPopoverOpen]);

onChange({
value: file ?? undefined,
valueText: file ? file.name : '',
});
const handleFileSelection: EuiFilePickerProps['onChange'] = useCallback(
(selectedFiles) => {
// Get only the first file selected
const file = selectedFiles?.item(0);

setIsPopoverOpen(false);
},
[onChange]
);
onChange({
value: file ?? undefined,
valueText: file ? file.name : '',
store: {
...state,
isPopoverOpen: false,
},
});
},
[onChange, state]
);

return (
<div>
<EuiPopover
isOpen={isPopoverOpen}
closePopover={handleClosePopover}
anchorPosition="upCenter"
initialFocus={`#${filePickerUUID}`}
anchorClassName="popoverAnchor"
button={
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="none">
<EuiFlexItem grow={false} className="eui-textTruncate" onClick={handleOpenPopover}>
<div className="eui-textTruncate" title={valueText || NO_FILE_SELECTED}>
{valueText || INITIAL_DISPLAY_LABEL}
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
iconType="folderOpen"
size="xs"
onClick={handleOpenPopover}
aria-label={OPEN_FILE_PICKER_LABEL}
/>
</EuiFlexItem>
</EuiFlexGroup>
}
>
{isPopoverOpen && (
<EuiFilePicker
id={filePickerUUID}
onChange={handleFileSelection}
fullWidth
display="large"
/>
)}
</EuiPopover>
</div>
);
}
);
return (
<div>
<EuiPopover
isOpen={state.isPopoverOpen}
closePopover={handleClosePopover}
anchorPosition="upCenter"
initialFocus={`#${filePickerUUID}`}
anchorClassName="popoverAnchor"
button={
<EuiFlexGroup responsive={false} alignItems="center" gutterSize="none">
<EuiFlexItem grow={false} className="eui-textTruncate" onClick={handleOpenPopover}>
<div className="eui-textTruncate" title={valueText || NO_FILE_SELECTED}>
{valueText || INITIAL_DISPLAY_LABEL}
</div>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonIcon
iconType="folderOpen"
size="xs"
onClick={handleOpenPopover}
aria-label={OPEN_FILE_PICKER_LABEL}
/>
</EuiFlexItem>
</EuiFlexGroup>
}
>
{state.isPopoverOpen && (
<EuiFilePicker
id={filePickerUUID}
onChange={handleFileSelection}
fullWidth
display="large"
/>
)}
</EuiPopover>
</div>
);
});
ArgumentFileSelector.displayName = 'ArgumentFileSelector';

0 comments on commit 304bea4

Please sign in to comment.