Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Space✨Time] Canvas-like Dashboards #212450

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ac19ec4
Switch panel styles
Heenawter Feb 24, 2025
0cc7a79
Small cleanup
Heenawter Feb 24, 2025
0017b13
First attempt at freeform styling
Heenawter Feb 24, 2025
e1a252e
Add layout toggle
Heenawter Feb 25, 2025
3ed8b85
Handle conversion
Heenawter Feb 25, 2025
9d5460c
Working on adding sections
Heenawter Feb 25, 2025
b0dc2d6
Merge branch 'main' into spacetime_canvas-like-dashboards_2025-02-24
Heenawter Feb 25, 2025
7874767
Add collapsible sections
Heenawter Feb 26, 2025
bc3629f
Add sections to saved object
Heenawter Feb 26, 2025
78d7f10
Include sections in SO fetch
Heenawter Feb 26, 2025
60d2b17
Store section index
Heenawter Feb 26, 2025
d383f26
Allow move to section 0
Heenawter Feb 26, 2025
fe24614
Fix reset
Heenawter Feb 26, 2025
9a65b5c
Fix z-index issue
Heenawter Feb 26, 2025
f6938c4
Start working on pagination
Heenawter Feb 27, 2025
09abf20
Fix some bugs + styles
Heenawter Feb 27, 2025
5f481bf
Fix unsaved changes bug
Heenawter Feb 27, 2025
0fef277
Fix clone bug
Heenawter Feb 27, 2025
b06e71c
Fix add panel
Heenawter Feb 27, 2025
5d9e223
Better duplicate fix
Heenawter Feb 27, 2025
9206848
Fix incoming embeddable
Heenawter Feb 27, 2025
7d27cd8
Add + delete sections
Heenawter Feb 27, 2025
4f75b5b
Add view mode
Heenawter Feb 28, 2025
e948db4
Fix bug with grid -> freeform
Heenawter Feb 28, 2025
ee59695
fix bug with empty dashboard
Heenawter Feb 28, 2025
6cfdbb0
Add z-index
Heenawter Feb 28, 2025
2974476
More action fixes
Heenawter Feb 28, 2025
5a7c2c3
First attempt at rotate handle
Heenawter Feb 28, 2025
916bbf7
Add rotate handler
Heenawter Mar 1, 2025
70f90cf
Allow rotate - with infinite grow bug
Heenawter Mar 1, 2025
4c779a2
Fix rotation bug - resize bug still present
Heenawter Mar 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
342 changes: 62 additions & 280 deletions packages/kbn-check-mappings-update-cli/current_fields.json

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions packages/kbn-check-mappings-update-cli/current_mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,20 @@
"index": false,
"type": "text"
},
"sections": {
"properties": {
"title": {
"doc_values": false,
"type": "keyword",
"index": false
},
"collapsed": {
"doc_values": false,
"type": "boolean",
"index": false
}
}
},
"refreshInterval": {
"properties": {
"display": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,16 @@ export const GridHeightSmoother = React.memo(
* making the panel shrink or grow unpredictably.
*/
const interactionStyleSubscription = combineLatest([
gridLayoutStateManager.activeSection$,
gridLayoutStateManager.gridDimensions$,
gridLayoutStateManager.interactionEvent$,
]).subscribe(([dimensions, interactionEvent]) => {
]).subscribe(([activeSection, dimensions, interactionEvent]) => {
if (!smoothHeightRef.current || gridLayoutStateManager.expandedPanelId$.getValue()) return;

if (activeSection !== undefined) {
smoothHeightRef.current.style.minHeight = 'unset';
return;
}
if (!interactionEvent) {
smoothHeightRef.current.style.minHeight = `${dimensions.height}px`;
return;
Expand All @@ -53,6 +58,20 @@ export const GridHeightSmoother = React.memo(
css={[styles.heightSmoothing, styles.hasActivePanel]}
>
{children}
{/* <div
css={css({
position: 'fixed',
height: '100px',
width: '100%',
bottom: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 100000,
})}
>
<div>test</div>
</div> */}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,36 @@ import { resolveGridRow } from './utils/resolve_grid_row';

export type GridLayoutProps = {
layout: GridLayoutData;
section?: number;
gridSettings: GridSettings;
onLayoutChange: (newLayout: GridLayoutData) => void;
onSectionChange?: (section?: number) => void;
expandedPanelId?: string;
accessMode?: GridAccessMode;
className?: string; // this makes it so that custom CSS can be passed via Emotion
} & UseCustomDragHandle;

export const GridLayout = ({
layout,
section,
gridSettings,
renderPanelContents,
onLayoutChange,
expandedPanelId,
accessMode = 'EDIT',
className,
onSectionChange,
useCustomDragHandle = false,
}: GridLayoutProps) => {
console.log(layout);
const layoutRef = useRef<HTMLDivElement | null>(null);
const { gridLayoutStateManager, setDimensionsRef } = useGridLayoutState({
layout,
layoutRef,
gridSettings,
expandedPanelId,
accessMode,
section,
});

const [rowCount, setRowCount] = useState<number>(
Expand All @@ -64,9 +70,11 @@ export const GridLayout = ({
* the layout sent in as a prop is not guaranteed to be valid (i.e it may have floating panels) -
* so, we need to loop through each row and ensure it is compacted
*/
newLayout.forEach((row, rowIndex) => {
newLayout[rowIndex] = resolveGridRow(row);
});
if (gridLayoutStateManager.runtimeSettings$.getValue() !== 'none') {
newLayout.forEach((row, rowIndex) => {
newLayout[rowIndex] = resolveGridRow(row);
});
}
gridLayoutStateManager.gridLayout$.next(newLayout);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down Expand Up @@ -108,9 +116,16 @@ export const GridLayout = ({
const gridLayoutClassSubscription = combineLatest([
gridLayoutStateManager.accessMode$,
gridLayoutStateManager.isMobileView$,
]).subscribe(([currentAccessMode, isMobileView]) => {
gridLayoutStateManager.runtimeSettings$,
]).subscribe(([currentAccessMode, isMobileView, runtimeSettings]) => {
if (!layoutRef) return;

if (runtimeSettings === 'none') {
layoutRef.current?.classList.add('kbnGrid--freeform');
} else {
layoutRef.current?.classList.remove('kbnGrid--freeform');
}

if (isMobileView) {
layoutRef.current?.classList.add('kbnGrid--mobileView');
} else {
Expand All @@ -124,10 +139,21 @@ export const GridLayout = ({
}
});

const activeSectionSubscription = gridLayoutStateManager.activeSection$
.pipe(skip(1), distinctUntilChanged())
.subscribe((activeSection) => {
onSectionChange?.(
activeSection !== undefined
? Math.min(activeSection, gridLayoutStateManager.gridLayout$.getValue().length - 1)
: undefined
);
});

return () => {
rowCountSubscription.unsubscribe();
onLayoutChangeSubscription.unsubscribe();
gridLayoutClassSubscription.unsubscribe();
activeSectionSubscription.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import React, { useEffect, useMemo } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { combineLatest, skip } from 'rxjs';

import { useEuiTheme } from '@elastic/eui';
import { UseEuiTheme } from '@elastic/eui';
import { css } from '@emotion/react';

import { useGridLayoutContext } from '../use_grid_layout_context';
import { DefaultDragHandle } from './drag_handle/default_drag_handle';
import { useDragHandleApi } from './drag_handle/use_drag_handle_api';
import { ResizeHandle } from './resize_handle';
import { RotateHandle } from './rotate_handle';

export interface GridPanelProps {
panelId: string;
Expand All @@ -27,26 +28,22 @@ export const GridPanel = React.memo(({ panelId, rowIndex }: GridPanelProps) => {
const { gridLayoutStateManager, useCustomDragHandle, renderPanelContents } =
useGridLayoutContext();

const { euiTheme } = useEuiTheme();
const dragHandleApi = useDragHandleApi({ panelId, rowIndex });
const [freeform, setFreeform] = useState<boolean>(
gridLayoutStateManager.runtimeSettings$.getValue() === 'none'
);

/** Set initial styles based on state at mount to prevent styles from "blipping" */
const initialStyles = useMemo(() => {
const initialPanel = (gridLayoutStateManager.proposedGridLayout$.getValue() ??
gridLayoutStateManager.gridLayout$.getValue())[rowIndex].panels[panelId];
return css`
position: relative;
height: calc(
1px *
(
${initialPanel.height} * (var(--kbnGridRowHeight) + var(--kbnGridGutterSize)) -
var(--kbnGridGutterSize)
)
);
grid-column-start: ${initialPanel.column + 1};
grid-column-end: ${initialPanel.column + 1 + initialPanel.width};
grid-row-start: ${initialPanel.row + 1};
grid-row-end: ${initialPanel.row + 1 + initialPanel.height};
--kbnGridPanelHeight: ${initialPanel.height};
--kbnGridPanelWidth: ${initialPanel.width};
--kbnGridPanelX: ${initialPanel.column};
--kbnGridPanelY: ${initialPanel.row};
--kbnGridPanelZ: ${initialPanel.zIndex ?? 1};
--kbnGridPanelRotate: ${initialPanel.rotate ?? 0};
`;
}, [gridLayoutStateManager, rowIndex, panelId]);

Expand All @@ -73,53 +70,49 @@ export const GridPanel = React.memo(({ panelId, rowIndex }: GridPanelProps) => {
const { position: draggingPosition } = activePanel;
const runtimeSettings = gridLayoutStateManager.runtimeSettings$.getValue();

ref.style.zIndex = `${euiTheme.levels.modal}`;
if (currentInteractionEvent?.type === 'resize') {
if (currentInteractionEvent?.type === 'rotate') {
ref.style.setProperty('--kbnGridPanelRotate', `${panel.rotate ?? 0}`);
} else if (currentInteractionEvent?.type === 'resize') {
// if the current panel is being resized, ensure it is not shrunk past the size of a single cell
ref.style.width = `${Math.max(
draggingPosition.right - draggingPosition.left,
runtimeSettings.columnPixelWidth
)}px`;
ref.style.height = `${Math.max(
draggingPosition.bottom - draggingPosition.top,
runtimeSettings.rowHeight
)}px`;

// undo any "lock to grid" styles **except** for the top left corner, which stays locked
ref.style.gridColumnStart = `${panel.column + 1}`;
ref.style.gridRowStart = `${panel.row + 1}`;
ref.style.gridColumnEnd = `auto`;
ref.style.gridRowEnd = `auto`;
ref.classList.add('kbnGridPanel--resize');
ref.style.setProperty(
'--kbnGridPanelWidth',
`${Math.max(
draggingPosition.right - draggingPosition.left,
runtimeSettings === 'none' ? 20 : runtimeSettings.columnPixelWidth
)}`
);
ref.style.setProperty(
'--kbnGridPanelHeight',
`${Math.max(
draggingPosition.bottom - draggingPosition.top,
runtimeSettings === 'none' ? 20 : runtimeSettings.rowHeight
)}`
);
} else {
// if the current panel is being dragged, render it with a fixed position + size
ref.style.position = 'fixed';

ref.style.left = `${draggingPosition.left}px`;
ref.style.top = `${draggingPosition.top}px`;
ref.style.width = `${draggingPosition.right - draggingPosition.left}px`;
ref.style.height = `${draggingPosition.bottom - draggingPosition.top}px`;

// undo any "lock to grid" styles
ref.style.gridArea = `auto`; // shortcut to set all grid styles to `auto`
ref.classList.add('kbnGridPanel--drag');
ref.style.setProperty(
'--kbnGridPanelHeight',
`${draggingPosition.bottom - draggingPosition.top}`
);
ref.style.setProperty(
'--kbnGridPanelWidth',
`${draggingPosition.right - draggingPosition.left}`
);
ref.style.setProperty('--kbnGridPanelX', `${draggingPosition.left}`);
ref.style.setProperty('--kbnGridPanelY', `${draggingPosition.top}`);
}
} else {
ref.classList.remove('kbnGridPanel--active');

ref.style.zIndex = `auto`;

// if the panel is not being dragged and/or resized, undo any fixed position styles
ref.style.position = '';
ref.style.left = ``;
ref.style.top = ``;
ref.style.width = ``;
// setting the height is necessary for mobile mode
ref.style.height = `calc(1px * (${panel.height} * (var(--kbnGridRowHeight) + var(--kbnGridGutterSize)) - var(--kbnGridGutterSize)))`;

// and render the panel locked to the grid
ref.style.gridColumnStart = `${panel.column + 1}`;
ref.style.gridColumnEnd = `${panel.column + 1 + panel.width}`;
ref.style.gridRowStart = `${panel.row + 1}`;
ref.style.gridRowEnd = `${panel.row + 1 + panel.height}`;
ref.classList.remove('kbnGridPanel--resize');
ref.classList.remove('kbnGridPanel--drag');

ref.style.setProperty('--kbnGridPanelWidth', `${panel.width}`);
ref.style.setProperty('--kbnGridPanelHeight', `${panel.height}`);
ref.style.setProperty('--kbnGridPanelX', `${Math.max(0, panel.column)}`);
ref.style.setProperty('--kbnGridPanelY', `${Math.max(0, panel.row)}`);
ref.style.setProperty('--kbnGridPanelZ', `${panel.zIndex ?? 1}`);
ref.style.setProperty('--kbnGridPanelRotate', `${panel.rotate ?? 0}`);
}
});

Expand Down Expand Up @@ -165,14 +158,67 @@ export const GridPanel = React.memo(({ panelId, rowIndex }: GridPanelProps) => {
}
gridLayoutStateManager.panelRefs.current[rowIndex][panelId] = element;
}}
css={initialStyles}
css={[initialStyles, panelStyles]}
className="kbnGridPanel"
>
{!useCustomDragHandle && <DefaultDragHandle dragHandleApi={dragHandleApi} />}
{panelContents}
<ResizeHandle panelId={panelId} rowIndex={rowIndex} />
{freeform && <RotateHandle panelId={panelId} rowIndex={rowIndex} />}
</div>
);
});

GridPanel.displayName = 'KbnGridLayoutPanel';

const panelStyles = ({ euiTheme }: UseEuiTheme) =>
// @ts-ignore We are using a variable to set the z-index and it hates it
css({
position: 'relative',
height: `calc(
1px *
(
var(--kbnGridPanelHeight) * (var(--kbnGridRowHeight) + var(--kbnGridGutterSize)) -
var(--kbnGridGutterSize)
)
)`,
zIndex: 'auto',
gridColumnStart: `calc(var(--kbnGridPanelX) + 1)`,
gridColumnEnd: `calc(var(--kbnGridPanelX) + 1 + var(--kbnGridPanelWidth))`,
gridRowStart: `calc(var(--kbnGridPanelY) + 1)`,
gridRowEnd: `calc(var(--kbnGridPanelY) + 1 + var(--kbnGridPanelHeight))`,
'.kbnGrid--freeform &': {
position: 'absolute',
zIndex: 'var(--kbnGridPanelZ)',
gridArea: 'auto', // shortcut to set all grid styles to `auto`
left: 'calc(var(--kbnGridPanelX) * 1px)',
top: 'calc(var(--kbnGridPanelY) * 1px)',
width: `calc(1px * var(--kbnGridPanelWidth)) `,
height: `calc(1px * var(--kbnGridPanelHeight)) `,
transform: `rotate(calc(var(--kbnGridPanelRotate) * 1deg))`,
transformOrigin: `50% -16px`,
'.kbnGridPanel--rotateHandle': {
opacity: '0',
},
'&:hover': {
'.kbnGridPanel--rotateHandle': {
opacity: '1',
},
},
},
'&.kbnGridPanel--active': {
zIndex: euiTheme.levels.modal,
width: `calc(1px * var(--kbnGridPanelWidth)) `,
height: `calc(1px * var(--kbnGridPanelHeight)) `,
},
'&.kbnGridPanel--resize': {
gridColumnEnd: `auto !important`,
gridRowEnd: `auto !important`,
},
'&.kbnGridPanel--drag': {
position: 'fixed',
gridArea: 'auto', // shortcut to set all grid styles to `auto`
left: 'calc(var(--kbnGridPanelX) * 1px)',
top: 'calc(var(--kbnGridPanelY) * 1px)',
},
});
Loading