mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Canvas: Connections positioning ux improvements (#62516)
This commit is contained in:
parent
bf2cf76cad
commit
a92c081a33
@ -78,6 +78,8 @@ export class Scene {
|
||||
tooltipCallback?: (tooltip: CanvasTooltipPayload | undefined) => void;
|
||||
tooltip?: CanvasTooltipPayload;
|
||||
|
||||
moveableActionCallback?: (moved: boolean) => void;
|
||||
|
||||
readonly editModeEnabled = new BehaviorSubject<boolean>(false);
|
||||
subscription: Subscription;
|
||||
|
||||
@ -407,13 +409,29 @@ export class Scene {
|
||||
})
|
||||
.on('drag', (event) => {
|
||||
const targetedElement = this.findElementByTarget(event.target);
|
||||
targetedElement!.applyDrag(event);
|
||||
if (targetedElement) {
|
||||
targetedElement.applyDrag(event);
|
||||
|
||||
if (this.connections.connectionsNeedUpdate(targetedElement) && this.moveableActionCallback) {
|
||||
this.moveableActionCallback(true);
|
||||
}
|
||||
}
|
||||
})
|
||||
.on('dragGroup', (e) => {
|
||||
e.events.forEach((event) => {
|
||||
let needsUpdate = false;
|
||||
for (let event of e.events) {
|
||||
const targetedElement = this.findElementByTarget(event.target);
|
||||
targetedElement!.applyDrag(event);
|
||||
});
|
||||
if (targetedElement) {
|
||||
targetedElement.applyDrag(event);
|
||||
if (!needsUpdate) {
|
||||
needsUpdate = this.connections.connectionsNeedUpdate(targetedElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needsUpdate && this.moveableActionCallback) {
|
||||
this.moveableActionCallback(true);
|
||||
}
|
||||
})
|
||||
.on('dragGroupEnd', (e) => {
|
||||
e.events.forEach((event) => {
|
||||
@ -450,14 +468,32 @@ export class Scene {
|
||||
})
|
||||
.on('resize', (event) => {
|
||||
const targetedElement = this.findElementByTarget(event.target);
|
||||
targetedElement!.applyResize(event);
|
||||
if (targetedElement) {
|
||||
targetedElement.applyResize(event);
|
||||
|
||||
if (this.connections.connectionsNeedUpdate(targetedElement) && this.moveableActionCallback) {
|
||||
this.moveableActionCallback(true);
|
||||
}
|
||||
}
|
||||
this.moved.next(Date.now()); // TODO only on end
|
||||
})
|
||||
.on('resizeGroup', (e) => {
|
||||
e.events.forEach((event) => {
|
||||
let needsUpdate = false;
|
||||
for (let event of e.events) {
|
||||
const targetedElement = this.findElementByTarget(event.target);
|
||||
targetedElement!.applyResize(event);
|
||||
});
|
||||
if (targetedElement) {
|
||||
targetedElement.applyResize(event);
|
||||
|
||||
if (!needsUpdate) {
|
||||
needsUpdate = this.connections.connectionsNeedUpdate(targetedElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needsUpdate && this.moveableActionCallback) {
|
||||
this.moveableActionCallback(true);
|
||||
}
|
||||
|
||||
this.moved.next(Date.now()); // TODO only on end
|
||||
})
|
||||
.on('resizeEnd', (event) => {
|
||||
|
@ -21,6 +21,7 @@ interface State {
|
||||
openInlineEdit: boolean;
|
||||
openSetBackground: boolean;
|
||||
contextMenuAnchorPoint: AnchorPoint;
|
||||
moveableAction: boolean;
|
||||
}
|
||||
|
||||
export interface InstanceState {
|
||||
@ -56,6 +57,7 @@ export class CanvasPanel extends Component<Props, State> {
|
||||
openInlineEdit: false,
|
||||
openSetBackground: false,
|
||||
contextMenuAnchorPoint: { x: 0, y: 0 },
|
||||
moveableAction: false,
|
||||
};
|
||||
|
||||
// Only the initial options are ever used.
|
||||
@ -72,6 +74,7 @@ export class CanvasPanel extends Component<Props, State> {
|
||||
this.scene.inlineEditingCallback = this.openInlineEdit;
|
||||
this.scene.setBackgroundCallback = this.openSetBackground;
|
||||
this.scene.tooltipCallback = this.tooltipCallback;
|
||||
this.scene.moveableActionCallback = this.moveableActionCallback;
|
||||
|
||||
this.subs.add(
|
||||
this.props.eventBus.subscribe(PanelEditEnteredEvent, (evt: PanelEditEnteredEvent) => {
|
||||
@ -184,6 +187,10 @@ export class CanvasPanel extends Component<Props, State> {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (this.state.moveableAction !== nextState.moveableAction) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// After editing, the options are valid, but the scene was in a different panel or inline editing mode has changed
|
||||
const inlineEditingSwitched = this.props.options.inlineEditing !== nextProps.options.inlineEditing;
|
||||
const shouldShowAdvancedTypesSwitched =
|
||||
@ -235,6 +242,11 @@ export class CanvasPanel extends Component<Props, State> {
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
moveableActionCallback = (updated: boolean) => {
|
||||
this.setState({ moveableAction: updated });
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
closeInlineEdit = () => {
|
||||
this.setState({ openInlineEdit: false });
|
||||
isInlineEditOpen = false;
|
||||
|
@ -7,18 +7,14 @@ 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';
|
||||
|
||||
type Props = {
|
||||
setSVGRef: (anchorElement: SVGSVGElement) => void;
|
||||
setLineRef: (anchorElement: SVGLineElement) => void;
|
||||
scene: Scene;
|
||||
};
|
||||
|
||||
interface ConnectionInfo {
|
||||
source: ElementState;
|
||||
target: ElementState;
|
||||
info: CanvasConnection;
|
||||
}
|
||||
|
||||
let idCounter = 0;
|
||||
export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
@ -89,22 +85,7 @@ export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
|
||||
|
||||
// Flat list of all connections
|
||||
const findConnections = useCallback(() => {
|
||||
const connections: ConnectionInfo[] = [];
|
||||
for (let v of scene.byName.values()) {
|
||||
if (v.options.connections) {
|
||||
for (let c of v.options.connections) {
|
||||
const target = c.targetName ? scene.byName.get(c.targetName) : v.parent;
|
||||
if (target) {
|
||||
connections.push({
|
||||
source: v,
|
||||
target,
|
||||
info: c,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return connections;
|
||||
return getConnections(scene.byName);
|
||||
}, [scene.byName]);
|
||||
|
||||
// Figure out target and then target's relative coordinates drawing (if no target do parent)
|
||||
|
@ -6,6 +6,7 @@ import { Scene } from 'app/features/canvas/runtime/scene';
|
||||
|
||||
import { CONNECTION_ANCHOR_ALT, ConnectionAnchors } from './ConnectionAnchors';
|
||||
import { ConnectionSVG } from './ConnectionSVG';
|
||||
import { isConnectionSource, isConnectionTarget } from './utils';
|
||||
|
||||
export class Connections {
|
||||
scene: Scene;
|
||||
@ -216,6 +217,11 @@ export class Connections {
|
||||
this.scene.selecto?.rootContainer?.addEventListener('mousemove', this.connectionListener);
|
||||
};
|
||||
|
||||
// used for moveable actions
|
||||
connectionsNeedUpdate = (element: ElementState): boolean => {
|
||||
return isConnectionSource(element) || isConnectionTarget(element, this.scene.byName);
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { CanvasConnection } from '../../../features/canvas';
|
||||
import { ElementState } from '../../../features/canvas/runtime/element';
|
||||
|
||||
export enum LayerActionID {
|
||||
@ -31,3 +32,9 @@ export interface CanvasTooltipPayload {
|
||||
element: ElementState | undefined;
|
||||
isOpen?: boolean;
|
||||
}
|
||||
|
||||
export interface ConnectionInfo {
|
||||
source: ElementState;
|
||||
target: ElementState;
|
||||
info: CanvasConnection;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ import { FrameState } from '../../../features/canvas/runtime/frame';
|
||||
import { Scene, SelectionParams } from '../../../features/canvas/runtime/scene';
|
||||
import { DimensionContext } from '../../../features/dimensions';
|
||||
|
||||
import { AnchorPoint } from './types';
|
||||
import { AnchorPoint, ConnectionInfo } from './types';
|
||||
|
||||
export function doSelect(scene: Scene, element: ElementState | FrameState) {
|
||||
try {
|
||||
@ -129,3 +129,31 @@ export function getDataLinks(ctx: DimensionContext, cfg: TextConfig, textData: s
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
export function isConnectionSource(element: ElementState) {
|
||||
return element.options.connections && element.options.connections.length > 0;
|
||||
}
|
||||
export function isConnectionTarget(element: ElementState, sceneByName: Map<string, ElementState>) {
|
||||
const connections = getConnections(sceneByName);
|
||||
return connections.some((connection) => connection.target === element);
|
||||
}
|
||||
|
||||
export function getConnections(sceneByName: Map<string, ElementState>) {
|
||||
const connections: ConnectionInfo[] = [];
|
||||
for (let v of sceneByName.values()) {
|
||||
if (v.options.connections) {
|
||||
for (let c of v.options.connections) {
|
||||
const target = c.targetName ? sceneByName.get(c.targetName) : v.parent;
|
||||
if (target) {
|
||||
connections.push({
|
||||
source: v,
|
||||
target,
|
||||
info: c,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return connections;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user