mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Dashboard] Focus on a single panel (#165417)
This commit is contained in:
parent
d4bb52b8b2
commit
1066eb3d59
18 changed files with 250 additions and 43 deletions
|
@ -20,7 +20,7 @@ type ToolbarButtonTypes = 'primary' | 'empty';
|
|||
export interface Props
|
||||
extends Pick<
|
||||
EuiButtonPropsForButton,
|
||||
'onClick' | 'iconType' | 'iconSide' | 'size' | 'data-test-subj'
|
||||
'onClick' | 'iconType' | 'iconSide' | 'size' | 'data-test-subj' | 'isDisabled'
|
||||
> {
|
||||
label: string;
|
||||
type?: ToolbarButtonTypes;
|
||||
|
@ -31,16 +31,23 @@ export const ToolbarButton: React.FunctionComponent<Props> = ({
|
|||
type = 'empty',
|
||||
iconSide = 'left',
|
||||
size = 'm',
|
||||
isDisabled,
|
||||
...rest
|
||||
}) => {
|
||||
const euiTheme = useEuiTheme();
|
||||
const toolbarButtonStyleProps: EuiButtonPropsForButton =
|
||||
type === 'primary'
|
||||
const toolbarButtonStyleProps: EuiButtonPropsForButton = !isDisabled
|
||||
? type === 'primary'
|
||||
? { color: 'primary', fill: true }
|
||||
: { color: 'text', css: ToolbarButtonStyles(euiTheme).emptyButton };
|
||||
: { color: 'text', css: ToolbarButtonStyles(euiTheme).emptyButton }
|
||||
: {};
|
||||
|
||||
return (
|
||||
<EuiButton size={size} {...toolbarButtonStyleProps} {...{ iconSide, ...rest }}>
|
||||
<EuiButton
|
||||
size={size}
|
||||
isDisabled={isDisabled}
|
||||
{...toolbarButtonStyleProps}
|
||||
{...{ iconSide, ...rest }}
|
||||
>
|
||||
{label}
|
||||
</EuiButton>
|
||||
);
|
||||
|
|
|
@ -35,6 +35,7 @@ export const ToolbarPopover = ({
|
|||
iconType,
|
||||
size = 'm',
|
||||
children,
|
||||
isDisabled,
|
||||
...popover
|
||||
}: Props) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
@ -46,6 +47,7 @@ export const ToolbarPopover = ({
|
|||
<ToolbarButton
|
||||
onClick={onButtonClick}
|
||||
size={size}
|
||||
isDisabled={isDisabled}
|
||||
{...{ type, label, iconType: iconType || 'arrowDown', iconSide: iconType ? 'left' : 'right' }}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -17,7 +17,13 @@ import { AddDataControlButton } from './add_data_control_button';
|
|||
import { AddTimeSliderControlButton } from './add_time_slider_control_button';
|
||||
import { EditControlGroupButton } from './edit_control_group_button';
|
||||
|
||||
export function ControlsToolbarButton({ controlGroup }: { controlGroup: ControlGroupContainer }) {
|
||||
export function ControlsToolbarButton({
|
||||
controlGroup,
|
||||
isDisabled,
|
||||
}: {
|
||||
controlGroup: ControlGroupContainer;
|
||||
isDisabled?: boolean;
|
||||
}) {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
return (
|
||||
|
@ -30,6 +36,7 @@ export function ControlsToolbarButton({ controlGroup }: { controlGroup: ControlG
|
|||
size="s"
|
||||
iconType="controlsHorizontal"
|
||||
data-test-subj="dashboard-controls-menu-button"
|
||||
isDisabled={isDisabled}
|
||||
>
|
||||
{({ closePopover }: { closePopover: () => void }) => (
|
||||
<EuiContextMenuPanel
|
||||
|
|
|
@ -23,7 +23,7 @@ import { ControlsToolbarButton } from './controls_toolbar_button';
|
|||
import { DASHBOARD_APP_ID, DASHBOARD_UI_METRIC_ID } from '../../dashboard_constants';
|
||||
import { dashboardReplacePanelActionStrings } from '../../dashboard_actions/_dashboard_actions_strings';
|
||||
|
||||
export function DashboardEditingToolbar() {
|
||||
export function DashboardEditingToolbar({ isDisabled }: { isDisabled?: boolean }) {
|
||||
const {
|
||||
usageCollection,
|
||||
data: { search },
|
||||
|
@ -106,15 +106,22 @@ export function DashboardEditingToolbar() {
|
|||
);
|
||||
|
||||
const extraButtons = [
|
||||
<EditorMenu createNewVisType={createNewVisType} createNewEmbeddable={createNewEmbeddable} />,
|
||||
<EditorMenu
|
||||
createNewVisType={createNewVisType}
|
||||
createNewEmbeddable={createNewEmbeddable}
|
||||
isDisabled={isDisabled}
|
||||
/>,
|
||||
<AddFromLibraryButton
|
||||
onClick={() => dashboard.addFromLibrary()}
|
||||
size="s"
|
||||
data-test-subj="dashboardAddFromLibraryButton"
|
||||
isDisabled={isDisabled}
|
||||
/>,
|
||||
];
|
||||
if (dashboard.controlGroup) {
|
||||
extraButtons.push(<ControlsToolbarButton controlGroup={dashboard.controlGroup} />);
|
||||
extraButtons.push(
|
||||
<ControlsToolbarButton isDisabled={isDisabled} controlGroup={dashboard.controlGroup} />
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -128,6 +135,7 @@ export function DashboardEditingToolbar() {
|
|||
primaryButton: (
|
||||
<ToolbarButton
|
||||
type="primary"
|
||||
isDisabled={isDisabled}
|
||||
iconType="lensApp"
|
||||
size="s"
|
||||
onClick={createNewVisType(lensAlias)}
|
||||
|
|
|
@ -83,6 +83,7 @@ export function DashboardTopNav({ embedSettings, redirectTo }: DashboardTopNavPr
|
|||
const fullScreenMode = dashboard.select((state) => state.componentState.fullScreenMode);
|
||||
const savedQueryId = dashboard.select((state) => state.componentState.savedQueryId);
|
||||
const lastSavedId = dashboard.select((state) => state.componentState.lastSavedId);
|
||||
const focusedPanelId = dashboard.select((state) => state.componentState.focusedPanelId);
|
||||
const managed = dashboard.select((state) => state.componentState.managed);
|
||||
|
||||
const viewMode = dashboard.select((state) => state.explicitInput.viewMode);
|
||||
|
@ -323,7 +324,9 @@ export function DashboardTopNav({ embedSettings, redirectTo }: DashboardTopNavPr
|
|||
<LabsFlyout solutions={['dashboard']} onClose={() => setIsLabsShown(false)} />
|
||||
</PresentationUtilContextProvider>
|
||||
) : null}
|
||||
{viewMode === ViewMode.EDIT ? <DashboardEditingToolbar /> : null}
|
||||
{viewMode === ViewMode.EDIT ? (
|
||||
<DashboardEditingToolbar isDisabled={!!focusedPanelId} />
|
||||
) : null}
|
||||
<EuiHorizontalRule margin="none" />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -24,6 +24,7 @@ import { pluginServices } from '../../services/plugin_services';
|
|||
import { DASHBOARD_APP_ID } from '../../dashboard_constants';
|
||||
|
||||
interface Props {
|
||||
isDisabled?: boolean;
|
||||
/** Handler for creating new visualization of a specified type */
|
||||
createNewVisType: (visType: BaseVisType | VisTypeAlias) => () => void;
|
||||
/** Handler for creating a new embeddable of a specified type */
|
||||
|
@ -43,7 +44,7 @@ interface UnwrappedEmbeddableFactory {
|
|||
isEditable: boolean;
|
||||
}
|
||||
|
||||
export const EditorMenu = ({ createNewVisType, createNewEmbeddable }: Props) => {
|
||||
export const EditorMenu = ({ createNewVisType, createNewEmbeddable, isDisabled }: Props) => {
|
||||
const {
|
||||
embeddable,
|
||||
visualizations: {
|
||||
|
@ -273,6 +274,7 @@ export const EditorMenu = ({ createNewVisType, createNewEmbeddable }: Props) =>
|
|||
label={i18n.translate('dashboard.solutionToolbar.editorMenuButtonLabel', {
|
||||
defaultMessage: 'Add panel',
|
||||
})}
|
||||
isDisabled={isDisabled}
|
||||
size="s"
|
||||
iconType="plusInCircle"
|
||||
panelPaddingSize="none"
|
||||
|
|
|
@ -21,12 +21,19 @@ jest.mock('./dashboard_grid_item', () => {
|
|||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
DashboardGridItem: require('react').forwardRef(
|
||||
(props: DashboardGridItemProps, ref: HTMLDivElement) => {
|
||||
const className =
|
||||
const className = `${
|
||||
props.expandedPanelId === undefined
|
||||
? 'regularPanel'
|
||||
: props.expandedPanelId === props.id
|
||||
? 'expandedPanel'
|
||||
: 'hiddenPanel';
|
||||
: 'hiddenPanel'
|
||||
} ${
|
||||
props.focusedPanelId
|
||||
? props.focusedPanelId === props.id
|
||||
? 'focusedPanel'
|
||||
: 'blurredPanel'
|
||||
: ''
|
||||
}`;
|
||||
return (
|
||||
<div className={className} id={`mockDashboardGridItem_${props.id}`}>
|
||||
mockDashboardGridItem
|
||||
|
@ -101,3 +108,21 @@ test('DashboardGrid renders expanded panel', async () => {
|
|||
expect(component.find('#mockDashboardGridItem_1').hasClass('regularPanel')).toBe(true);
|
||||
expect(component.find('#mockDashboardGridItem_2').hasClass('regularPanel')).toBe(true);
|
||||
});
|
||||
|
||||
test('DashboardGrid renders focused panel', async () => {
|
||||
const { dashboardContainer, component } = createAndMountDashboardGrid();
|
||||
dashboardContainer.setFocusedPanelId('2');
|
||||
component.update();
|
||||
// Both panels should still exist in the dom, so nothing needs to be re-fetched once minimized.
|
||||
expect(component.find('GridItem').length).toBe(2);
|
||||
|
||||
expect(component.find('#mockDashboardGridItem_1').hasClass('blurredPanel')).toBe(true);
|
||||
expect(component.find('#mockDashboardGridItem_2').hasClass('focusedPanel')).toBe(true);
|
||||
|
||||
dashboardContainer.setFocusedPanelId(undefined);
|
||||
component.update();
|
||||
expect(component.find('GridItem').length).toBe(2);
|
||||
|
||||
expect(component.find('#mockDashboardGridItem_1').hasClass('blurredPanel')).toBe(false);
|
||||
expect(component.find('#mockDashboardGridItem_2').hasClass('focusedPanel')).toBe(false);
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ export const DashboardGrid = ({ viewportWidth }: { viewportWidth: number }) => {
|
|||
const viewMode = dashboard.select((state) => state.explicitInput.viewMode);
|
||||
const useMargins = dashboard.select((state) => state.explicitInput.useMargins);
|
||||
const expandedPanelId = dashboard.select((state) => state.componentState.expandedPanelId);
|
||||
const focusedPanelId = dashboard.select((state) => state.componentState.focusedPanelId);
|
||||
const animatePanelTransforms = dashboard.select(
|
||||
(state) => state.componentState.animatePanelTransforms
|
||||
);
|
||||
|
@ -78,11 +79,12 @@ export const DashboardGrid = ({ viewportWidth }: { viewportWidth: number }) => {
|
|||
index={index + 1}
|
||||
type={type}
|
||||
expandedPanelId={expandedPanelId}
|
||||
focusedPanelId={focusedPanelId}
|
||||
onPanelStatusChange={onPanelStatusChange}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}, [expandedPanelId, onPanelStatusChange, panels, panelsInOrder]);
|
||||
}, [expandedPanelId, onPanelStatusChange, panels, panelsInOrder, focusedPanelId]);
|
||||
|
||||
const onLayoutChange = useCallback(
|
||||
(newLayout: Array<Layout & { i: string }>) => {
|
||||
|
@ -127,8 +129,8 @@ export const DashboardGrid = ({ viewportWidth }: { viewportWidth: number }) => {
|
|||
breakpoints={breakpoints}
|
||||
onDragStop={onLayoutChange}
|
||||
onResizeStop={onLayoutChange}
|
||||
isResizable={!expandedPanelId}
|
||||
isDraggable={!expandedPanelId}
|
||||
isResizable={!expandedPanelId && !focusedPanelId}
|
||||
isDraggable={!expandedPanelId && !focusedPanelId}
|
||||
rowHeight={DASHBOARD_GRID_HEIGHT}
|
||||
margin={useMargins ? [DASHBOARD_MARGIN_SIZE, DASHBOARD_MARGIN_SIZE] : [0, 0]}
|
||||
draggableHandle={'.embPanel--dragHandle'}
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { mountWithIntl } from '@kbn/test-jest-helpers';
|
||||
import { CONTACT_CARD_EMBEDDABLE } from '@kbn/embeddable-plugin/public/lib/test_samples/embeddables';
|
||||
|
||||
import { buildMockDashboard } from '../../../mocks';
|
||||
import { Item, Props as DashboardGridItemProps } from './dashboard_grid_item';
|
||||
import { DashboardContainerContext } from '../../embeddable/dashboard_container';
|
||||
|
||||
jest.mock('@kbn/embeddable-plugin/public', () => {
|
||||
const original = jest.requireActual('@kbn/embeddable-plugin/public');
|
||||
|
||||
return {
|
||||
...original,
|
||||
EmbeddablePanel: (props: DashboardGridItemProps) => {
|
||||
return (
|
||||
<div className="embedPanel" id={`mockEmbedPanel_${props.id}`}>
|
||||
mockEmbeddablePanel
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const createAndMountDashboardGridItem = (props: DashboardGridItemProps) => {
|
||||
const panels = {
|
||||
'1': {
|
||||
gridData: { x: 0, y: 0, w: 6, h: 6, i: '1' },
|
||||
type: CONTACT_CARD_EMBEDDABLE,
|
||||
explicitInput: { id: '1' },
|
||||
},
|
||||
'2': {
|
||||
gridData: { x: 6, y: 6, w: 6, h: 6, i: '2' },
|
||||
type: CONTACT_CARD_EMBEDDABLE,
|
||||
explicitInput: { id: '2' },
|
||||
},
|
||||
};
|
||||
const dashboardContainer = buildMockDashboard({ panels });
|
||||
|
||||
const component = mountWithIntl(
|
||||
<DashboardContainerContext.Provider value={dashboardContainer}>
|
||||
<Item {...props} />
|
||||
</DashboardContainerContext.Provider>
|
||||
);
|
||||
return { dashboardContainer, component };
|
||||
};
|
||||
|
||||
test('renders Item', async () => {
|
||||
const { component } = createAndMountDashboardGridItem({
|
||||
id: '1',
|
||||
key: '1',
|
||||
type: CONTACT_CARD_EMBEDDABLE,
|
||||
});
|
||||
const panelElements = component.find('.embedPanel');
|
||||
expect(panelElements.length).toBe(1);
|
||||
|
||||
expect(component.find('#panel-1').hasClass('dshDashboardGrid__item--expanded')).toBe(false);
|
||||
expect(component.find('#panel-1').hasClass('dshDashboardGrid__item--hidden')).toBe(false);
|
||||
|
||||
expect(component.find('#panel-1').hasClass('dshDashboardGrid__item--focused')).toBe(false);
|
||||
expect(component.find('#panel-1').hasClass('dshDashboardGrid__item--blurred')).toBe(false);
|
||||
});
|
||||
|
||||
test('renders expanded panel', async () => {
|
||||
const { component } = createAndMountDashboardGridItem({
|
||||
id: '1',
|
||||
key: '1',
|
||||
type: CONTACT_CARD_EMBEDDABLE,
|
||||
expandedPanelId: '1',
|
||||
});
|
||||
expect(component.find('#panel-1').hasClass('dshDashboardGrid__item--expanded')).toBe(true);
|
||||
expect(component.find('#panel-1').hasClass('dshDashboardGrid__item--hidden')).toBe(false);
|
||||
});
|
||||
|
||||
test('renders hidden panel', async () => {
|
||||
const { component } = createAndMountDashboardGridItem({
|
||||
id: '1',
|
||||
key: '1',
|
||||
type: CONTACT_CARD_EMBEDDABLE,
|
||||
expandedPanelId: '2',
|
||||
});
|
||||
expect(component.find('#panel-1').hasClass('dshDashboardGrid__item--expanded')).toBe(false);
|
||||
expect(component.find('#panel-1').hasClass('dshDashboardGrid__item--hidden')).toBe(true);
|
||||
});
|
||||
|
||||
test('renders focused panel', async () => {
|
||||
const { component } = createAndMountDashboardGridItem({
|
||||
id: '1',
|
||||
key: '1',
|
||||
type: CONTACT_CARD_EMBEDDABLE,
|
||||
focusedPanelId: '1',
|
||||
});
|
||||
|
||||
expect(component.find('#panel-1').hasClass('dshDashboardGrid__item--focused')).toBe(true);
|
||||
expect(component.find('#panel-1').hasClass('dshDashboardGrid__item--blurred')).toBe(false);
|
||||
});
|
||||
|
||||
test('renders blurred panel', async () => {
|
||||
const { component } = createAndMountDashboardGridItem({
|
||||
id: '1',
|
||||
key: '1',
|
||||
type: CONTACT_CARD_EMBEDDABLE,
|
||||
focusedPanelId: '2',
|
||||
});
|
||||
|
||||
expect(component.find('#panel-1').hasClass('dshDashboardGrid__item--focused')).toBe(false);
|
||||
expect(component.find('#panel-1').hasClass('dshDashboardGrid__item--blurred')).toBe(true);
|
||||
});
|
|
@ -12,6 +12,7 @@ import classNames from 'classnames';
|
|||
|
||||
import { EmbeddablePhaseEvent, EmbeddablePanel, ViewMode } from '@kbn/embeddable-plugin/public';
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import { DashboardPanelState } from '../../../../common';
|
||||
import { pluginServices } from '../../../services/plugin_services';
|
||||
import { useDashboardContainer } from '../../embeddable/dashboard_container';
|
||||
|
@ -22,14 +23,14 @@ export interface Props extends DivProps {
|
|||
id: DashboardPanelState['explicitInput']['id'];
|
||||
index?: number;
|
||||
type: DashboardPanelState['type'];
|
||||
focusedPanelId?: string;
|
||||
expandedPanelId?: string;
|
||||
focusedPanelId?: string;
|
||||
key: string;
|
||||
isRenderable?: boolean;
|
||||
onPanelStatusChange?: (info: EmbeddablePhaseEvent) => void;
|
||||
}
|
||||
|
||||
const Item = React.forwardRef<HTMLDivElement, Props>(
|
||||
export const Item = React.forwardRef<HTMLDivElement, Props>(
|
||||
(
|
||||
{
|
||||
expandedPanelId,
|
||||
|
@ -43,7 +44,6 @@ const Item = React.forwardRef<HTMLDivElement, Props>(
|
|||
// https://github.com/react-grid-layout/react-grid-layout/issues/1241#issuecomment-658306889
|
||||
children,
|
||||
className,
|
||||
style,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
|
@ -54,9 +54,13 @@ const Item = React.forwardRef<HTMLDivElement, Props>(
|
|||
|
||||
const expandPanel = expandedPanelId !== undefined && expandedPanelId === id;
|
||||
const hidePanel = expandedPanelId !== undefined && expandedPanelId !== id;
|
||||
const focusPanel = focusedPanelId !== undefined && focusedPanelId === id;
|
||||
const blurPanel = focusedPanelId !== undefined && focusedPanelId !== id;
|
||||
const classes = classNames({
|
||||
'dshDashboardGrid__item--expanded': expandPanel,
|
||||
'dshDashboardGrid__item--hidden': hidePanel,
|
||||
'dshDashboardGrid__item--focused': focusPanel,
|
||||
'dshDashboardGrid__item--blurred': blurPanel,
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
printViewport__vis: container.getInput().viewMode === ViewMode.PRINT,
|
||||
});
|
||||
|
@ -69,12 +73,29 @@ const Item = React.forwardRef<HTMLDivElement, Props>(
|
|||
if (highlightPanelId === id) {
|
||||
container.highlightPanel(ref.current);
|
||||
}
|
||||
|
||||
ref.current.querySelectorAll('*').forEach((e) => {
|
||||
if (blurPanel) {
|
||||
// remove blurred panels and nested elements from tab order
|
||||
e.setAttribute('tabindex', '-1');
|
||||
} else {
|
||||
// restore tab order
|
||||
e.removeAttribute('tabindex');
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [id, container, scrollToPanelId, highlightPanelId, ref]);
|
||||
}, [id, container, scrollToPanelId, highlightPanelId, ref, blurPanel]);
|
||||
|
||||
const focusStyles = blurPanel
|
||||
? css`
|
||||
pointer-events: none;
|
||||
opacity: 0.25;
|
||||
`
|
||||
: css``;
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ ...style, zIndex: focusedPanelId === id ? 2 : 'auto' }}
|
||||
css={focusStyles}
|
||||
className={[classes, className].join(' ')}
|
||||
data-test-subj="dashboardPanel"
|
||||
id={`panel-${id}`}
|
||||
|
@ -140,11 +161,16 @@ export const DashboardGridItem = React.forwardRef<HTMLDivElement, Props>((props,
|
|||
const {
|
||||
settings: { isProjectEnabledInLabs },
|
||||
} = pluginServices.getServices();
|
||||
const container = useDashboardContainer();
|
||||
const focusedPanelId = container.select((state) => state.componentState.focusedPanelId);
|
||||
|
||||
const dashboard = useDashboardContainer();
|
||||
|
||||
const isPrintMode = dashboard.select((state) => state.explicitInput.viewMode) === ViewMode.PRINT;
|
||||
const isEnabled = !isPrintMode && isProjectEnabledInLabs('labs:dashboard:deferBelowFold');
|
||||
const isEnabled =
|
||||
!isPrintMode &&
|
||||
isProjectEnabledInLabs('labs:dashboard:deferBelowFold') &&
|
||||
(!focusedPanelId || focusedPanelId === props.id);
|
||||
|
||||
return isEnabled ? <ObservedItem ref={ref} {...props} /> : <Item ref={ref} {...props} />;
|
||||
});
|
||||
|
|
|
@ -23,7 +23,9 @@
|
|||
|
||||
// Remove border color unless in editing mode
|
||||
.dshLayout-withoutMargins:not(.dshLayout--editing),
|
||||
.dshDashboardGrid__item--expanded {
|
||||
.dshDashboardGrid__item--expanded,
|
||||
.dshDashboardGrid__item--blurred,
|
||||
.dshDashboardGrid__item--focused {
|
||||
.embPanel {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
|
|
@ -20,13 +20,13 @@ import { pluginServices } from '../../../services/plugin_services';
|
|||
import { useDashboardContainer } from '../../embeddable/dashboard_container';
|
||||
import { DashboardEmptyScreen } from '../empty_screen/dashboard_empty_screen';
|
||||
|
||||
export const useDebouncedWidthObserver = (wait = 250) => {
|
||||
export const useDebouncedWidthObserver = (skipDebounce = false, wait = 100) => {
|
||||
const [width, setWidth] = useState<number>(0);
|
||||
const onWidthChange = useMemo(() => debounce(setWidth, wait), [wait]);
|
||||
const { ref } = useResizeObserver<HTMLDivElement>({
|
||||
onResize: (dimensions) => {
|
||||
if (dimensions.width) {
|
||||
if (width === 0) setWidth(dimensions.width);
|
||||
if (width === 0 || skipDebounce) setWidth(dimensions.width);
|
||||
if (dimensions.width !== width) onWidthChange(dimensions.width);
|
||||
}
|
||||
},
|
||||
|
@ -58,10 +58,11 @@ export const DashboardViewportComponent = () => {
|
|||
const viewMode = dashboard.select((state) => state.explicitInput.viewMode);
|
||||
const dashboardTitle = dashboard.select((state) => state.explicitInput.title);
|
||||
const description = dashboard.select((state) => state.explicitInput.description);
|
||||
const focusedPanelId = dashboard.select((state) => state.componentState.focusedPanelId);
|
||||
const expandedPanelId = dashboard.select((state) => state.componentState.expandedPanelId);
|
||||
const controlsEnabled = isProjectEnabledInLabs('labs:dashboard:dashboardControls');
|
||||
|
||||
const { ref: resizeRef, width: viewportWidth } = useDebouncedWidthObserver();
|
||||
const { ref: resizeRef, width: viewportWidth } = useDebouncedWidthObserver(!!focusedPanelId);
|
||||
|
||||
const classes = classNames({
|
||||
dshDashboardViewport: true,
|
||||
|
|
|
@ -434,14 +434,18 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
|
|||
this.dispatch.setExpandedPanelId(newId);
|
||||
};
|
||||
|
||||
public openOverlay = (ref: OverlayRef) => {
|
||||
public openOverlay = (ref: OverlayRef, options?: { focusedPanelId?: string }) => {
|
||||
this.clearOverlays();
|
||||
this.dispatch.setHasOverlays(true);
|
||||
this.overlayRef = ref;
|
||||
if (options?.focusedPanelId) {
|
||||
this.setFocusedPanelId(options?.focusedPanelId);
|
||||
}
|
||||
};
|
||||
|
||||
public clearOverlays = () => {
|
||||
this.dispatch.setHasOverlays(false);
|
||||
this.dispatch.setFocusedPanelId(undefined);
|
||||
this.controlGroup?.closeAllFlyouts();
|
||||
this.overlayRef?.close();
|
||||
};
|
||||
|
@ -500,4 +504,8 @@ export class DashboardContainer extends Container<InheritedChildInput, Dashboard
|
|||
}
|
||||
this.setHighlightPanelId(undefined);
|
||||
};
|
||||
|
||||
public setFocusedPanelId = (id: string | undefined) => {
|
||||
this.dispatch.setFocusedPanelId(id);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -239,6 +239,9 @@ export const dashboardContainerReducers = {
|
|||
setHighlightPanelId: (state: DashboardReduxState, action: PayloadAction<string | undefined>) => {
|
||||
state.componentState.highlightPanelId = action.payload;
|
||||
},
|
||||
setFocusedPanelId: (state: DashboardReduxState, action: PayloadAction<string | undefined>) => {
|
||||
state.componentState.focusedPanelId = action.payload;
|
||||
},
|
||||
|
||||
setAnimatePanelTransforms: (
|
||||
state: DashboardReduxState,
|
||||
|
|
|
@ -43,6 +43,7 @@ export interface DashboardPublicState {
|
|||
managed?: boolean;
|
||||
scrollToPanelId?: string;
|
||||
highlightPanelId?: string;
|
||||
focusedPanelId?: string;
|
||||
}
|
||||
|
||||
export interface DashboardRenderPerformanceStats {
|
||||
|
|
|
@ -8,8 +8,12 @@
|
|||
|
||||
import { OverlayRef } from '@kbn/core-mount-utils-browser';
|
||||
|
||||
interface TracksOverlaysOptions {
|
||||
focusedPanelId?: string;
|
||||
}
|
||||
|
||||
interface TracksOverlays {
|
||||
openOverlay: (ref: OverlayRef) => void;
|
||||
openOverlay: (ref: OverlayRef, options?: TracksOverlaysOptions) => void;
|
||||
clearOverlays: () => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ import { uiActions } from '../../kibana_services';
|
|||
import { EmbeddablePanelProps, PanelUniversalActions } from '../types';
|
||||
import { getContextMenuAriaLabel } from '../embeddable_panel_strings';
|
||||
import { useSelectFromEmbeddableInput } from '../use_select_from_embeddable';
|
||||
import { IEmbeddable, contextMenuTrigger, CONTEXT_MENU_TRIGGER, ViewMode } from '../..';
|
||||
import { IEmbeddable, contextMenuTrigger, CONTEXT_MENU_TRIGGER } from '../..';
|
||||
|
||||
const sortByOrderField = (
|
||||
{ order: orderA }: { order?: number },
|
||||
|
@ -56,7 +56,6 @@ export const EmbeddablePanelContextMenu = ({
|
|||
const [contextMenuPanels, setContextMenuPanels] = useState<EuiContextMenuPanelDescriptor[]>([]);
|
||||
|
||||
const title = useSelectFromEmbeddableInput('title', embeddable);
|
||||
const viewMode = useSelectFromEmbeddableInput('viewMode', embeddable);
|
||||
|
||||
useEffect(() => {
|
||||
/**
|
||||
|
@ -135,7 +134,7 @@ export const EmbeddablePanelContextMenu = ({
|
|||
data-test-subj="embeddablePanelToggleMenuIcon"
|
||||
aria-label={getContextMenuAriaLabel(title, index)}
|
||||
onClick={() => setIsContextMenuOpen((isOpen) => !isOpen)}
|
||||
iconType={viewMode === ViewMode.VIEW ? 'boxesHorizontal' : 'gear'}
|
||||
iconType={'boxesHorizontal'}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import './helpers.scss';
|
||||
import type { IEmbeddable } from '@kbn/embeddable-plugin/public';
|
||||
import type { OverlayRef, OverlayStart, ThemeServiceStart } from '@kbn/core/public';
|
||||
import { IEmbeddable, tracksOverlays } from '@kbn/embeddable-plugin/public';
|
||||
import type { OverlayStart, ThemeServiceStart } from '@kbn/core/public';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
import { isLensEmbeddable } from '../utils';
|
||||
|
@ -20,15 +20,6 @@ interface Context {
|
|||
theme: ThemeServiceStart;
|
||||
}
|
||||
|
||||
interface TracksOverlays {
|
||||
openOverlay: (ref: OverlayRef) => void;
|
||||
clearOverlays: () => void;
|
||||
}
|
||||
|
||||
function tracksOverlays(root: unknown): root is TracksOverlays {
|
||||
return Boolean((root as TracksOverlays).openOverlay && (root as TracksOverlays).clearOverlays);
|
||||
}
|
||||
|
||||
export async function isActionCompatible(embeddable: IEmbeddable) {
|
||||
return Boolean(isLensEmbeddable(embeddable) && embeddable.isTextBasedLanguage());
|
||||
}
|
||||
|
@ -67,6 +58,6 @@ export async function executeAction({ embeddable, startDependencies, overlays, t
|
|||
outsideClickCloses: true,
|
||||
}
|
||||
);
|
||||
overlayTracker?.openOverlay(handle);
|
||||
overlayTracker?.openOverlay(handle, { focusedPanelId: embeddable.id });
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue