Skip to content

Commit

Permalink
migrated options list and range slider to use new redux system. Start…
Browse files Browse the repository at this point in the history
…ing on Control Group
  • Loading branch information
ThomThomson committed Jul 18, 2022
1 parent 450d2f9 commit 0e00f73
Show file tree
Hide file tree
Showing 27 changed files with 714 additions and 629 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export interface OptionsListEmbeddableInput extends DataControlInput {
selectedOptions?: string[];
runPastTimeout?: boolean;
singleSelect?: boolean;
loading?: boolean;
}

export type OptionsListField = DataViewField & {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,45 +10,45 @@ import { PayloadAction } from '@reduxjs/toolkit';
import { WritableDraft } from 'immer/dist/types/types-external';

import { ControlWidth } from '../../types';
import { ControlGroupInput } from '../types';
import { ControlGroupInput, ControlGroupReduxState } from '../types';

export const controlGroupReducers = {
setControlStyle: (
state: WritableDraft<ControlGroupInput>,
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<ControlGroupInput['controlStyle']>
) => {
state.controlStyle = action.payload;
state.input.controlStyle = action.payload;
},
setDefaultControlWidth: (
state: WritableDraft<ControlGroupInput>,
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<ControlGroupInput['defaultControlWidth']>
) => {
state.defaultControlWidth = action.payload;
state.input.defaultControlWidth = action.payload;
},
setDefaultControlGrow: (
state: WritableDraft<ControlGroupInput>,
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<ControlGroupInput['defaultControlGrow']>
) => {
state.defaultControlGrow = action.payload;
state.input.defaultControlGrow = action.payload;
},
setControlWidth: (
state: WritableDraft<ControlGroupInput>,
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<{ width: ControlWidth; embeddableId: string }>
) => {
state.panels[action.payload.embeddableId].width = action.payload.width;
state.input.panels[action.payload.embeddableId].width = action.payload.width;
},
setControlGrow: (
state: WritableDraft<ControlGroupInput>,
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<{ grow: boolean; embeddableId: string }>
) => {
state.panels[action.payload.embeddableId].grow = action.payload.grow;
state.input.panels[action.payload.embeddableId].grow = action.payload.grow;
},
setControlOrders: (
state: WritableDraft<ControlGroupInput>,
state: WritableDraft<ControlGroupReduxState>,
action: PayloadAction<{ ids: string[] }>
) => {
action.payload.ids.forEach((id, index) => {
state.panels[id].order = index;
state.input.panels[id].order = index;
});
},
};
5 changes: 5 additions & 0 deletions src/plugins/controls/public/control_group/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@
*/

import { ContainerOutput } from '@kbn/embeddable-plugin/public';
import { ReduxEmbeddableState } from '@kbn/presentation-util-plugin/public';
import { ControlGroupInput } from '../../common/control_group/types';
import { CommonControlOutput } from '../types';

export type ControlGroupOutput = ContainerOutput & CommonControlOutput;

// public only - redux embeddable state type
export type ControlGroupReduxState = ReduxEmbeddableState<ControlGroupInput, ControlGroupOutput>;

export {
type ControlsPanels,
type ControlGroupInput,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,68 +8,53 @@

import { EuiFilterButton, EuiFilterGroup, EuiPopover, useResizeObserver } from '@elastic/eui';
import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
import { BehaviorSubject, Subject } from 'rxjs';
import { Subject } from 'rxjs';
import classNames from 'classnames';
import { debounce, isEmpty } from 'lodash';

import { useReduxEmbeddableContext } from '@kbn/presentation-util-plugin/public';
import { DataViewField } from '@kbn/data-views-plugin/public';
import { OptionsListStrings } from './options_list_strings';
import { optionsListReducers } from './options_list_reducers';
import { OptionsListPopover } from './options_list_popover_component';

import './options_list.scss';
import { useStateObservable } from '../../hooks/use_state_observable';
import { OptionsListEmbeddableInput } from './types';

// OptionsListComponentState is controlled by the embeddable, but is not considered embeddable input.
export interface OptionsListComponentState {
loading: boolean;
field?: DataViewField;
totalCardinality?: number;
availableOptions?: string[];
invalidSelections?: string[];
validSelections?: string[];
}
import { OptionsListReduxState } from './types';

export const OptionsListComponent = ({
typeaheadSubject,
componentStateSubject,
}: {
typeaheadSubject: Subject<string>;
componentStateSubject: BehaviorSubject<OptionsListComponentState>;
}) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const [searchString, setSearchString] = useState('');

const resizeRef = useRef(null);
const dimensions = useResizeObserver(resizeRef.current);

// Redux embeddable Context to get state from Embeddable input
// Redux embeddable Context
const {
useEmbeddableDispatch,
useEmbeddableSelector,
actions: { replaceSelection },
} = useReduxEmbeddableContext<OptionsListEmbeddableInput, typeof optionsListReducers>();
useEmbeddableSelector: select,
} = useReduxEmbeddableContext<OptionsListReduxState, typeof optionsListReducers>();
const dispatch = useEmbeddableDispatch();
const { controlStyle, selectedOptions, singleSelect, id } = useEmbeddableSelector(
(state) => state
);

// useStateObservable to get component state from Embeddable
const { availableOptions, loading, invalidSelections, validSelections, totalCardinality, field } =
useStateObservable<OptionsListComponentState>(
componentStateSubject,
componentStateSubject.getValue()
);
// Select current state from Redux using multiple selectors to avoid rerenders.
const invalidSelections = select((state) => state.componentState?.invalidSelections);
const validSelections = select((state) => state.componentState?.validSelections);
const selectedOptions = select((state) => state.input.selectedOptions);
const controlStyle = select((state) => state.input.controlStyle);
const singleSelect = select((state) => state.input.singleSelect);
const loading = select((state) => state.output.loading);
const id = select((state) => state.input.id);

// debounce loading state so loading doesn't flash when user types
const [buttonLoading, setButtonLoading] = useState(true);
const debounceSetButtonLoading = useMemo(
() => debounce((latestLoading: boolean) => setButtonLoading(latestLoading), 100),
[]
);
useEffect(() => debounceSetButtonLoading(loading), [loading, debounceSetButtonLoading]);
useEffect(() => debounceSetButtonLoading(loading ?? false), [loading, debounceSetButtonLoading]);

// remove all other selections if this control is single select
useEffect(() => {
Expand Down Expand Up @@ -132,24 +117,19 @@ export const OptionsListComponent = ({
})}
>
<EuiPopover
ownFocus
button={button}
repositionOnScroll
isOpen={isPopoverOpen}
className="optionsList__popoverOverride"
anchorClassName="optionsList__anchorOverride"
closePopover={() => setIsPopoverOpen(false)}
panelPaddingSize="none"
anchorPosition="downCenter"
ownFocus
repositionOnScroll
className="optionsList__popoverOverride"
closePopover={() => setIsPopoverOpen(false)}
anchorClassName="optionsList__anchorOverride"
>
<OptionsListPopover
field={field}
width={dimensions.width}
loading={loading}
searchString={searchString}
totalCardinality={totalCardinality}
availableOptions={availableOptions}
invalidSelections={invalidSelections}
updateSearchString={updateSearchString}
/>
</EuiPopover>
Expand Down
Loading

0 comments on commit 0e00f73

Please sign in to comment.