/* * 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 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 { EuiBadge, EuiButton, EuiButtonEmpty, EuiButtonGroup, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiPageTemplate, EuiPopover, EuiRange, EuiSpacer, transparentize, useEuiTheme, } from '@elastic/eui'; import { css } from '@emotion/react'; 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 { 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 { clearSerializedDashboardState, getSerializedDashboardState, setSerializedGridLayout, } from './serialized_grid_layout'; import { MockSerializedDashboardState } from './types'; import { useMockDashboardApi } from './use_mock_dashboard_api'; import { dashboardInputToGridLayout, gridLayoutToDashboardPanelMap } from './utils'; const DASHBOARD_MARGIN_SIZE = 8; const DASHBOARD_GRID_HEIGHT = 20; const DASHBOARD_GRID_COLUMN_COUNT = 48; export const GridExample = ({ coreStart, uiActions, }: { coreStart: CoreStart; uiActions: UiActionsStart; }) => { const { euiTheme } = useEuiTheme(); const savedState = useRef(getSerializedDashboardState()); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [currentLayout, setCurrentLayout] = useState( dashboardInputToGridLayout(savedState.current) ); const [isSettingsPopoverOpen, setIsSettingsPopoverOpen] = useState(false); const [gutterSize, setGutterSize] = useState(DASHBOARD_MARGIN_SIZE); const [rowHeight, setRowHeight] = useState(DASHBOARD_GRID_HEIGHT); const mockDashboardApi = useMockDashboardApi({ savedState: savedState.current }); const [viewMode, expandedPanelId] = useBatchedPublishingSubjects( mockDashboardApi.viewMode$, mockDashboardApi.expandedPanelId$ ); 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)); setHasUnsavedChanges(hasChanges); setCurrentLayout(dashboardInputToGridLayout({ panels, rows })); }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const renderPanelContents = useCallback( (id: string, setDragHandles?: (refs: Array) => void) => { const currentPanels = mockDashboardApi.panels$.getValue(); return ( mockDashboardApi} panelProps={{ showBadges: true, showBorder: true, showNotifications: true, showShadow: false, setDragHandles, }} /> ); }, [mockDashboardApi] ); const customLayoutCss = useMemo(() => { const gridColor = transparentize(euiTheme.colors.backgroundFilledAccentSecondary, 0.2); return css` .kbnGridRow--targeted { background-position: top calc((var(--kbnGridGutterSize) / 2) * -1px) left calc((var(--kbnGridGutterSize) / 2) * -1px); background-size: calc((var(--kbnGridColumnWidth) + var(--kbnGridGutterSize)) * 1px) calc((var(--kbnGridRowHeight) + var(--kbnGridGutterSize)) * 1px); background-image: linear-gradient(to right, ${gridColor} 1px, transparent 1px), linear-gradient(to bottom, ${gridColor} 1px, transparent 1px); background-color: ${transparentize(euiTheme.colors.backgroundFilledAccentSecondary, 0.1)}; } .kbnGridPanel--dragPreview { border-radius: ${euiTheme.border.radius}; background-color: ${transparentize(euiTheme.colors.backgroundFilledAccentSecondary, 0.2)}; transition: opacity 100ms linear; } .kbnGridPanel--resizeHandle { opacity: 0; transition: opacity 0.2s, border 0.2s; border-radius: 7px 0 7px 0; border-bottom: 2px solid ${euiTheme.colors.accentSecondary}; border-right: 2px solid ${euiTheme.colors.accentSecondary}; &:hover, &:focus { outline-style: none !important; opacity: 1; background-color: ${transparentize(euiTheme.colors.accentSecondary, 0.05)}; } } `; }, [euiTheme]); return ( { clearSerializedDashboardState(); window.location.reload(); }} > {i18n.translate('examples.gridExample.resetExampleButton', { defaultMessage: 'Reset example', })} {' '} setIsSettingsPopoverOpen(!isSettingsPopoverOpen)} > {i18n.translate('examples.gridExample.settingsPopover.title', { defaultMessage: 'Layout settings', })} } isOpen={isSettingsPopoverOpen} closePopover={() => setIsSettingsPopoverOpen(false)} > <> { mockDashboardApi.viewMode$.next(id); }} /> setGutterSize(parseInt(e.currentTarget.value, 10))} showLabels showValue /> setRowHeight(parseInt(e.currentTarget.value, 10))} showLabels showValue /> {hasUnsavedChanges && ( {i18n.translate('examples.gridExample.unsavedChangesBadge', { defaultMessage: 'Unsaved changes', })} )} { const { panels, rows } = savedState.current; mockDashboardApi.panels$.next(panels); mockDashboardApi.rows$.next(rows); }} > {i18n.translate('examples.gridExample.resetLayoutButton', { defaultMessage: 'Reset', })} { const newSavedState = { panels: mockDashboardApi.panels$.getValue(), rows: mockDashboardApi.rows$.getValue(), }; savedState.current = newSavedState; setHasUnsavedChanges(false); setSerializedGridLayout(newSavedState); }} > {i18n.translate('examples.gridExample.saveLayoutButton', { defaultMessage: 'Save', })} { const { panels, rows } = gridLayoutToDashboardPanelMap( mockDashboardApi.panels$.getValue(), newLayout ); mockDashboardApi.panels$.next(panels); mockDashboardApi.rows$.next(rows); }} css={customLayoutCss} /> ); }; export const renderGridExampleApp = ( element: AppMountParameters['element'], deps: { uiActions: UiActionsStart; coreStart: CoreStart } ) => { ReactDOM.render(, element); return () => ReactDOM.unmountComponentAtNode(element); };