Canvas: Add option to disable the tooltip (#108196)
This commit is contained in:
@@ -270,6 +270,14 @@ You can enable infinite panning in a canvas when pan and zoom is enabled. This a
|
||||
Infinite panning is an experimental feature that may not work as expected in all scenarios. For example, elements that are not top-left constrained may experience unexpected movement when panning.
|
||||
{{< /admonition >}}
|
||||
|
||||
### Tooltip options
|
||||
|
||||
The **Tooltip mode** setting controls the display of tooltips when hovering over canvas elements that are connected to data, data links, or actions.
|
||||
The options are:
|
||||
|
||||
- **Enabled** - Show a tooltip when the cursor hovers over an element.
|
||||
- **Disabled** - Tooltips are not shown on hover.
|
||||
|
||||
### Layer options
|
||||
|
||||
The **Layer** options let you add elements to the canvas and control its appearance:
|
||||
|
||||
+8
@@ -112,6 +112,10 @@ export const defaultCanvasElementOptions: Partial<CanvasElementOptions> = {
|
||||
connections: [],
|
||||
};
|
||||
|
||||
export interface CanvasTooltip {
|
||||
mode: ui.TooltipDisplayMode;
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
/**
|
||||
* Enable infinite pan
|
||||
@@ -147,6 +151,10 @@ export interface Options {
|
||||
* Show all available element types
|
||||
*/
|
||||
showAdvancedTypes: boolean;
|
||||
/**
|
||||
* Controls tooltip options
|
||||
*/
|
||||
tooltip: CanvasTooltip;
|
||||
}
|
||||
|
||||
export const defaultOptions: Partial<Options> = {
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
ActionVariableInput,
|
||||
} from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { TooltipDisplayMode } from '@grafana/schema';
|
||||
import { ConfirmModal, VariablesInputModal } from '@grafana/ui';
|
||||
import { LayerElement } from 'app/core/components/Layers/types';
|
||||
import { notFoundItem } from 'app/features/canvas/elements/notFound';
|
||||
@@ -619,7 +620,7 @@ export class ElementState implements LayerElement {
|
||||
handleMouseEnter = (event: React.MouseEvent, isSelected: boolean | undefined) => {
|
||||
const scene = this.getScene();
|
||||
|
||||
const shouldHandleTooltip = !scene?.isEditingEnabled && !scene?.tooltip?.isOpen;
|
||||
const shouldHandleTooltip = !scene?.isEditingEnabled && !scene?.tooltipPayload?.isOpen;
|
||||
if (shouldHandleTooltip) {
|
||||
this.handleTooltip(event);
|
||||
} else if (!isSelected) {
|
||||
@@ -686,7 +687,7 @@ export class ElementState implements LayerElement {
|
||||
|
||||
handleTooltip = (event: React.MouseEvent) => {
|
||||
const scene = this.getScene();
|
||||
if (scene?.tooltipCallback) {
|
||||
if (scene?.tooltipCallback && scene.tooltipMode !== TooltipDisplayMode.None) {
|
||||
const rect = this.div?.getBoundingClientRect();
|
||||
scene.tooltipCallback({
|
||||
anchorPoint: { x: rect?.right ?? event.pageX, y: rect?.top ?? event.pageY },
|
||||
@@ -698,7 +699,7 @@ export class ElementState implements LayerElement {
|
||||
|
||||
handleMouseLeave = (event: React.MouseEvent) => {
|
||||
const scene = this.getScene();
|
||||
if (scene?.tooltipCallback && !scene?.tooltip?.isOpen) {
|
||||
if (scene?.tooltipCallback && !scene?.tooltipPayload?.isOpen) {
|
||||
scene.tooltipCallback(undefined);
|
||||
}
|
||||
|
||||
@@ -744,9 +745,9 @@ export class ElementState implements LayerElement {
|
||||
|
||||
onTooltipCallback = () => {
|
||||
const scene = this.getScene();
|
||||
if (scene?.tooltipCallback && scene.tooltip?.anchorPoint) {
|
||||
if (scene?.tooltipCallback && scene.tooltipPayload?.anchorPoint) {
|
||||
scene.tooltipCallback({
|
||||
anchorPoint: { x: scene.tooltip.anchorPoint.x, y: scene.tooltip.anchorPoint.y },
|
||||
anchorPoint: { x: scene.tooltipPayload.anchorPoint.x, y: scene.tooltipPayload.anchorPoint.y },
|
||||
element: this,
|
||||
isOpen: true,
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
ScalarDimensionConfig,
|
||||
ScaleDimensionConfig,
|
||||
TextDimensionConfig,
|
||||
TooltipDisplayMode,
|
||||
} from '@grafana/schema';
|
||||
import { Portal } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
@@ -27,11 +28,13 @@ import {
|
||||
import { CanvasContextMenu } from 'app/plugins/panel/canvas/components/CanvasContextMenu';
|
||||
import { CanvasTooltip } from 'app/plugins/panel/canvas/components/CanvasTooltip';
|
||||
import { Connections } from 'app/plugins/panel/canvas/components/connections/Connections';
|
||||
import { Options } from 'app/plugins/panel/canvas/panelcfg.gen';
|
||||
import { AnchorPoint, CanvasTooltipPayload } from 'app/plugins/panel/canvas/types';
|
||||
import { getTransformInstance } from 'app/plugins/panel/canvas/utils';
|
||||
|
||||
import appEvents from '../../../core/app_events';
|
||||
import { CanvasPanel } from '../../../plugins/panel/canvas/CanvasPanel';
|
||||
import { getDashboardSrv } from '../../dashboard/services/DashboardSrv';
|
||||
import { CanvasFrameOptions } from '../frame';
|
||||
import { DEFAULT_CANVAS_ELEMENT_CONFIG } from '../registry';
|
||||
|
||||
@@ -71,6 +74,7 @@ export class Scene {
|
||||
shouldShowAdvancedTypes?: boolean;
|
||||
shouldPanZoom?: boolean;
|
||||
shouldInfinitePan?: boolean;
|
||||
tooltipMode?: TooltipDisplayMode;
|
||||
skipNextSelectionBroadcast = false;
|
||||
ignoreDataUpdate = false;
|
||||
panel: CanvasPanel;
|
||||
@@ -93,7 +97,7 @@ export class Scene {
|
||||
setBackgroundCallback?: (anchorPoint: AnchorPoint) => void;
|
||||
|
||||
tooltipCallback?: (tooltip: CanvasTooltipPayload | undefined) => void;
|
||||
tooltip?: CanvasTooltipPayload;
|
||||
tooltipPayload?: CanvasTooltipPayload;
|
||||
|
||||
moveableActionCallback?: (moved: boolean) => void;
|
||||
|
||||
@@ -106,15 +110,16 @@ export class Scene {
|
||||
transformComponentRef: RefObject<ReactZoomPanPinchContentRef> | undefined;
|
||||
|
||||
constructor(
|
||||
cfg: CanvasFrameOptions,
|
||||
enableEditing: boolean,
|
||||
showAdvancedTypes: boolean,
|
||||
panZoom: boolean,
|
||||
infinitePan: boolean,
|
||||
options: Options,
|
||||
public onSave: (cfg: CanvasFrameOptions) => void,
|
||||
panel: CanvasPanel
|
||||
) {
|
||||
this.root = this.load(cfg, enableEditing, showAdvancedTypes, panZoom, infinitePan);
|
||||
// TODO: Will need to update this approach for dashboard scenes
|
||||
// migration (new dashboard edit experience)
|
||||
const dashboard = getDashboardSrv().getCurrent();
|
||||
const enableEditing = options.inlineEditing && dashboard?.editable;
|
||||
|
||||
this.root = this.load(options, enableEditing);
|
||||
|
||||
this.subscription = this.editModeEnabled.subscribe((open) => {
|
||||
if (!this.moveable || !this.isEditingEnabled) {
|
||||
@@ -147,15 +152,12 @@ export class Scene {
|
||||
return !this.byName.has(v);
|
||||
};
|
||||
|
||||
load(
|
||||
cfg: CanvasFrameOptions,
|
||||
enableEditing: boolean,
|
||||
showAdvancedTypes: boolean,
|
||||
panZoom: boolean,
|
||||
infinitePan: boolean
|
||||
) {
|
||||
load(options: Options, enableEditing: boolean) {
|
||||
const { root, showAdvancedTypes, panZoom, infinitePan, tooltip } = options;
|
||||
const tooltipMode = tooltip?.mode ?? TooltipDisplayMode.Single;
|
||||
|
||||
this.root = new RootElement(
|
||||
cfg ?? {
|
||||
root ?? {
|
||||
type: 'frame',
|
||||
elements: [DEFAULT_CANVAS_ELEMENT_CONFIG],
|
||||
},
|
||||
@@ -167,6 +169,7 @@ export class Scene {
|
||||
this.shouldShowAdvancedTypes = showAdvancedTypes;
|
||||
this.shouldPanZoom = panZoom;
|
||||
this.shouldInfinitePan = infinitePan;
|
||||
this.tooltipMode = tooltipMode;
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.div) {
|
||||
@@ -283,11 +286,13 @@ export class Scene {
|
||||
};
|
||||
|
||||
render() {
|
||||
const hasDataLinks = this.tooltip?.element?.getLinks && this.tooltip.element.getLinks({}).length > 0;
|
||||
const hasActions = this.tooltip?.element?.options.actions && this.tooltip.element.options.actions.length > 0;
|
||||
const hasDataLinks = this.tooltipPayload?.element?.getLinks && this.tooltipPayload.element.getLinks({}).length > 0;
|
||||
const hasActions =
|
||||
this.tooltipPayload?.element?.options.actions && this.tooltipPayload.element.options.actions.length > 0;
|
||||
|
||||
const isTooltipValid = hasDataLinks || hasActions || this.tooltip?.element?.data?.field;
|
||||
const canShowElementTooltip = !this.isEditingEnabled && isTooltipValid;
|
||||
const isTooltipValid = hasDataLinks || hasActions || this.tooltipPayload?.element?.data?.field;
|
||||
const isTooltipEnabled = this.tooltipMode !== TooltipDisplayMode.None;
|
||||
const canShowElementTooltip = !this.isEditingEnabled && isTooltipValid && isTooltipEnabled;
|
||||
|
||||
const sceneDiv = (
|
||||
<div key={this.revId} className={this.styles.wrap} style={this.style} ref={this.setRef}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component } from 'react';
|
||||
import * as React from 'react';
|
||||
import { Component } from 'react';
|
||||
import { ReplaySubject, Subscription } from 'rxjs';
|
||||
|
||||
import { PanelProps } from '@grafana/data';
|
||||
@@ -8,7 +8,6 @@ import { PanelContext, PanelContextRoot } from '@grafana/ui';
|
||||
import { CanvasFrameOptions } from 'app/features/canvas/frame';
|
||||
import { ElementState } from 'app/features/canvas/runtime/element';
|
||||
import { Scene } from 'app/features/canvas/runtime/scene';
|
||||
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
||||
import { PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events';
|
||||
|
||||
import { SetBackground } from './components/SetBackground';
|
||||
@@ -63,22 +62,9 @@ export class CanvasPanel extends Component<Props, State> {
|
||||
moveableAction: false,
|
||||
};
|
||||
|
||||
// TODO: Will need to update this approach for dashboard scenes
|
||||
// migration (new dashboard edit experience)
|
||||
const dashboard = getDashboardSrv().getCurrent();
|
||||
const allowEditing = this.props.options.inlineEditing && dashboard?.editable;
|
||||
|
||||
// Only the initial options are ever used.
|
||||
// later changes are all controlled by the scene
|
||||
this.scene = new Scene(
|
||||
this.props.options.root,
|
||||
allowEditing,
|
||||
this.props.options.showAdvancedTypes,
|
||||
this.props.options.panZoom,
|
||||
this.props.options.infinitePan,
|
||||
this.onUpdateScene,
|
||||
this
|
||||
);
|
||||
this.scene = new Scene(this.props.options, this.onUpdateScene, this);
|
||||
this.scene.updateSize(props.width, props.height);
|
||||
this.scene.updateData(props.data);
|
||||
this.scene.inlineEditingCallback = this.openInlineEdit;
|
||||
@@ -239,12 +225,14 @@ export class CanvasPanel extends Component<Props, State> {
|
||||
this.props.options.showAdvancedTypes !== nextProps.options.showAdvancedTypes;
|
||||
const panZoomSwitched = this.props.options.panZoom !== nextProps.options.panZoom;
|
||||
const infinitePanSwitched = this.props.options.infinitePan !== nextProps.options.infinitePan;
|
||||
const tooltipModeSwitched = this.props.options.tooltip?.mode !== nextProps.options.tooltip?.mode;
|
||||
if (
|
||||
this.needsReload ||
|
||||
inlineEditingSwitched ||
|
||||
shouldShowAdvancedTypesSwitched ||
|
||||
panZoomSwitched ||
|
||||
infinitePanSwitched
|
||||
infinitePanSwitched ||
|
||||
tooltipModeSwitched
|
||||
) {
|
||||
if (inlineEditingSwitched) {
|
||||
// Replace scene div to prevent selecto instance leaks
|
||||
@@ -252,13 +240,7 @@ export class CanvasPanel extends Component<Props, State> {
|
||||
}
|
||||
|
||||
this.needsReload = false;
|
||||
this.scene.load(
|
||||
nextProps.options.root,
|
||||
nextProps.options.inlineEditing,
|
||||
nextProps.options.showAdvancedTypes,
|
||||
nextProps.options.panZoom,
|
||||
nextProps.options.infinitePan
|
||||
);
|
||||
this.scene.load(nextProps.options, nextProps.options.inlineEditing);
|
||||
this.scene.updateSize(nextProps.width, nextProps.height);
|
||||
this.scene.updateData(nextProps.data);
|
||||
changed = true;
|
||||
@@ -294,7 +276,7 @@ export class CanvasPanel extends Component<Props, State> {
|
||||
};
|
||||
|
||||
tooltipCallback = (tooltip: CanvasTooltipPayload | undefined) => {
|
||||
this.scene.tooltip = tooltip;
|
||||
this.scene.tooltipPayload = tooltip;
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export const CanvasTooltip = ({ scene }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const onClose = () => {
|
||||
if (scene?.tooltipCallback && scene.tooltip) {
|
||||
if (scene?.tooltipCallback && scene.tooltipPayload) {
|
||||
scene.tooltipCallback(undefined);
|
||||
}
|
||||
};
|
||||
@@ -45,7 +45,7 @@ export const CanvasTooltip = ({ scene }: Props) => {
|
||||
const { overlayProps } = useOverlay({ onClose: onClose, isDismissable: true }, ref);
|
||||
const { dialogProps } = useDialog({}, ref);
|
||||
|
||||
const element = scene.tooltip?.element;
|
||||
const element = scene.tooltipPayload?.element;
|
||||
if (!element) {
|
||||
return <></>;
|
||||
}
|
||||
@@ -138,18 +138,18 @@ export const CanvasTooltip = ({ scene }: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{scene.tooltip?.element && scene.tooltip.anchorPoint && (
|
||||
{scene.tooltipPayload?.element && scene.tooltipPayload.anchorPoint && (
|
||||
<Portal>
|
||||
<VizTooltipContainer
|
||||
className={cx(styles.tooltipWrapper, scene.tooltip.isOpen && styles.pinned)}
|
||||
position={{ x: scene.tooltip.anchorPoint.x, y: scene.tooltip.anchorPoint.y }}
|
||||
className={cx(styles.tooltipWrapper, scene.tooltipPayload.isOpen && styles.pinned)}
|
||||
position={{ x: scene.tooltipPayload.anchorPoint.x, y: scene.tooltipPayload.anchorPoint.y }}
|
||||
offset={{ x: 5, y: 0 }}
|
||||
allowPointerEvents={scene.tooltip.isOpen}
|
||||
allowPointerEvents={scene.tooltipPayload.isOpen}
|
||||
>
|
||||
<section ref={ref} {...overlayProps} {...dialogProps}>
|
||||
{scene.tooltip.isOpen && <CloseButton style={{ zIndex: 1 }} onClick={onClose} />}
|
||||
<VizTooltipHeader item={headerItem} isPinned={scene.tooltip.isOpen!} />
|
||||
{element.data.text && <VizTooltipContent items={contentItems} isPinned={scene.tooltip.isOpen!} />}
|
||||
{scene.tooltipPayload.isOpen && <CloseButton style={{ zIndex: 1 }} onClick={onClose} />}
|
||||
<VizTooltipHeader item={headerItem} isPinned={scene.tooltipPayload.isOpen!} />
|
||||
{element.data.text && <VizTooltipContent items={contentItems} isPinned={scene.tooltipPayload.isOpen!} />}
|
||||
{(links.length > 0 || actions.length > 0) && <VizTooltipFooter dataLinks={links} actions={actions} />}
|
||||
</section>
|
||||
</VizTooltipContainer>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { FieldConfigProperty, PanelOptionsEditorBuilder, PanelPlugin } from '@grafana/data';
|
||||
import { t } from '@grafana/i18n';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { TooltipDisplayMode } from '@grafana/schema/dist/esm/common/common.gen';
|
||||
import { FrameState } from 'app/features/canvas/runtime/frame';
|
||||
|
||||
import { CanvasPanel, InstanceState } from './CanvasPanel';
|
||||
@@ -12,7 +13,7 @@ import { canvasMigrationHandler } from './migrations';
|
||||
import { Options } from './panelcfg.gen';
|
||||
|
||||
export const addStandardCanvasEditorOptions = (builder: PanelOptionsEditorBuilder<Options>) => {
|
||||
const category = [t('canvas.category-canvas', 'Canvas')];
|
||||
let category = [t('canvas.category-canvas', 'Canvas')];
|
||||
builder.addBooleanSwitch({
|
||||
path: 'inlineEditing',
|
||||
name: t('canvas.name-inline-editing', 'Inline editing'),
|
||||
@@ -56,6 +57,27 @@ export const addStandardCanvasEditorOptions = (builder: PanelOptionsEditorBuilde
|
||||
defaultValue: false,
|
||||
showIf: (opts) => config.featureToggles.canvasPanelPanZoom && opts.panZoom,
|
||||
});
|
||||
|
||||
category = [t('canvas.category-tooltip', 'Tooltip')];
|
||||
|
||||
builder.addRadio({
|
||||
path: 'tooltip.mode',
|
||||
name: t('canvas.tooltip-options.name-tooltip-mode', 'Tooltip mode'),
|
||||
category,
|
||||
defaultValue: TooltipDisplayMode.Single,
|
||||
settings: {
|
||||
options: [
|
||||
{
|
||||
value: TooltipDisplayMode.Single,
|
||||
label: t('canvas.tooltip-options.tooltip-mode-options.label-enabled', 'Enabled'),
|
||||
},
|
||||
{
|
||||
value: TooltipDisplayMode.None,
|
||||
label: t('canvas.tooltip-options.tooltip-mode-options.label-disabled', 'Disabled'),
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const plugin = new PanelPlugin<Options>(CanvasPanel)
|
||||
|
||||
@@ -88,6 +88,9 @@ composableKinds: PanelCfg: {
|
||||
border?: LineConfig
|
||||
connections?: [...CanvasConnection]
|
||||
} @cuetsy(kind="interface")
|
||||
CanvasTooltip: {
|
||||
mode: ui.TooltipDisplayMode
|
||||
} @cuetsy(kind="interface")
|
||||
|
||||
Options: {
|
||||
// Enable inline editing
|
||||
@@ -108,6 +111,8 @@ composableKinds: PanelCfg: {
|
||||
// The list of canvas elements attached to the root element
|
||||
elements: [...CanvasElementOptions]
|
||||
} @cuetsy(kind="interface")
|
||||
// Controls tooltip options
|
||||
tooltip: CanvasTooltip
|
||||
} @cuetsy(kind="interface")
|
||||
}
|
||||
}]
|
||||
|
||||
@@ -110,6 +110,10 @@ export const defaultCanvasElementOptions: Partial<CanvasElementOptions> = {
|
||||
connections: [],
|
||||
};
|
||||
|
||||
export interface CanvasTooltip {
|
||||
mode: ui.TooltipDisplayMode;
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
/**
|
||||
* Enable infinite pan
|
||||
@@ -145,6 +149,10 @@ export interface Options {
|
||||
* Show all available element types
|
||||
*/
|
||||
showAdvancedTypes: boolean;
|
||||
/**
|
||||
* Controls tooltip options
|
||||
*/
|
||||
tooltip: CanvasTooltip;
|
||||
}
|
||||
|
||||
export const defaultOptions: Partial<Options> = {
|
||||
|
||||
@@ -3738,6 +3738,7 @@
|
||||
"category-selected-connection": "Selected connection",
|
||||
"category-selected-element": "Selected element ({{element}})",
|
||||
"category-size": "Size",
|
||||
"category-tooltip": "Tooltip",
|
||||
"cloud-item": {
|
||||
"category-cloud": "Cloud",
|
||||
"label": {
|
||||
@@ -3982,6 +3983,13 @@
|
||||
"auto": "Auto"
|
||||
}
|
||||
},
|
||||
"tooltip-options": {
|
||||
"name-tooltip-mode": "Tooltip mode",
|
||||
"tooltip-mode-options": {
|
||||
"label-disabled": "Disabled",
|
||||
"label-enabled": "Enabled"
|
||||
}
|
||||
},
|
||||
"tree-navigation-editor": {
|
||||
"clear-selection": "Clear selection",
|
||||
"frame-selection": "Frame selection",
|
||||
|
||||
Reference in New Issue
Block a user