mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[kbn-grid-layout] Add ability to create, edit, and delete rows (#209193)
Closes https://github.com/elastic/kibana/issues/204849 ## Summary This PR adds the ability to create, edit, and delete sections / rows to `kbn-grid-layout`: https://github.com/user-attachments/assets/4831b289-2c71-42fb-851d-0925560e233a Note that sections are still statically placed - dragging rows around will be added in a follow-up PR, because it's a larger undertaking. Since this feature is not available to users yet, it is okay to implement this in stages like this. ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Identify risks Collapsible sections are not available on Dashboard yet and so there is no user-facing risk to this PR. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Marta Bondyra <4283304+mbondyra@users.noreply.github.com>
This commit is contained in:
parent
4a8928d5d4
commit
e587187ffc
17 changed files with 1004 additions and 218 deletions
|
@ -10,11 +10,22 @@
|
|||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
|
||||
import { ADD_PANEL_TRIGGER, UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
import {
|
||||
PublishingSubject,
|
||||
ViewMode,
|
||||
apiPublishesViewMode,
|
||||
useStateFromPublishingSubject,
|
||||
} from '@kbn/presentation-publishing';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
export function AddButton({ pageApi, uiActions }: { pageApi: unknown; uiActions: UiActionsStart }) {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
const [items, setItems] = useState<ReactElement[]>([]);
|
||||
|
||||
const viewMode = useStateFromPublishingSubject(
|
||||
apiPublishesViewMode(pageApi) ? pageApi?.viewMode$ : (of('edit') as PublishingSubject<ViewMode>)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
|
@ -59,6 +70,7 @@ export function AddButton({ pageApi, uiActions }: { pageApi: unknown; uiActions:
|
|||
onClick={() => {
|
||||
setIsPopoverOpen(!isPopoverOpen);
|
||||
}}
|
||||
disabled={viewMode !== 'edit'}
|
||||
>
|
||||
Add panel
|
||||
</EuiButton>
|
||||
|
|
|
@ -10,20 +10,16 @@
|
|||
import deepEqual from 'fast-deep-equal';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { combineLatest, debounceTime } from 'rxjs';
|
||||
import { Subject, combineLatest, debounceTime, map, skip, take } from 'rxjs';
|
||||
|
||||
import {
|
||||
EuiBadge,
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiButtonGroup,
|
||||
EuiCallOut,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiPageTemplate,
|
||||
EuiPopover,
|
||||
EuiRange,
|
||||
EuiSpacer,
|
||||
transparentize,
|
||||
useEuiTheme,
|
||||
|
@ -33,12 +29,13 @@ import { AppMountParameters } from '@kbn/core-application-browser';
|
|||
import { CoreStart } from '@kbn/core-lifecycle-browser';
|
||||
import { AddEmbeddableButton } from '@kbn/embeddable-examples-plugin/public';
|
||||
import { ReactEmbeddableRenderer } from '@kbn/embeddable-plugin/public';
|
||||
import { GridLayout, GridLayoutData } from '@kbn/grid-layout';
|
||||
import { GridLayout, GridLayoutData, GridSettings } from '@kbn/grid-layout';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useBatchedPublishingSubjects } from '@kbn/presentation-publishing';
|
||||
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
|
||||
import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
||||
|
||||
import { GridLayoutOptions } from './grid_layout_options';
|
||||
import {
|
||||
clearSerializedDashboardState,
|
||||
getSerializedDashboardState,
|
||||
|
@ -66,39 +63,54 @@ export const GridExample = ({
|
|||
const [currentLayout, setCurrentLayout] = useState<GridLayoutData>(
|
||||
dashboardInputToGridLayout(savedState.current)
|
||||
);
|
||||
const [isSettingsPopoverOpen, setIsSettingsPopoverOpen] = useState(false);
|
||||
const [gutterSize, setGutterSize] = useState<number>(DASHBOARD_MARGIN_SIZE);
|
||||
const [rowHeight, setRowHeight] = useState<number>(DASHBOARD_GRID_HEIGHT);
|
||||
const [gridSettings, setGridSettings] = useState<GridSettings>({
|
||||
gutterSize: DASHBOARD_MARGIN_SIZE,
|
||||
rowHeight: DASHBOARD_GRID_HEIGHT,
|
||||
columnCount: DASHBOARD_GRID_COLUMN_COUNT,
|
||||
});
|
||||
|
||||
const mockDashboardApi = useMockDashboardApi({ savedState: savedState.current });
|
||||
const [viewMode, expandedPanelId] = useBatchedPublishingSubjects(
|
||||
mockDashboardApi.viewMode$,
|
||||
mockDashboardApi.expandedPanelId$
|
||||
);
|
||||
const layoutUpdated$ = useMemo(() => new Subject<void>(), []);
|
||||
|
||||
useEffect(() => {
|
||||
combineLatest([mockDashboardApi.panels$, mockDashboardApi.rows$])
|
||||
.pipe(debounceTime(0)) // debounce to avoid subscribe being called twice when both panels$ and rows$ publish
|
||||
.subscribe(([panels, rows]) => {
|
||||
const panelIds = Object.keys(panels);
|
||||
let panelsAreEqual = true;
|
||||
for (const panelId of panelIds) {
|
||||
if (!panelsAreEqual) break;
|
||||
const currentPanel = panels[panelId];
|
||||
const savedPanel = savedState.current.panels[panelId];
|
||||
panelsAreEqual = deepEqual(
|
||||
{ row: 0, ...currentPanel.gridData },
|
||||
{ row: 0, ...savedPanel.gridData }
|
||||
);
|
||||
}
|
||||
|
||||
const hasChanges = !(panelsAreEqual && deepEqual(rows, savedState.current.rows));
|
||||
.pipe(
|
||||
debounceTime(0), // debounce to avoid subscribe being called twice when both panels$ and rows$ publish
|
||||
map(([panels, rows]) => {
|
||||
const panelIds = Object.keys(panels);
|
||||
let panelsAreEqual = true;
|
||||
for (const panelId of panelIds) {
|
||||
if (!panelsAreEqual) break;
|
||||
const currentPanel = panels[panelId];
|
||||
const savedPanel = savedState.current.panels[panelId];
|
||||
panelsAreEqual = deepEqual(
|
||||
{ row: 0, ...currentPanel.gridData },
|
||||
{ row: 0, ...savedPanel.gridData }
|
||||
);
|
||||
}
|
||||
const hasChanges = !(panelsAreEqual && deepEqual(rows, savedState.current.rows));
|
||||
return { hasChanges, updatedLayout: dashboardInputToGridLayout({ panels, rows }) };
|
||||
})
|
||||
)
|
||||
.subscribe(({ hasChanges, updatedLayout }) => {
|
||||
setHasUnsavedChanges(hasChanges);
|
||||
setCurrentLayout(dashboardInputToGridLayout({ panels, rows }));
|
||||
setCurrentLayout(updatedLayout);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* On layout update, emit `layoutUpdated$` so that side effects to layout updates can
|
||||
* happen (such as scrolling to the bottom of the screen after adding a new section)
|
||||
*/
|
||||
useEffect(() => {
|
||||
layoutUpdated$.next();
|
||||
}, [currentLayout, layoutUpdated$]);
|
||||
|
||||
const renderPanelContents = useCallback(
|
||||
(id: string, setDragHandles?: (refs: Array<HTMLElement | null>) => void) => {
|
||||
const currentPanels = mockDashboardApi.panels$.getValue();
|
||||
|
@ -122,6 +134,41 @@ export const GridExample = ({
|
|||
[mockDashboardApi]
|
||||
);
|
||||
|
||||
const onLayoutChange = useCallback(
|
||||
(newLayout: GridLayoutData) => {
|
||||
const { panels, rows } = gridLayoutToDashboardPanelMap(
|
||||
mockDashboardApi.panels$.getValue(),
|
||||
newLayout
|
||||
);
|
||||
mockDashboardApi.panels$.next(panels);
|
||||
mockDashboardApi.rows$.next(rows);
|
||||
},
|
||||
[mockDashboardApi.panels$, mockDashboardApi.rows$]
|
||||
);
|
||||
|
||||
const addNewSection = useCallback(() => {
|
||||
mockDashboardApi.rows$.next([
|
||||
...mockDashboardApi.rows$.getValue(),
|
||||
{
|
||||
title: i18n.translate('examples.gridExample.defaultSectionTitle', {
|
||||
defaultMessage: 'New collapsible section',
|
||||
}),
|
||||
collapsed: false,
|
||||
},
|
||||
]);
|
||||
|
||||
// scroll to bottom after row is added
|
||||
layoutUpdated$.pipe(skip(1), take(1)).subscribe(() => {
|
||||
window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
|
||||
});
|
||||
}, [mockDashboardApi.rows$, layoutUpdated$]);
|
||||
|
||||
const resetUnsavedChanges = useCallback(() => {
|
||||
const { panels, rows } = savedState.current;
|
||||
mockDashboardApi.panels$.next(panels);
|
||||
mockDashboardApi.rows$.next(rows);
|
||||
}, [mockDashboardApi.panels$, mockDashboardApi.rows$]);
|
||||
|
||||
const customLayoutCss = useMemo(() => {
|
||||
const gridColor = transparentize(euiTheme.colors.backgroundFilledAccentSecondary, 0.2);
|
||||
return css`
|
||||
|
@ -196,89 +243,22 @@ export const GridExample = ({
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AddEmbeddableButton pageApi={mockDashboardApi} uiActions={uiActions} />{' '}
|
||||
<AddEmbeddableButton pageApi={mockDashboardApi} uiActions={uiActions} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButton
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
onClick={() => setIsSettingsPopoverOpen(!isSettingsPopoverOpen)}
|
||||
>
|
||||
{i18n.translate('examples.gridExample.settingsPopover.title', {
|
||||
defaultMessage: 'Layout settings',
|
||||
})}
|
||||
</EuiButton>
|
||||
}
|
||||
isOpen={isSettingsPopoverOpen}
|
||||
closePopover={() => setIsSettingsPopoverOpen(false)}
|
||||
>
|
||||
<>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('examples.gridExample.settingsPopover.viewMode', {
|
||||
defaultMessage: 'View mode',
|
||||
})}
|
||||
>
|
||||
<EuiButtonGroup
|
||||
legend={i18n.translate('examples.gridExample.layoutOptionsLegend', {
|
||||
defaultMessage: 'Layout options',
|
||||
})}
|
||||
options={[
|
||||
{
|
||||
id: 'view',
|
||||
label: i18n.translate('examples.gridExample.viewOption', {
|
||||
defaultMessage: 'View',
|
||||
}),
|
||||
toolTipContent:
|
||||
'The layout adjusts when the window is resized. Panel interactivity, such as moving and resizing within the grid, is disabled.',
|
||||
},
|
||||
{
|
||||
id: 'edit',
|
||||
label: i18n.translate('examples.gridExample.editOption', {
|
||||
defaultMessage: 'Edit',
|
||||
}),
|
||||
toolTipContent:
|
||||
'The layout does not adjust when the window is resized.',
|
||||
},
|
||||
]}
|
||||
idSelected={viewMode}
|
||||
onChange={(id) => {
|
||||
mockDashboardApi.viewMode$.next(id);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('examples.gridExample.settingsPopover.gutterSize', {
|
||||
defaultMessage: 'Gutter size',
|
||||
})}
|
||||
>
|
||||
<EuiRange
|
||||
min={1}
|
||||
max={30}
|
||||
value={gutterSize}
|
||||
onChange={(e) => setGutterSize(parseInt(e.currentTarget.value, 10))}
|
||||
showLabels
|
||||
showValue
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('examples.gridExample.settingsPopover.rowHeight', {
|
||||
defaultMessage: 'Row height',
|
||||
})}
|
||||
>
|
||||
<EuiRange
|
||||
min={5}
|
||||
max={30}
|
||||
step={5}
|
||||
value={rowHeight}
|
||||
onChange={(e) => setRowHeight(parseInt(e.currentTarget.value, 10))}
|
||||
showLabels
|
||||
showValue
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
</EuiPopover>
|
||||
<EuiButton onClick={addNewSection} disabled={viewMode !== 'edit'}>
|
||||
{i18n.translate('examples.gridExample.addRowButton', {
|
||||
defaultMessage: 'Add collapsible section',
|
||||
})}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<GridLayoutOptions
|
||||
mockDashboardApi={mockDashboardApi}
|
||||
gridSettings={gridSettings}
|
||||
setGridSettings={setGridSettings}
|
||||
viewMode={viewMode}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
@ -294,13 +274,7 @@ export const GridExample = ({
|
|||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
onClick={() => {
|
||||
const { panels, rows } = savedState.current;
|
||||
mockDashboardApi.panels$.next(panels);
|
||||
mockDashboardApi.rows$.next(rows);
|
||||
}}
|
||||
>
|
||||
<EuiButtonEmpty onClick={resetUnsavedChanges}>
|
||||
{i18n.translate('examples.gridExample.resetLayoutButton', {
|
||||
defaultMessage: 'Reset',
|
||||
})}
|
||||
|
@ -332,21 +306,10 @@ export const GridExample = ({
|
|||
accessMode={viewMode === 'view' ? 'VIEW' : 'EDIT'}
|
||||
expandedPanelId={expandedPanelId}
|
||||
layout={currentLayout}
|
||||
gridSettings={{
|
||||
gutterSize,
|
||||
rowHeight,
|
||||
columnCount: DASHBOARD_GRID_COLUMN_COUNT,
|
||||
}}
|
||||
gridSettings={gridSettings}
|
||||
useCustomDragHandle={true}
|
||||
renderPanelContents={renderPanelContents}
|
||||
onLayoutChange={(newLayout) => {
|
||||
const { panels, rows } = gridLayoutToDashboardPanelMap(
|
||||
mockDashboardApi.panels$.getValue(),
|
||||
newLayout
|
||||
);
|
||||
mockDashboardApi.panels$.next(panels);
|
||||
mockDashboardApi.rows$.next(rows);
|
||||
}}
|
||||
onLayoutChange={onLayoutChange}
|
||||
css={customLayoutCss}
|
||||
/>
|
||||
</EuiPageTemplate.Section>
|
||||
|
|
116
examples/grid_example/public/grid_layout_options.tsx
Normal file
116
examples/grid_example/public/grid_layout_options.tsx
Normal file
|
@ -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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { EuiButton, EuiButtonGroup, EuiFormRow, EuiPopover, EuiRange } from '@elastic/eui';
|
||||
import { GridSettings } from '@kbn/grid-layout';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ViewMode } from '@kbn/presentation-publishing';
|
||||
import { MockDashboardApi } from './types';
|
||||
|
||||
export const GridLayoutOptions = ({
|
||||
viewMode,
|
||||
mockDashboardApi,
|
||||
gridSettings,
|
||||
setGridSettings,
|
||||
}: {
|
||||
viewMode: ViewMode;
|
||||
mockDashboardApi: MockDashboardApi;
|
||||
gridSettings: GridSettings;
|
||||
setGridSettings: (settings: GridSettings) => void;
|
||||
}) => {
|
||||
const [isSettingsPopoverOpen, setIsSettingsPopoverOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButton
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
onClick={() => setIsSettingsPopoverOpen(!isSettingsPopoverOpen)}
|
||||
>
|
||||
{i18n.translate('examples.gridExample.settingsPopover.title', {
|
||||
defaultMessage: 'Layout settings',
|
||||
})}
|
||||
</EuiButton>
|
||||
}
|
||||
isOpen={isSettingsPopoverOpen}
|
||||
closePopover={() => setIsSettingsPopoverOpen(false)}
|
||||
>
|
||||
<>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('examples.gridExample.settingsPopover.viewMode', {
|
||||
defaultMessage: 'View mode',
|
||||
})}
|
||||
>
|
||||
<EuiButtonGroup
|
||||
legend={i18n.translate('examples.gridExample.layoutOptionsLegend', {
|
||||
defaultMessage: 'Layout options',
|
||||
})}
|
||||
options={[
|
||||
{
|
||||
id: 'view',
|
||||
label: i18n.translate('examples.gridExample.viewOption', {
|
||||
defaultMessage: 'View',
|
||||
}),
|
||||
toolTipContent:
|
||||
'The layout adjusts when the window is resized. Panel interactivity, such as moving and resizing within the grid, is disabled.',
|
||||
},
|
||||
{
|
||||
id: 'edit',
|
||||
label: i18n.translate('examples.gridExample.editOption', {
|
||||
defaultMessage: 'Edit',
|
||||
}),
|
||||
toolTipContent: 'The layout does not adjust when the window is resized.',
|
||||
},
|
||||
]}
|
||||
idSelected={viewMode}
|
||||
onChange={(id) => {
|
||||
mockDashboardApi.setViewMode(id as ViewMode);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('examples.gridExample.settingsPopover.gutterSize', {
|
||||
defaultMessage: 'Gutter size',
|
||||
})}
|
||||
>
|
||||
<EuiRange
|
||||
min={1}
|
||||
max={30}
|
||||
value={gridSettings.gutterSize}
|
||||
onChange={(e) =>
|
||||
setGridSettings({ ...gridSettings, gutterSize: parseInt(e.currentTarget.value, 10) })
|
||||
}
|
||||
showLabels
|
||||
showValue
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={i18n.translate('examples.gridExample.settingsPopover.rowHeight', {
|
||||
defaultMessage: 'Row height',
|
||||
})}
|
||||
>
|
||||
<EuiRange
|
||||
min={5}
|
||||
max={30}
|
||||
step={5}
|
||||
value={gridSettings.rowHeight}
|
||||
onChange={(e) =>
|
||||
setGridSettings({ ...gridSettings, rowHeight: parseInt(e.currentTarget.value, 10) })
|
||||
}
|
||||
showLabels
|
||||
showValue
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
|
@ -7,6 +7,15 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import {
|
||||
CanAddNewPanel,
|
||||
CanExpandPanels,
|
||||
HasSerializedChildState,
|
||||
PresentationContainer,
|
||||
} from '@kbn/presentation-containers';
|
||||
import { PublishesWritableViewMode } from '@kbn/presentation-publishing';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
export interface DashboardGridData {
|
||||
w: number;
|
||||
h: number;
|
||||
|
@ -32,3 +41,12 @@ export interface MockSerializedDashboardState {
|
|||
panels: MockedDashboardPanelMap;
|
||||
rows: MockedDashboardRowMap;
|
||||
}
|
||||
|
||||
export type MockDashboardApi = PresentationContainer &
|
||||
CanAddNewPanel &
|
||||
HasSerializedChildState &
|
||||
PublishesWritableViewMode &
|
||||
CanExpandPanels & {
|
||||
panels$: BehaviorSubject<MockedDashboardPanelMap>;
|
||||
rows$: BehaviorSubject<MockedDashboardRowMap>;
|
||||
};
|
||||
|
|
|
@ -15,7 +15,9 @@ import { v4 } from 'uuid';
|
|||
import { TimeRange } from '@kbn/es-query';
|
||||
import { PanelPackage } from '@kbn/presentation-containers';
|
||||
|
||||
import { ViewMode } from '@kbn/presentation-publishing';
|
||||
import {
|
||||
MockDashboardApi,
|
||||
MockSerializedDashboardState,
|
||||
MockedDashboardPanelMap,
|
||||
MockedDashboardRowMap,
|
||||
|
@ -29,10 +31,11 @@ export const useMockDashboardApi = ({
|
|||
savedState,
|
||||
}: {
|
||||
savedState: MockSerializedDashboardState;
|
||||
}) => {
|
||||
}): MockDashboardApi => {
|
||||
const mockDashboardApi = useMemo(() => {
|
||||
const panels$ = new BehaviorSubject<MockedDashboardPanelMap>(savedState.panels);
|
||||
const expandedPanelId$ = new BehaviorSubject<string | undefined>(undefined);
|
||||
const viewMode$ = new BehaviorSubject<ViewMode>('edit');
|
||||
|
||||
return {
|
||||
getSerializedStateForChild: (id: string) => {
|
||||
|
@ -48,8 +51,12 @@ export const useMockDashboardApi = ({
|
|||
}),
|
||||
filters$: new BehaviorSubject([]),
|
||||
query$: new BehaviorSubject(''),
|
||||
viewMode$: new BehaviorSubject('edit'),
|
||||
viewMode$,
|
||||
setViewMode: (viewMode: ViewMode) => viewMode$.next(viewMode),
|
||||
panels$,
|
||||
getPanelCount: () => {
|
||||
return Object.keys(panels$.getValue()).length;
|
||||
},
|
||||
rows$: new BehaviorSubject<MockedDashboardRowMap>(savedState.rows),
|
||||
expandedPanelId$,
|
||||
expandPanel: (id: string) => {
|
||||
|
@ -64,7 +71,7 @@ export const useMockDashboardApi = ({
|
|||
delete panels[id]; // the grid layout component will handle compacting, if necessary
|
||||
mockDashboardApi.panels$.next(panels);
|
||||
},
|
||||
replacePanel: (id: string, newPanel: PanelPackage) => {
|
||||
replacePanel: async (id: string, newPanel: PanelPackage): Promise<string> => {
|
||||
const currentPanels = mockDashboardApi.panels$.getValue();
|
||||
const otherPanels = { ...currentPanels };
|
||||
const oldPanel = currentPanels[id];
|
||||
|
@ -75,8 +82,9 @@ export const useMockDashboardApi = ({
|
|||
explicitInput: { ...newPanel.initialState, id: newId },
|
||||
};
|
||||
mockDashboardApi.panels$.next(otherPanels);
|
||||
return newId;
|
||||
},
|
||||
addNewPanel: async (panelPackage: PanelPackage) => {
|
||||
addNewPanel: async (panelPackage: PanelPackage): Promise<undefined> => {
|
||||
// we are only implementing "place at top" here, for demo purposes
|
||||
const currentPanels = mockDashboardApi.panels$.getValue();
|
||||
const otherPanels = { ...currentPanels };
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue