Skip to content

Commit

Permalink
First step of POC
Browse files Browse the repository at this point in the history
  • Loading branch information
Heenawter committed Nov 21, 2024
1 parent 1e3e247 commit e804b83
Show file tree
Hide file tree
Showing 16 changed files with 415 additions and 206 deletions.
22 changes: 19 additions & 3 deletions packages/kbn-grid-layout/grid/grid_height_smoother.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import { css } from '@emotion/react';
import React, { PropsWithChildren, useEffect, useRef } from 'react';
import { combineLatest } from 'rxjs';
import { combineLatest, distinctUntilChanged, map } from 'rxjs';
import { GridLayoutStateManager } from './types';

export const GridHeightSmoother = ({
Expand All @@ -19,13 +19,14 @@ export const GridHeightSmoother = ({
// set the parent div size directly to smooth out height changes.
const smoothHeightRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const subscription = combineLatest([
const heightSubscription = combineLatest([
gridLayoutStateManager.gridDimensions$,
gridLayoutStateManager.interactionEvent$,
]).subscribe(([dimensions, interactionEvent]) => {
if (!smoothHeightRef.current) return;
if (!interactionEvent) {
smoothHeightRef.current.style.height = `${dimensions.height}px`;
smoothHeightRef.current.style.userSelect = 'auto';
return;
}

Expand All @@ -38,8 +39,23 @@ export const GridHeightSmoother = ({
dimensions.height ?? 0,
smoothHeightRef.current.getBoundingClientRect().height
)}px`;
smoothHeightRef.current.style.userSelect = 'none';
});
return () => subscription.unsubscribe();

const marginSubscription = gridLayoutStateManager.runtimeSettings$
.pipe(
map(({ gutterSize }) => gutterSize),
distinctUntilChanged()
)
.subscribe((gutterSize) => {
if (!smoothHeightRef.current) return;
smoothHeightRef.current.style.margin = `${gutterSize}px`;
});

return () => {
marginSubscription.unsubscribe();
heightSubscription.unsubscribe();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Expand Down
5 changes: 4 additions & 1 deletion packages/kbn-grid-layout/grid/grid_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ import { compactGridRow } from './utils/resolve_grid_row';
interface GridLayoutProps {
layout: GridLayoutData;
gridSettings: GridSettings;
renderPanelContents: (panelId: string) => React.ReactNode;
renderPanelContents: (
panelId: string,
setDragHandles: (refs: Array<HTMLElement | null>) => void
) => React.ReactNode;
onLayoutChange: (newLayout: GridLayoutData) => void;
}

Expand Down
213 changes: 149 additions & 64 deletions packages/kbn-grid-layout/grid/grid_panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

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

import {
Expand All @@ -28,10 +28,14 @@ export const GridPanel = forwardRef<
{
panelId: string;
rowIndex: number;
renderPanelContents: (panelId: string) => React.ReactNode;
renderPanelContents: (
panelId: string,
setDragHandles: (refs: Array<HTMLElement | null>) => void
) => React.ReactNode;
interactionStart: (
panelId: string,
type: PanelInteractionEvent['type'] | 'drop',
e: React.MouseEvent<HTMLDivElement, MouseEvent>
e: MouseEvent | React.MouseEvent<HTMLDivElement, MouseEvent>
) => void;
gridLayoutStateManager: GridLayoutStateManager;
}
Expand All @@ -42,6 +46,9 @@ export const GridPanel = forwardRef<
) => {
const { euiTheme } = useEuiTheme();

const removeEventListenersRef = useRef<(() => void) | null>(null);
const [dragHandleCount, setDragHandleCount] = useState<number>(0);

/** Set initial styles based on state at mount to prevent styles from "blipping" */
const initialStyles = useMemo(() => {
const initialPanel = gridLayoutStateManager.gridLayout$.getValue()[rowIndex].panels[panelId];
Expand All @@ -63,6 +70,7 @@ export const GridPanel = forwardRef<
])
.pipe(skip(1)) // skip the first emit because the `initialStyles` will take care of it
.subscribe(([activePanel, gridLayout, runtimeSettings]) => {
// console.log('SUBSCRIBE!!!!');
const ref = gridLayoutStateManager.panelRefs.current[rowIndex][panelId];
const panel = gridLayout[rowIndex].panels[panelId];
if (!ref || !panel) return;
Expand All @@ -87,31 +95,40 @@ export const GridPanel = forwardRef<
// 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 = ``;
ref.style.gridRowEnd = ``;
ref.style.gridColumnEnd = `auto`;
ref.style.gridRowEnd = `auto`;

// if (resizeHandleRef.current) {
// resizeHandleRef.current.style.width = `${Math.min(
// 24,
// runtimeSettings.columnPixelWidth * panel.width
// )}px`;
// }
} else {
// if the current panel is being dragged, render it with a fixed position + size
ref.style.position = 'fixed';

ref.classList.add('react-draggable-dragging');

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.gridColumnStart = ``;
ref.style.gridRowStart = ``;
ref.style.gridColumnEnd = ``;
ref.style.gridRowEnd = ``;
ref.style.gridArea = `auto`; // shortcut to set all grid styles to `auto`
}
} else {
ref.style.zIndex = '0';
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 = ``;
ref.style.height = ``;
ref.style.position = 'static';
ref.style.left = `auto`;
ref.style.top = `auto`;
ref.style.width = `auto`;
ref.style.height = `auto`;

ref.classList.remove('react-draggable-dragging');

// and render the panel locked to the grid
ref.style.gridColumnStart = `${panel.column + 1}`;
Expand All @@ -129,67 +146,143 @@ export const GridPanel = forwardRef<
[]
);

useEffect(() => {
const onDropEventHandler = (dropEvent: MouseEvent) =>
interactionStart(panelId, 'drop', dropEvent);
/**
* Subscription to add a singular "drop" event handler whenever an interaction starts -
* this is handled in a subscription so that it is not lost when the component gets remounted
* (which happens when a panel gets dragged from one grid row to another)
*/
const dropEventSubscription = gridLayoutStateManager.interactionEvent$.subscribe((event) => {
if (!event || event.id !== panelId) return;

/**
* By adding the "drop" event listener to the document rather than the drag/resize event handler,
* we prevent the element from getting "stuck" in an interaction; however, we only attach this event
* listener **when the drag/resize event starts**, and it only executes once, which means we don't
* have to remove the `mouseup` event listener
*/
document.addEventListener('mouseup', onDropEventHandler, {
once: true,
passive: true,
});
});

return () => {
dropEventSubscription.unsubscribe();
document.removeEventListener('mouseup', onDropEventHandler); // removes the event listener on row change
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [panelId]);

/**
* We need to memoize the `onMouseDown` callback so that we don't assign a new `onMouseDown` event handler
* every time `setDragHandles` is called
*/
const onMouseDown = useCallback(
(e: MouseEvent) => {
e.stopPropagation();
interactionStart(panelId, 'drag', e);
},
[panelId, interactionStart]
);

const setDragHandles = useCallback(
(dragHandles: Array<HTMLElement | null>) => {
setDragHandleCount(dragHandles.length);

for (const handle of dragHandles) {
if (handle === null) return;
handle.addEventListener('mousedown', onMouseDown, { passive: true });
}

removeEventListenersRef.current = () => {
for (const handle of dragHandles) {
if (handle === null) return;
handle.removeEventListener('mousedown', onMouseDown);
}
};
},
[onMouseDown]
);

/**
* Memoize panel contents to prevent unnecessary re-renders
*/
const panelContents = useMemo(() => {
return renderPanelContents(panelId);
}, [panelId, renderPanelContents]);
return renderPanelContents(panelId, setDragHandles);
}, [panelId, renderPanelContents, setDragHandles]);

useEffect(() => {
return () => {
if (removeEventListenersRef.current) {
removeEventListenersRef.current();
}
};
}, []);

return (
<>
<div ref={panelRef} css={initialStyles}>
<EuiPanel
hasShadow={false}
hasBorder={true}
<div
css={css`
padding: 0;
position: relative;
height: 100%;
&:hover,
&:active {
& .dragHandle,
& .resizeHandle {
opacity: 1;
}
}
`}
>
{/* drag handle */}
<div
className="dragHandle"
css={css`
opacity: 0;
display: flex;
cursor: move;
position: absolute;
align-items: center;
justify-content: center;
top: -${euiThemeVars.euiSizeL};
width: ${euiThemeVars.euiSizeL};
height: ${euiThemeVars.euiSizeL};
z-index: ${euiThemeVars.euiZLevel3};
margin-left: ${euiThemeVars.euiSizeS};
border: 1px solid ${euiTheme.border.color};
background-color: ${euiTheme.colors.emptyShade};
border-radius: ${euiThemeVars.euiBorderRadius} ${euiThemeVars.euiBorderRadius} 0 0;
&:hover {
cursor: grab;
opacity: 1 !important;
}
&:active {
cursor: grabbing;
opacity: 1 !important;
}
`}
onMouseDown={(e) => interactionStart('drag', e)}
onMouseUp={(e) => interactionStart('drop', e)}
>
<EuiIcon type="grabOmnidirectional" />
</div>
{!dragHandleCount && (
<div
className="dragHandle"
css={css`
opacity: 0;
display: flex;
cursor: move;
position: absolute;
align-items: center;
justify-content: center;
top: -${euiThemeVars.euiSizeL};
width: ${euiThemeVars.euiSizeL};
height: ${euiThemeVars.euiSizeL};
z-index: ${euiThemeVars.euiZLevel3};
margin-left: ${euiThemeVars.euiSizeS};
border: 1px solid ${euiTheme.border.color};
background-color: ${euiTheme.colors.emptyShade};
border-radius: ${euiThemeVars.euiBorderRadius} ${euiThemeVars.euiBorderRadius} 0 0;
&:hover {
cursor: grab;
opacity: 1 !important;
}
&:active {
cursor: grabbing;
opacity: 1 !important;
}
`}
onMouseDown={(e) => interactionStart(panelId, 'drag', e)}
>
<EuiIcon type="grabOmnidirectional" />
</div>
)}
{/* Resize handle */}
<div
// ref={resizeHandleRef}
className="resizeHandle"
onMouseDown={(e) => interactionStart('resize', e)}
onMouseUp={(e) => interactionStart('drop', e)}
onMouseDown={(e) => interactionStart(panelId, 'resize', e)}
css={css`
right: 0;
bottom: 0;
opacity: 0;
margin: -2px;
z-index: 9000;
position: absolute;
width: ${euiThemeVars.euiSizeL};
height: ${euiThemeVars.euiSizeL};
Expand All @@ -204,16 +297,8 @@ export const GridPanel = forwardRef<
}
`}
/>
<div
css={css`
${euiFullHeight()}
${useEuiOverflowScroll('y', false)}
${useEuiOverflowScroll('x', false)}
`}
>
{panelContents}
</div>
</EuiPanel>
{panelContents}
</div>
</div>
</>
);
Expand Down
Loading

0 comments on commit e804b83

Please sign in to comment.