mirror of
https://github.com/grafana/grafana.git
synced 2025-08-02 06:22:35 +08:00
Canvas: Connection properties based on data (#64360)
Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
This commit is contained in:
@ -5193,6 +5193,9 @@ exports[`better eslint`] = {
|
||||
"public/app/plugins/panel/canvas/editor/TreeNavigationEditor.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/editor/connectionEditor.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/editor/elementEditor.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "1"],
|
||||
|
@ -4,7 +4,7 @@ import { RegistryItem } from '@grafana/data';
|
||||
import { PanelOptionsSupplier } from '@grafana/data/src/panel/PanelPlugin';
|
||||
import { config } from 'app/core/config';
|
||||
|
||||
import { DimensionContext } from '../dimensions/context';
|
||||
import { DimensionContext, ColorDimensionConfig, ScaleDimensionConfig } from '../dimensions';
|
||||
|
||||
import { BackgroundConfig, Constraint, LineConfig, Placement } from './types';
|
||||
|
||||
@ -46,6 +46,8 @@ export interface CanvasConnection {
|
||||
target: ConnectionCoordinates;
|
||||
targetName?: string;
|
||||
path: ConnectionPath;
|
||||
color?: ColorDimensionConfig;
|
||||
size?: ScaleDimensionConfig;
|
||||
// See https://github.com/anseki/leader-line#options for more examples of more properties
|
||||
}
|
||||
|
||||
|
@ -144,6 +144,8 @@ export class Scene {
|
||||
this.initMoveable(destroySelecto, enableEditing);
|
||||
this.currentLayer = this.root;
|
||||
this.selection.next([]);
|
||||
this.connections.select(undefined);
|
||||
this.connections.updateState();
|
||||
}
|
||||
});
|
||||
return this.root;
|
||||
|
@ -12,7 +12,7 @@ import { PanelEditEnteredEvent, PanelEditExitedEvent } from 'app/types/events';
|
||||
import { InlineEdit } from './InlineEdit';
|
||||
import { SetBackground } from './SetBackground';
|
||||
import { PanelOptions } from './models.gen';
|
||||
import { AnchorPoint, CanvasTooltipPayload } from './types';
|
||||
import { AnchorPoint, CanvasTooltipPayload, ConnectionState } from './types';
|
||||
|
||||
interface Props extends PanelProps<PanelOptions> {}
|
||||
|
||||
@ -27,6 +27,7 @@ interface State {
|
||||
export interface InstanceState {
|
||||
scene: Scene;
|
||||
selected: ElementState[];
|
||||
selectedConnection?: ConnectionState;
|
||||
}
|
||||
|
||||
export interface SelectionAction {
|
||||
@ -113,20 +114,56 @@ export class CanvasPanel extends Component<Props, State> {
|
||||
this.subs.add(
|
||||
this.scene.selection.subscribe({
|
||||
next: (v) => {
|
||||
if (v.length) {
|
||||
activeCanvasPanel = this;
|
||||
activePanelSubject.next({ panel: this });
|
||||
}
|
||||
|
||||
canvasInstances.forEach((canvasInstance) => {
|
||||
if (canvasInstance !== activeCanvasPanel) {
|
||||
canvasInstance.scene.clearCurrentSelection(true);
|
||||
canvasInstance.scene.connections.select(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
this.panelContext.onInstanceStateChange!({
|
||||
scene: this.scene,
|
||||
selected: v,
|
||||
layer: this.scene.root,
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
activeCanvasPanel = this;
|
||||
activePanelSubject.next({ panel: this });
|
||||
this.subs.add(
|
||||
this.scene.connections.selection.subscribe({
|
||||
next: (v) => {
|
||||
if (!this.context.instanceState) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.panelContext.onInstanceStateChange!({
|
||||
scene: this.scene,
|
||||
selected: this.context.instanceState.selected,
|
||||
selectedConnection: v,
|
||||
layer: this.scene.root,
|
||||
});
|
||||
|
||||
if (v) {
|
||||
activeCanvasPanel = this;
|
||||
activePanelSubject.next({ panel: this });
|
||||
}
|
||||
|
||||
canvasInstances.forEach((canvasInstance) => {
|
||||
if (canvasInstance !== activeCanvasPanel) {
|
||||
canvasInstance.scene.clearCurrentSelection(true);
|
||||
canvasInstance.scene.connections.select(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -1,14 +1,12 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { useStyles2 } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
import { CanvasConnection } from 'app/features/canvas/element';
|
||||
import { ElementState } from 'app/features/canvas/runtime/element';
|
||||
import { Scene } from 'app/features/canvas/runtime/scene';
|
||||
|
||||
import { getConnections } from './utils';
|
||||
import { ConnectionState } from './types';
|
||||
|
||||
type Props = {
|
||||
setSVGRef: (anchorElement: SVGSVGElement) => void;
|
||||
@ -17,16 +15,18 @@ type Props = {
|
||||
};
|
||||
|
||||
let idCounter = 0;
|
||||
const htmlElementTypes = ['input', 'textarea'];
|
||||
|
||||
export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const headId = Date.now() + '_' + idCounter++;
|
||||
const CONNECTION_LINE_ID = 'connectionLineId';
|
||||
const CONNECTION_HEAD_ID = useMemo(() => `head-${headId}`, [headId]);
|
||||
const CONNECTION_LINE_ID = useMemo(() => `connectionLineId-${headId}`, [headId]);
|
||||
const EDITOR_HEAD_ID = useMemo(() => `editorHead-${headId}`, [headId]);
|
||||
const defaultArrowColor = config.theme2.colors.text.primary;
|
||||
const defaultArrowSize = 2;
|
||||
|
||||
const [selectedConnection, setSelectedConnection] = useState<CanvasConnection | undefined>(undefined);
|
||||
const [selectedConnection, setSelectedConnection] = useState<ConnectionState | undefined>(undefined);
|
||||
|
||||
// Need to use ref to ensure state is not stale in event handler
|
||||
const selectedConnectionRef = useRef(selectedConnection);
|
||||
@ -34,24 +34,36 @@ export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
|
||||
selectedConnectionRef.current = selectedConnection;
|
||||
});
|
||||
|
||||
const [selectedConnectionSource, setSelectedConnectionSource] = useState<ElementState | undefined>(undefined);
|
||||
const selectedConnectionSourceRef = useRef(selectedConnectionSource);
|
||||
useEffect(() => {
|
||||
selectedConnectionSourceRef.current = selectedConnectionSource;
|
||||
});
|
||||
if (scene.panel.context.instanceState?.selectedConnection) {
|
||||
setSelectedConnection(scene.panel.context.instanceState?.selectedConnection);
|
||||
}
|
||||
}, [scene.panel.context.instanceState?.selectedConnection]);
|
||||
|
||||
const onKeyUp = (e: KeyboardEvent) => {
|
||||
const target = e.target;
|
||||
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (htmlElementTypes.indexOf(target.nodeName.toLowerCase()) > -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Backspace (8) or delete (46)
|
||||
if (e.keyCode === 8 || e.keyCode === 46) {
|
||||
if (selectedConnectionRef.current && selectedConnectionSourceRef.current) {
|
||||
selectedConnectionSourceRef.current.options.connections =
|
||||
selectedConnectionSourceRef.current.options.connections?.filter(
|
||||
(connection) => connection !== selectedConnectionRef.current
|
||||
if (selectedConnectionRef.current && selectedConnectionRef.current.source) {
|
||||
selectedConnectionRef.current.source.options.connections =
|
||||
selectedConnectionRef.current.source.options.connections?.filter(
|
||||
(connection) => connection !== selectedConnectionRef.current?.info
|
||||
);
|
||||
selectedConnectionSourceRef.current.onChange(selectedConnectionSourceRef.current.options);
|
||||
selectedConnectionRef.current.source.onChange(selectedConnectionRef.current.source.options);
|
||||
|
||||
setSelectedConnection(undefined);
|
||||
setSelectedConnectionSource(undefined);
|
||||
scene.connections.select(undefined);
|
||||
scene.connections.updateState();
|
||||
scene.save();
|
||||
}
|
||||
} else {
|
||||
// Prevent removing event listener if key is not delete
|
||||
@ -71,28 +83,36 @@ export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
|
||||
|
||||
if (shouldResetSelectedConnection) {
|
||||
setSelectedConnection(undefined);
|
||||
setSelectedConnectionSource(undefined);
|
||||
scene.connections.select(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const selectConnection = (connection: CanvasConnection, source: ElementState) => {
|
||||
const selectConnection = (connection: ConnectionState) => {
|
||||
if (scene.isEditingEnabled) {
|
||||
setSelectedConnection(connection);
|
||||
setSelectedConnectionSource(source);
|
||||
scene.connections.select(connection);
|
||||
|
||||
document.addEventListener('keyup', onKeyUp);
|
||||
scene.selecto!.rootContainer!.addEventListener('click', clearSelectedConnection);
|
||||
}
|
||||
};
|
||||
|
||||
// Flat list of all connections
|
||||
const findConnections = useCallback(() => {
|
||||
return getConnections(scene.byName);
|
||||
}, [scene.byName]);
|
||||
// @TODO revisit, currently returning last row index for field
|
||||
const getRowIndex = (fieldName: string | undefined) => {
|
||||
if (fieldName) {
|
||||
const series = scene.context.getPanelData()?.series[0];
|
||||
const field = series?.fields.find((f) => (f.name = fieldName));
|
||||
const data = field?.values;
|
||||
|
||||
return data ? data.length - 1 : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Figure out target and then target's relative coordinates drawing (if no target do parent)
|
||||
const renderConnections = () => {
|
||||
return findConnections().map((v, idx) => {
|
||||
return scene.connections.state.map((v, idx) => {
|
||||
const { source, target, info } = v;
|
||||
const sourceRect = source.div?.getBoundingClientRect();
|
||||
const parent = source.div?.parentElement;
|
||||
@ -129,13 +149,21 @@ export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
|
||||
y2 = parentVerticalCenter - (info.target.y * parentRect.height) / 2;
|
||||
}
|
||||
|
||||
const isSelected = selectedConnection === info;
|
||||
const selectedStyles = { stroke: '#44aaff', strokeWidth: 3 };
|
||||
const isSelected = selectedConnection === v && scene.panel.context.instanceState.selectedConnection;
|
||||
|
||||
const strokeColor = info.color ? scene.context.getColor(info.color).value() : defaultArrowColor;
|
||||
const lastRowIndex = getRowIndex(info.size?.field);
|
||||
|
||||
const strokeWidth = info.size ? scene.context.getScale(info.size).get(lastRowIndex) : defaultArrowSize;
|
||||
|
||||
const connectionCursorStyle = scene.isEditingEnabled ? 'grab' : '';
|
||||
const selectedStyles = { stroke: '#44aaff', strokeOpacity: 0.6, strokeWidth: strokeWidth + 5 };
|
||||
|
||||
const CONNECTION_HEAD_ID = `connectionHead-${headId + Math.random()}`;
|
||||
|
||||
return (
|
||||
<svg className={styles.connection} key={idx}>
|
||||
<g onClick={() => selectConnection(info, source)}>
|
||||
<g onClick={() => selectConnection(v)}>
|
||||
<defs>
|
||||
<marker
|
||||
id={CONNECTION_HEAD_ID}
|
||||
@ -144,17 +172,18 @@ export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
|
||||
refX="10"
|
||||
refY="3.5"
|
||||
orient="auto"
|
||||
stroke={defaultArrowColor}
|
||||
stroke={strokeColor}
|
||||
>
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill={defaultArrowColor} />
|
||||
<polygon points="0 0, 10 3.5, 0 7" fill={strokeColor} />
|
||||
</marker>
|
||||
</defs>
|
||||
<line
|
||||
id={`${CONNECTION_LINE_ID}_transparent`}
|
||||
cursor={connectionCursorStyle}
|
||||
stroke="transparent"
|
||||
pointerEvents="auto"
|
||||
stroke="transparent"
|
||||
strokeWidth={15}
|
||||
style={isSelected ? selectedStyles : {}}
|
||||
x1={x1}
|
||||
y1={y1}
|
||||
x2={x2}
|
||||
@ -162,15 +191,14 @@ export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
|
||||
/>
|
||||
<line
|
||||
id={CONNECTION_LINE_ID}
|
||||
stroke={defaultArrowColor}
|
||||
stroke={strokeColor}
|
||||
pointerEvents="auto"
|
||||
strokeWidth={2}
|
||||
strokeWidth={strokeWidth}
|
||||
markerEnd={`url(#${CONNECTION_HEAD_ID})`}
|
||||
x1={x1}
|
||||
y1={y1}
|
||||
x2={x2}
|
||||
y2={y2}
|
||||
style={isSelected ? selectedStyles : {}}
|
||||
cursor={connectionCursorStyle}
|
||||
/>
|
||||
</g>
|
||||
|
@ -1,12 +1,15 @@
|
||||
import React from 'react';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { ConnectionPath } from 'app/features/canvas';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { CanvasConnection, ConnectionPath } from 'app/features/canvas';
|
||||
import { ElementState } from 'app/features/canvas/runtime/element';
|
||||
import { Scene } from 'app/features/canvas/runtime/scene';
|
||||
|
||||
import { CONNECTION_ANCHOR_ALT, ConnectionAnchors, CONNECTION_ANCHOR_HIGHLIGHT_OFFSET } from './ConnectionAnchors';
|
||||
import { ConnectionSVG } from './ConnectionSVG';
|
||||
import { isConnectionSource, isConnectionTarget } from './utils';
|
||||
import { ConnectionState } from './types';
|
||||
import { getConnections, isConnectionSource, isConnectionTarget } from './utils';
|
||||
|
||||
export class Connections {
|
||||
scene: Scene;
|
||||
@ -17,11 +20,35 @@ export class Connections {
|
||||
connectionTarget?: ElementState;
|
||||
isDrawingConnection?: boolean;
|
||||
didConnectionLeaveHighlight?: boolean;
|
||||
state: ConnectionState[] = [];
|
||||
readonly selection = new BehaviorSubject<ConnectionState | undefined>(undefined);
|
||||
|
||||
constructor(scene: Scene) {
|
||||
this.scene = scene;
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
select = (connection: ConnectionState | undefined) => {
|
||||
if (connection === this.selection.value) {
|
||||
return;
|
||||
}
|
||||
this.selection.next(connection);
|
||||
};
|
||||
|
||||
updateState = () => {
|
||||
const s = this.selection.value;
|
||||
this.state = getConnections(this.scene.byName);
|
||||
|
||||
if (s) {
|
||||
for (let c of this.state) {
|
||||
if (c.source === s.source && c.index === s.index) {
|
||||
this.selection.next(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
setConnectionAnchorRef = (anchorElement: HTMLDivElement) => {
|
||||
this.connectionAnchorDiv = anchorElement;
|
||||
};
|
||||
@ -174,8 +201,14 @@ export class Connections {
|
||||
y: targetY,
|
||||
},
|
||||
targetName: targetName,
|
||||
color: 'white',
|
||||
size: 10,
|
||||
color: {
|
||||
fixed: config.theme2.colors.text.primary,
|
||||
},
|
||||
size: {
|
||||
fixed: 2,
|
||||
min: 1,
|
||||
max: 10,
|
||||
},
|
||||
path: ConnectionPath.Straight,
|
||||
};
|
||||
|
||||
@ -199,6 +232,8 @@ export class Connections {
|
||||
}
|
||||
|
||||
this.isDrawingConnection = false;
|
||||
this.updateState();
|
||||
this.scene.save();
|
||||
}
|
||||
};
|
||||
|
||||
@ -224,6 +259,13 @@ export class Connections {
|
||||
this.scene.selecto?.rootContainer?.addEventListener('mousemove', this.connectionListener);
|
||||
};
|
||||
|
||||
onChange = (current: ConnectionState, update: CanvasConnection) => {
|
||||
const connections = current.source.options.connections?.splice(0) ?? [];
|
||||
connections[current.index] = update;
|
||||
current.source.onChange({ ...current.source.options, connections });
|
||||
this.updateState();
|
||||
};
|
||||
|
||||
// used for moveable actions
|
||||
connectionsNeedUpdate = (element: ElementState): boolean => {
|
||||
return isConnectionSource(element) || isConnectionTarget(element, this.scene.byName);
|
||||
|
42
public/app/plugins/panel/canvas/editor/connectionEditor.tsx
Normal file
42
public/app/plugins/panel/canvas/editor/connectionEditor.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import { get as lodashGet } from 'lodash';
|
||||
|
||||
import { NestedPanelOptions, NestedValueAccess } from '@grafana/data/src/utils/OptionsUIBuilders';
|
||||
import { CanvasConnection } from 'app/features/canvas';
|
||||
import { Scene } from 'app/features/canvas/runtime/scene';
|
||||
import { setOptionImmutably } from 'app/features/dashboard/components/PanelEditor/utils';
|
||||
|
||||
import { ConnectionState } from '../types';
|
||||
|
||||
import { optionBuilder } from './options';
|
||||
|
||||
export interface CanvasConnectionEditorOptions {
|
||||
connection: ConnectionState;
|
||||
scene: Scene;
|
||||
category?: string[];
|
||||
}
|
||||
|
||||
export function getConnectionEditor(opts: CanvasConnectionEditorOptions): NestedPanelOptions<CanvasConnection> {
|
||||
return {
|
||||
category: opts.category,
|
||||
path: '--', // not used!
|
||||
|
||||
values: (parent: NestedValueAccess) => ({
|
||||
getValue: (path: string) => {
|
||||
return lodashGet(opts.connection.info, path);
|
||||
},
|
||||
// TODO: Fix this any (maybe a dimension supplier?)
|
||||
onChange: (path: string, value: any) => {
|
||||
console.log(value, typeof value);
|
||||
let options = opts.connection.info;
|
||||
options = setOptionImmutably(options, path, value);
|
||||
opts.scene.connections.onChange(opts.connection, options);
|
||||
},
|
||||
}),
|
||||
|
||||
build: (builder, context) => {
|
||||
const ctx = { ...context, options: opts.connection.info };
|
||||
optionBuilder.addColor(builder, ctx);
|
||||
optionBuilder.addSize(builder, ctx);
|
||||
},
|
||||
};
|
||||
}
|
@ -1,11 +1,13 @@
|
||||
import { PanelOptionsSupplier } from '@grafana/data/src/panel/PanelPlugin';
|
||||
import { CanvasElementOptions } from 'app/features/canvas';
|
||||
import { ColorDimensionEditor, ResourceDimensionEditor } from 'app/features/dimensions/editors';
|
||||
import { CanvasConnection, CanvasElementOptions } from 'app/features/canvas';
|
||||
import { ColorDimensionEditor, ResourceDimensionEditor, ScaleDimensionEditor } from 'app/features/dimensions/editors';
|
||||
import { BackgroundSizeEditor } from 'app/features/dimensions/editors/BackgroundSizeEditor';
|
||||
|
||||
interface OptionSuppliers {
|
||||
addBackground: PanelOptionsSupplier<CanvasElementOptions>;
|
||||
addBorder: PanelOptionsSupplier<CanvasElementOptions>;
|
||||
addColor: PanelOptionsSupplier<CanvasConnection>;
|
||||
addSize: PanelOptionsSupplier<CanvasConnection>;
|
||||
}
|
||||
|
||||
const getCategoryName = (str: string, type: string | undefined) => {
|
||||
@ -81,4 +83,41 @@ export const optionBuilder: OptionSuppliers = {
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
addColor: (builder, context) => {
|
||||
const category = ['Color'];
|
||||
builder.addCustomEditor({
|
||||
category,
|
||||
id: 'color',
|
||||
path: 'color',
|
||||
name: 'Color',
|
||||
editor: ColorDimensionEditor,
|
||||
settings: {},
|
||||
defaultValue: {
|
||||
// Configured values
|
||||
fixed: '',
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
addSize: (builder, context) => {
|
||||
const category = ['Size'];
|
||||
builder.addCustomEditor({
|
||||
category,
|
||||
id: 'size',
|
||||
path: 'size',
|
||||
name: 'Size',
|
||||
editor: ScaleDimensionEditor,
|
||||
settings: {
|
||||
min: 1,
|
||||
max: 10,
|
||||
},
|
||||
defaultValue: {
|
||||
// Configured values
|
||||
fixed: 2,
|
||||
min: 1,
|
||||
max: 10,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@ -2,6 +2,7 @@ import { FieldConfigProperty, PanelOptionsEditorBuilder, PanelPlugin } from '@gr
|
||||
import { FrameState } from 'app/features/canvas/runtime/frame';
|
||||
|
||||
import { CanvasPanel, InstanceState } from './CanvasPanel';
|
||||
import { getConnectionEditor } from './editor/connectionEditor';
|
||||
import { getElementEditor } from './editor/elementEditor';
|
||||
import { getLayerEditor } from './editor/layerEditor';
|
||||
import { canvasMigrationHandler } from './migrations';
|
||||
@ -44,6 +45,8 @@ export const plugin = new PanelPlugin<PanelOptions>(CanvasPanel)
|
||||
builder.addNestedOptions(getLayerEditor(state));
|
||||
|
||||
const selection = state.selected;
|
||||
const connectionSelection = state.selectedConnection;
|
||||
|
||||
if (selection?.length === 1) {
|
||||
const element = selection[0];
|
||||
if (!(element instanceof FrameState)) {
|
||||
@ -56,5 +59,15 @@ export const plugin = new PanelPlugin<PanelOptions>(CanvasPanel)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (connectionSelection) {
|
||||
builder.addNestedOptions(
|
||||
getConnectionEditor({
|
||||
category: ['Selected connection'],
|
||||
connection: connectionSelection,
|
||||
scene: state.scene,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -20,6 +20,7 @@ export interface DropNode extends DragNode {
|
||||
export enum InlineEditTabs {
|
||||
ElementManagement = 'element-management',
|
||||
SelectedElement = 'selected-element',
|
||||
SelectedConnection = 'selected-connection',
|
||||
}
|
||||
|
||||
export type AnchorPoint = {
|
||||
@ -33,7 +34,8 @@ export interface CanvasTooltipPayload {
|
||||
isOpen?: boolean;
|
||||
}
|
||||
|
||||
export interface ConnectionInfo {
|
||||
export interface ConnectionState {
|
||||
index: number; // array index from the source
|
||||
source: ElementState;
|
||||
target: ElementState;
|
||||
info: CanvasConnection;
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { isNumber, isString } from 'lodash';
|
||||
|
||||
import { AppEvents, Field, LinkModel, PluginState, SelectableValue } from '@grafana/data';
|
||||
import { hasAlphaPanels } from 'app/core/config';
|
||||
|
||||
@ -16,7 +18,7 @@ import { FrameState } from '../../../features/canvas/runtime/frame';
|
||||
import { Scene, SelectionParams } from '../../../features/canvas/runtime/scene';
|
||||
import { DimensionContext } from '../../../features/dimensions';
|
||||
|
||||
import { AnchorPoint, ConnectionInfo } from './types';
|
||||
import { AnchorPoint, ConnectionState } from './types';
|
||||
|
||||
export function doSelect(scene: Scene, element: ElementState | FrameState) {
|
||||
try {
|
||||
@ -139,19 +141,29 @@ export function isConnectionTarget(element: ElementState, sceneByName: Map<strin
|
||||
}
|
||||
|
||||
export function getConnections(sceneByName: Map<string, ElementState>) {
|
||||
const connections: ConnectionInfo[] = [];
|
||||
const connections: ConnectionState[] = [];
|
||||
for (let v of sceneByName.values()) {
|
||||
if (v.options.connections) {
|
||||
for (let c of v.options.connections) {
|
||||
v.options.connections.forEach((c, index) => {
|
||||
// @TODO Remove after v10.x
|
||||
if (isString(c.color)) {
|
||||
c.color = { fixed: c.color };
|
||||
}
|
||||
|
||||
if (isNumber(c.size)) {
|
||||
c.size = { fixed: 2, min: 1, max: 10 };
|
||||
}
|
||||
|
||||
const target = c.targetName ? sceneByName.get(c.targetName) : v.parent;
|
||||
if (target) {
|
||||
connections.push({
|
||||
index,
|
||||
source: v,
|
||||
target,
|
||||
info: c,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,7 +171,7 @@ export function getConnections(sceneByName: Map<string, ElementState>) {
|
||||
}
|
||||
|
||||
export function getConnectionsByTarget(element: ElementState, scene: Scene) {
|
||||
return getConnections(scene.byName).filter((connection) => connection.target === element);
|
||||
return scene.connections.state.filter((connection) => connection.target === element);
|
||||
}
|
||||
|
||||
export function updateConnectionsForSource(element: ElementState, scene: Scene) {
|
||||
|
Reference in New Issue
Block a user