Handle view/edit mode in control group (#115867)

This commit is contained in:
Corey Robertson 2021-10-20 19:11:43 -04:00 committed by GitHub
parent 491ccc0d55
commit ee3c0c447e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 102 additions and 40 deletions

View file

@ -6,8 +6,10 @@
* Side Public License, v 1.
*/
import React, { useEffect, useMemo } from 'react';
import React, { useEffect, useMemo, useState, useCallback, FC } from 'react';
import uuid from 'uuid';
import { EuiFlexGroup, EuiFlexItem, EuiSwitch, EuiTextAlign } from '@elastic/eui';
import useEffectOnce from 'react-use/lib/useEffectOnce';
import { decorators } from './decorators';
import { pluginServices, registry } from '../../../services/storybook';
@ -18,6 +20,7 @@ import {
OptionsListEmbeddableInput,
OPTIONS_LIST_CONTROL,
} from '../control_types/options_list/options_list_embeddable';
import { ViewMode } from '../control_group/types';
export default {
title: 'Controls',
@ -25,13 +28,31 @@ export default {
decorators,
};
const EmptyControlGroupStoryComponent = ({ panels }: { panels?: ControlsPanels }) => {
type UnwrapPromise<T> = T extends Promise<infer P> ? P : T;
type EmbeddableType = UnwrapPromise<ReturnType<ControlGroupContainerFactory['create']>>;
const EmptyControlGroupStoryComponent: FC<{
panels?: ControlsPanels;
edit?: boolean;
}> = ({ panels, edit }) => {
const embeddableRoot: React.RefObject<HTMLDivElement> = useMemo(() => React.createRef(), []);
const [embeddable, setEmbeddable] = useState<EmbeddableType>();
const [viewMode, setViewMode] = useState<ViewMode>(
edit === undefined || edit ? ViewMode.EDIT : ViewMode.VIEW
);
const handleToggleViewMode = useCallback(() => {
if (embeddable) {
const newViewMode =
embeddable.getInput().viewMode === ViewMode.EDIT ? ViewMode.VIEW : ViewMode.EDIT;
embeddable.updateInput({ viewMode: newViewMode });
}
}, [embeddable]);
pluginServices.setRegistry(registry.start({}));
populateStorybookControlFactories(pluginServices.getServices().controls);
useEffect(() => {
useEffectOnce(() => {
(async () => {
const factory = new ControlGroupContainerFactory();
const controlGroupContainerEmbeddable = await factory.create({
@ -43,17 +64,45 @@ const EmptyControlGroupStoryComponent = ({ panels }: { panels?: ControlsPanels }
controlStyle: 'oneLine',
panels: panels ?? {},
id: uuid.v4(),
viewMode,
});
if (controlGroupContainerEmbeddable && embeddableRoot.current) {
controlGroupContainerEmbeddable.render(embeddableRoot.current);
}
setEmbeddable(controlGroupContainerEmbeddable);
})();
}, [embeddableRoot, panels]);
});
return <div ref={embeddableRoot} />;
useEffect(() => {
if (embeddable) {
const subscription = embeddable.getInput$().subscribe((updatedInput) => {
if (updatedInput.viewMode) {
setViewMode(updatedInput.viewMode);
}
});
return () => subscription.unsubscribe();
}
}, [embeddable, setViewMode]);
return (
<>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTextAlign textAlign="right">
<EuiSwitch checked={viewMode === 'edit'} label="Edit" onChange={handleToggleViewMode} />
</EuiTextAlign>
</EuiFlexItem>
</EuiFlexGroup>
<br />
<div ref={embeddableRoot} />
</>
);
};
export const EmptyControlGroupStory = () => <EmptyControlGroupStoryComponent />;
export const EmptyControlGroupStory = () => <EmptyControlGroupStoryComponent edit={false} />;
export const ConfiguredControlGroupStory = () => (
<EmptyControlGroupStoryComponent
panels={{

View file

@ -36,7 +36,7 @@ import {
LayoutMeasuringStrategy,
} from '@dnd-kit/core';
import { ControlGroupInput } from '../types';
import { ControlGroupInput, ViewMode } from '../types';
import { pluginServices } from '../../../../services';
import { ControlGroupStrings } from '../control_group_strings';
import { CreateControlButton } from '../editor/create_control';
@ -64,7 +64,9 @@ export const ControlGroup = () => {
const dispatch = useEmbeddableDispatch();
// current state
const { panels, controlStyle } = useEmbeddableSelector((state) => state);
const { panels, viewMode, controlStyle } = useEmbeddableSelector((state) => state);
const isEditable = viewMode === ViewMode.EDIT;
const idsInOrder = useMemo(
() =>
@ -100,6 +102,10 @@ export const ControlGroup = () => {
};
const emptyState = !(idsInOrder && idsInOrder.length > 0);
// Empty, non-editable view is null
if (!isEditable && emptyState) {
return null;
}
return (
<EuiPanel
@ -141,6 +147,7 @@ export const ControlGroup = () => {
(controlId, index) =>
panels[controlId] && (
<SortableControl
isEditable={isEditable}
dragInfo={{ index, draggingIndex }}
embeddableId={controlId}
key={controlId}
@ -154,33 +161,35 @@ export const ControlGroup = () => {
</DragOverlay>
</DndContext>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiFlexGroup responsive={false} className="groupEditActions" gutterSize="xs">
<EuiFlexItem>
<EuiToolTip content={ControlGroupStrings.management.getManageButtonTitle()}>
<EuiButtonIcon
aria-label={ControlGroupStrings.management.getManageButtonTitle()}
iconType="gear"
color="subdued"
data-test-subj="inputControlsSortingButton"
onClick={() => {
const flyoutInstance = openFlyout(
forwardAllContext(
<EditControlGroup closeFlyout={() => flyoutInstance.close()} />,
reduxContainerContext
)
);
}}
/>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem>
<EuiToolTip content={ControlGroupStrings.management.getAddControlTitle()}>
<CreateControlButton isIconButton={true} />
</EuiToolTip>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{isEditable && (
<EuiFlexItem grow={false}>
<EuiFlexGroup responsive={false} className="groupEditActions" gutterSize="xs">
<EuiFlexItem>
<EuiToolTip content={ControlGroupStrings.management.getManageButtonTitle()}>
<EuiButtonIcon
aria-label={ControlGroupStrings.management.getManageButtonTitle()}
iconType="gear"
color="subdued"
data-test-subj="inputControlsSortingButton"
onClick={() => {
const flyoutInstance = openFlyout(
forwardAllContext(
<EditControlGroup closeFlyout={() => flyoutInstance.close()} />,
reduxContainerContext
)
);
}}
/>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem>
<EuiToolTip content={ControlGroupStrings.management.getAddControlTitle()}>
<CreateControlButton isIconButton={true} />
</EuiToolTip>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
)}
</EuiFlexGroup>
) : (
<>

View file

@ -25,17 +25,19 @@ interface DragInfo {
export type SortableControlProps = ControlFrameProps & {
dragInfo: DragInfo;
isEditable: boolean;
};
/**
* A sortable wrapper around the generic control frame.
*/
export const SortableControl = (frameProps: SortableControlProps) => {
const { embeddableId } = frameProps;
const { embeddableId, isEditable } = frameProps;
const { over, listeners, isSorting, transform, transition, attributes, isDragging, setNodeRef } =
useSortable({
id: embeddableId,
animateLayoutChanges: () => true,
disabled: !isEditable,
});
frameProps.dragInfo = { ...frameProps.dragInfo, isOver: over?.id === embeddableId, isDragging };
@ -58,7 +60,7 @@ export const SortableControl = (frameProps: SortableControlProps) => {
const SortableControlInner = forwardRef<
HTMLButtonElement,
SortableControlProps & { style: HTMLAttributes<HTMLButtonElement>['style'] }
>(({ embeddableId, dragInfo, style, ...dragHandleProps }, dragHandleRef) => {
>(({ embeddableId, dragInfo, style, isEditable, ...dragHandleProps }, dragHandleRef) => {
const { isOver, isDragging, draggingIndex, index } = dragInfo;
const { useEmbeddableSelector } = useReduxContainerContext<ControlGroupInput>();
const { panels } = useEmbeddableSelector((state) => state);
@ -85,9 +87,9 @@ const SortableControlInner = forwardRef<
style={style}
>
<ControlFrame
enableActions={draggingIndex === -1}
enableActions={isEditable && draggingIndex === -1}
embeddableId={embeddableId}
customPrepend={dragHandle}
customPrepend={isEditable ? dragHandle : undefined}
/>
</EuiFlexItem>
);

View file

@ -6,10 +6,12 @@
* Side Public License, v 1.
*/
import { PanelState, EmbeddableInput } from '../../../../../embeddable/public';
import { PanelState, EmbeddableInput, ViewMode } from '../../../../../embeddable/public';
import { InputControlInput } from '../../../services/controls';
import { ControlStyle, ControlWidth } from '../types';
export { ViewMode };
export interface ControlGroupInput
extends EmbeddableInput,
Omit<InputControlInput, 'twoLineLayout'> {