mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
Handle view/edit mode in control group (#115867)
This commit is contained in:
parent
491ccc0d55
commit
ee3c0c447e
4 changed files with 102 additions and 40 deletions
|
@ -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={{
|
||||
|
|
|
@ -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>
|
||||
) : (
|
||||
<>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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'> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue