From 41465ba474ef64f02e2e3a8cf00557f482e56a52 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed Date: Fri, 3 May 2024 14:51:44 +0200 Subject: [PATCH 1/9] Add attention state as a combination between keyboard focus and mouse activity --- .../layout/grid/SceneGridLayoutRenderer.tsx | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/scenes/src/components/layout/grid/SceneGridLayoutRenderer.tsx b/packages/scenes/src/components/layout/grid/SceneGridLayoutRenderer.tsx index da3288332..dc415e361 100644 --- a/packages/scenes/src/components/layout/grid/SceneGridLayoutRenderer.tsx +++ b/packages/scenes/src/components/layout/grid/SceneGridLayoutRenderer.tsx @@ -13,6 +13,9 @@ import { GrafanaTheme2 } from '@grafana/data'; export function SceneGridLayoutRenderer({ model }: SceneComponentProps) { const { children, isLazy, isDraggable, isResizable } = model.useState(); + // Attention to handle keyboard and hover shortcuts + const [attention, setAttention] = React.useState(); + validateChildrenSize(children); return ( @@ -66,6 +69,8 @@ export function SceneGridLayoutRenderer({ model }: SceneComponentProps setAttention(gridItem.i)} /> ))} @@ -82,10 +87,25 @@ interface GridItemWrapperProps extends React.HTMLAttributes { index: number; totalCount: number; isLazy?: boolean; + hasAttention: boolean; + setAttention: () => void; } const GridItemWrapper = React.forwardRef((props, ref) => { - const { grid, layoutItem, index, totalCount, isLazy, style, onLoad, onChange, children, ...divProps } = props; + const { + grid, + layoutItem, + index, + totalCount, + isLazy, + style, + onLoad, + onChange, + children, + hasAttention, + setAttention, + ...divProps + } = props; const sceneChild = grid.getSceneLayoutChild(layoutItem.i)!; const className = sceneChild.getClassName?.(); @@ -97,6 +117,9 @@ const GridItemWrapper = React.forwardRef(( {...divProps} key={sceneChild.state.key!} data-griditem-key={sceneChild.state.key} + data-attention={hasAttention} + onFocus={() => !hasAttention && setAttention()} + onMouseMove={() => !hasAttention && setAttention()} className={cx(className, props.className)} style={style} ref={ref} @@ -113,6 +136,9 @@ const GridItemWrapper = React.forwardRef(( ref={ref} key={sceneChild.state.key} data-griditem-key={sceneChild.state.key} + data-attention={hasAttention} + onFocus={() => !hasAttention && setAttention()} + onMouseMove={() => !hasAttention && setAttention()} className={cx(className, props.className)} style={style} > From 8c567b7611f743d4cdf703b8469b40269ddc613d Mon Sep 17 00:00:00 2001 From: Tobias Skarhed Date: Wed, 22 May 2024 14:40:19 +0200 Subject: [PATCH 2/9] Use PanelAttentionService --- .../components/VizPanel/VizPanelRenderer.tsx | 13 +++++++-- .../layout/grid/SceneGridLayoutRenderer.tsx | 28 +------------------ 2 files changed, 12 insertions(+), 29 deletions(-) diff --git a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx index c69949ca3..e12d3c135 100644 --- a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx @@ -2,7 +2,8 @@ import React, { RefCallback } from 'react'; import { useMeasure } from 'react-use'; import { AlertState, GrafanaTheme2, PanelData, PluginContextProvider } from '@grafana/data'; -import { getAppEvents } from '@grafana/runtime'; +// @ts-ignore +import { getAppEvents, getPanelAttentionSrv } from '@grafana/runtime'; import { PanelChrome, ErrorBoundaryAlert, PanelContextProvider, Tooltip, useStyles2, Icon } from '@grafana/ui'; import { sceneGraph } from '../../core/sceneGraph'; @@ -26,10 +27,12 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { description, } = model.useState(); const [ref, { width, height }] = useMeasure(); + const panelAttentionService = getPanelAttentionSrv(); const plugin = model.getPlugin(); const { dragClass, dragClassCancel } = getDragClasses(model); const dataObject = sceneGraph.getData(model); + const rawData = dataObject.useState(); const dataWithFieldConfig = model.applyFieldConfig(rawData.data!); const sceneTimeRange = sceneGraph.getTimeRange(model); @@ -130,7 +133,13 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { return (
-
} className={absoluteWrapper} data-viz-panel-key={model.state.key}> +
} + className={absoluteWrapper} + onFocus={() => panelAttentionService.setPanelWithAttention(model)} + onMouseMove={() => panelAttentionService.setPanelWithAttention(model)} + data-viz-panel-key={model.state.key} + > {width > 0 && height > 0 && ( ) { const { children, isLazy, isDraggable, isResizable } = model.useState(); - // Attention to handle keyboard and hover shortcuts - const [attention, setAttention] = React.useState(); - validateChildrenSize(children); return ( @@ -69,8 +66,6 @@ export function SceneGridLayoutRenderer({ model }: SceneComponentProps setAttention(gridItem.i)} /> ))} @@ -87,25 +82,10 @@ interface GridItemWrapperProps extends React.HTMLAttributes { index: number; totalCount: number; isLazy?: boolean; - hasAttention: boolean; - setAttention: () => void; } const GridItemWrapper = React.forwardRef((props, ref) => { - const { - grid, - layoutItem, - index, - totalCount, - isLazy, - style, - onLoad, - onChange, - children, - hasAttention, - setAttention, - ...divProps - } = props; + const { grid, layoutItem, index, totalCount, isLazy, style, onLoad, onChange, children, ...divProps } = props; const sceneChild = grid.getSceneLayoutChild(layoutItem.i)!; const className = sceneChild.getClassName?.(); @@ -117,9 +97,6 @@ const GridItemWrapper = React.forwardRef(( {...divProps} key={sceneChild.state.key!} data-griditem-key={sceneChild.state.key} - data-attention={hasAttention} - onFocus={() => !hasAttention && setAttention()} - onMouseMove={() => !hasAttention && setAttention()} className={cx(className, props.className)} style={style} ref={ref} @@ -136,9 +113,6 @@ const GridItemWrapper = React.forwardRef(( ref={ref} key={sceneChild.state.key} data-griditem-key={sceneChild.state.key} - data-attention={hasAttention} - onFocus={() => !hasAttention && setAttention()} - onMouseMove={() => !hasAttention && setAttention()} className={cx(className, props.className)} style={style} > From 655f6460147f08cc0d5e2e563f7f371cccd33003 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed Date: Thu, 23 May 2024 10:44:37 +0200 Subject: [PATCH 3/9] Use viz-panel-key instead --- packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx index e12d3c135..2cbaead21 100644 --- a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx @@ -136,8 +136,8 @@ export function VizPanelRenderer({ model }: SceneComponentProps) {
} className={absoluteWrapper} - onFocus={() => panelAttentionService.setPanelWithAttention(model)} - onMouseMove={() => panelAttentionService.setPanelWithAttention(model)} + onFocus={() => panelAttentionService.setPanelWithAttention(model.state.key)} + onMouseMove={() => panelAttentionService.setPanelWithAttention(model.state.key)} data-viz-panel-key={model.state.key} > {width > 0 && height > 0 && ( From d6a5418a81db954c9f8d78c00d1ea783d4ca7a6a Mon Sep 17 00:00:00 2001 From: Tobias Skarhed Date: Thu, 23 May 2024 13:47:37 +0200 Subject: [PATCH 4/9] support undefined service use case --- packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx index 2cbaead21..b5499a23f 100644 --- a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx @@ -136,8 +136,8 @@ export function VizPanelRenderer({ model }: SceneComponentProps) {
} className={absoluteWrapper} - onFocus={() => panelAttentionService.setPanelWithAttention(model.state.key)} - onMouseMove={() => panelAttentionService.setPanelWithAttention(model.state.key)} + onFocus={() => panelAttentionService?.setPanelWithAttention(model.state.key)} + onMouseMove={() => panelAttentionService?.setPanelWithAttention(model.state.key)} data-viz-panel-key={model.state.key} > {width > 0 && height > 0 && ( From 5885025d8842f8472b2930e9ff737fd35e1a4f47 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed Date: Fri, 24 May 2024 10:30:27 +0200 Subject: [PATCH 5/9] Debounce mousemove --- .../scenes/src/components/VizPanel/VizPanelRenderer.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx index b5499a23f..c2288de93 100644 --- a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx @@ -1,4 +1,4 @@ -import React, { RefCallback } from 'react'; +import React, { RefCallback, useMemo } from 'react'; import { useMeasure } from 'react-use'; import { AlertState, GrafanaTheme2, PanelData, PluginContextProvider } from '@grafana/data'; @@ -11,6 +11,7 @@ import { isSceneObject, SceneComponentProps, SceneLayout, SceneObject } from '.. import { VizPanel } from './VizPanel'; import { css, cx } from '@emotion/css'; +import { debounce } from 'lodash'; export function VizPanelRenderer({ model }: SceneComponentProps) { const { @@ -27,7 +28,9 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { description, } = model.useState(); const [ref, { width, height }] = useMeasure(); - const panelAttentionService = getPanelAttentionSrv(); + const panelAttentionService = useMemo(() => getPanelAttentionSrv(), []); + const debouncedMouseMove = debounce(() => panelAttentionService.setPanelWithAttention(model.state.key), 100); + const plugin = model.getPlugin(); const { dragClass, dragClassCancel } = getDragClasses(model); @@ -137,7 +140,7 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { ref={ref as RefCallback} className={absoluteWrapper} onFocus={() => panelAttentionService?.setPanelWithAttention(model.state.key)} - onMouseMove={() => panelAttentionService?.setPanelWithAttention(model.state.key)} + onMouseMove={debouncedMouseMove} data-viz-panel-key={model.state.key} > {width > 0 && height > 0 && ( From ae367370050f5dff15bf9ee860b45626ce4ec99f Mon Sep 17 00:00:00 2001 From: Tobias Skarhed Date: Mon, 27 May 2024 16:30:48 +0200 Subject: [PATCH 6/9] Use AppEvents --- .../components/VizPanel/VizPanelRenderer.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx index c2288de93..b21680046 100644 --- a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx @@ -1,9 +1,10 @@ -import React, { RefCallback, useMemo } from 'react'; +import React, { RefCallback } from 'react'; import { useMeasure } from 'react-use'; -import { AlertState, GrafanaTheme2, PanelData, PluginContextProvider } from '@grafana/data'; // @ts-ignore -import { getAppEvents, getPanelAttentionSrv } from '@grafana/runtime'; +import { AlertState, GrafanaTheme2, PanelData, PluginContextProvider, SetPanelAttentionEvent } from '@grafana/data'; + +import { getAppEvents } from '@grafana/runtime'; import { PanelChrome, ErrorBoundaryAlert, PanelContextProvider, Tooltip, useStyles2, Icon } from '@grafana/ui'; import { sceneGraph } from '../../core/sceneGraph'; @@ -28,8 +29,10 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { description, } = model.useState(); const [ref, { width, height }] = useMeasure(); - const panelAttentionService = useMemo(() => getPanelAttentionSrv(), []); - const debouncedMouseMove = debounce(() => panelAttentionService.setPanelWithAttention(model.state.key), 100); + const appEvents = getAppEvents(); + + const setPanelAttention = () => appEvents.publish(new SetPanelAttentionEvent({ panelId: model.state.key })); + const debouncedMouseMove = debounce(() => setPanelAttention(), 100); const plugin = model.getPlugin(); @@ -136,13 +139,7 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { return (
-
} - className={absoluteWrapper} - onFocus={() => panelAttentionService?.setPanelWithAttention(model.state.key)} - onMouseMove={debouncedMouseMove} - data-viz-panel-key={model.state.key} - > +
} className={absoluteWrapper} data-viz-panel-key={model.state.key}> {width > 0 && height > 0 && ( ) { padding={plugin.noPadding ? 'none' : 'md'} menu={panelMenu} onCancelQuery={model.onCancelQuery} + //@ts-ignore + onFocus={() => setPanelAttention()} + onMouseMove={debouncedMouseMove} > {(innerWidth, innerHeight) => ( <> @@ -185,7 +185,7 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { onOptionsChange={model.onOptionsChange} onFieldConfigChange={model.onFieldConfigChange} onChangeTimeRange={model.onTimeRangeChange} - eventBus={getAppEvents()} + eventBus={appEvents} /> )} From 5f4ef92894770d6bc5ed42573cd2d4263ffe241f Mon Sep 17 00:00:00 2001 From: Tobias Skarhed Date: Thu, 30 May 2024 10:26:37 +0200 Subject: [PATCH 7/9] Memoize functions --- .../src/components/VizPanel/VizPanelRenderer.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx index b21680046..83c31e212 100644 --- a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx @@ -1,4 +1,4 @@ -import React, { RefCallback } from 'react'; +import React, { RefCallback, useCallback, useMemo } from 'react'; import { useMeasure } from 'react-use'; // @ts-ignore @@ -29,10 +29,13 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { description, } = model.useState(); const [ref, { width, height }] = useMeasure(); - const appEvents = getAppEvents(); + const appEvents = useMemo(() => getAppEvents(), []); - const setPanelAttention = () => appEvents.publish(new SetPanelAttentionEvent({ panelId: model.state.key })); - const debouncedMouseMove = debounce(() => setPanelAttention(), 100); + const setPanelAttention = useCallback( + () => appEvents.publish(new SetPanelAttentionEvent({ panelId: model.state.key })), + [model.state.key, appEvents] + ); + const debouncedMouseMove = useMemo(() => debounce(setPanelAttention, 100), [setPanelAttention]); const plugin = model.getPlugin(); @@ -159,9 +162,8 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { padding={plugin.noPadding ? 'none' : 'md'} menu={panelMenu} onCancelQuery={model.onCancelQuery} - //@ts-ignore onFocus={() => setPanelAttention()} - onMouseMove={debouncedMouseMove} + onMouseMove={() => debouncedMouseMove()} > {(innerWidth, innerHeight) => ( <> From 74b3657f95e5dd57797a61786c1e85b9fe9348d5 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed Date: Thu, 30 May 2024 10:30:00 +0200 Subject: [PATCH 8/9] Ignore onFocus (it is avilable in grafana/ui canary) --- packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx index 83c31e212..aca27d44a 100644 --- a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx @@ -162,6 +162,7 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { padding={plugin.noPadding ? 'none' : 'md'} menu={panelMenu} onCancelQuery={model.onCancelQuery} + // @ts-ignore onFocus={() => setPanelAttention()} onMouseMove={() => debouncedMouseMove()} > From 897eb8c705a1a0eeb72a0ba427713a8ef1b49718 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Thu, 30 May 2024 11:12:20 +0200 Subject: [PATCH 9/9] Update packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Torkel Ödegaard --- packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx index aca27d44a..5865dfda7 100644 --- a/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx +++ b/packages/scenes/src/components/VizPanel/VizPanelRenderer.tsx @@ -163,8 +163,8 @@ export function VizPanelRenderer({ model }: SceneComponentProps) { menu={panelMenu} onCancelQuery={model.onCancelQuery} // @ts-ignore - onFocus={() => setPanelAttention()} - onMouseMove={() => debouncedMouseMove()} + onFocus={setPanelAttention} + onMouseMove={debouncedMouseMove} > {(innerWidth, innerHeight) => ( <>