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;
|
tooltipCallback?: (tooltip: CanvasTooltipPayload | undefined) => void;
|
||||||
tooltip?: CanvasTooltipPayload;
|
tooltip?: CanvasTooltipPayload;
|
||||||
|
|
||||||
|
moveableActionCallback?: (moved: boolean) => void;
|
||||||
|
|
||||||
readonly editModeEnabled = new BehaviorSubject<boolean>(false);
|
readonly editModeEnabled = new BehaviorSubject<boolean>(false);
|
||||||
subscription: Subscription;
|
subscription: Subscription;
|
||||||
|
|
||||||
@ -407,13 +409,29 @@ export class Scene {
|
|||||||
})
|
})
|
||||||
.on('drag', (event) => {
|
.on('drag', (event) => {
|
||||||
const targetedElement = this.findElementByTarget(event.target);
|
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) => {
|
.on('dragGroup', (e) => {
|
||||||
e.events.forEach((event) => {
|
let needsUpdate = false;
|
||||||
|
for (let event of e.events) {
|
||||||
const targetedElement = this.findElementByTarget(event.target);
|
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) => {
|
.on('dragGroupEnd', (e) => {
|
||||||
e.events.forEach((event) => {
|
e.events.forEach((event) => {
|
||||||
@ -450,14 +468,32 @@ export class Scene {
|
|||||||
})
|
})
|
||||||
.on('resize', (event) => {
|
.on('resize', (event) => {
|
||||||
const targetedElement = this.findElementByTarget(event.target);
|
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
|
this.moved.next(Date.now()); // TODO only on end
|
||||||
})
|
})
|
||||||
.on('resizeGroup', (e) => {
|
.on('resizeGroup', (e) => {
|
||||||
e.events.forEach((event) => {
|
let needsUpdate = false;
|
||||||
|
for (let event of e.events) {
|
||||||
const targetedElement = this.findElementByTarget(event.target);
|
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
|
this.moved.next(Date.now()); // TODO only on end
|
||||||
})
|
})
|
||||||
.on('resizeEnd', (event) => {
|
.on('resizeEnd', (event) => {
|
||||||
|
@ -21,6 +21,7 @@ interface State {
|
|||||||
openInlineEdit: boolean;
|
openInlineEdit: boolean;
|
||||||
openSetBackground: boolean;
|
openSetBackground: boolean;
|
||||||
contextMenuAnchorPoint: AnchorPoint;
|
contextMenuAnchorPoint: AnchorPoint;
|
||||||
|
moveableAction: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InstanceState {
|
export interface InstanceState {
|
||||||
@ -56,6 +57,7 @@ export class CanvasPanel extends Component<Props, State> {
|
|||||||
openInlineEdit: false,
|
openInlineEdit: false,
|
||||||
openSetBackground: false,
|
openSetBackground: false,
|
||||||
contextMenuAnchorPoint: { x: 0, y: 0 },
|
contextMenuAnchorPoint: { x: 0, y: 0 },
|
||||||
|
moveableAction: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only the initial options are ever used.
|
// 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.inlineEditingCallback = this.openInlineEdit;
|
||||||
this.scene.setBackgroundCallback = this.openSetBackground;
|
this.scene.setBackgroundCallback = this.openSetBackground;
|
||||||
this.scene.tooltipCallback = this.tooltipCallback;
|
this.scene.tooltipCallback = this.tooltipCallback;
|
||||||
|
this.scene.moveableActionCallback = this.moveableActionCallback;
|
||||||
|
|
||||||
this.subs.add(
|
this.subs.add(
|
||||||
this.props.eventBus.subscribe(PanelEditEnteredEvent, (evt: PanelEditEnteredEvent) => {
|
this.props.eventBus.subscribe(PanelEditEnteredEvent, (evt: PanelEditEnteredEvent) => {
|
||||||
@ -184,6 +187,10 @@ export class CanvasPanel extends Component<Props, State> {
|
|||||||
changed = true;
|
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
|
// 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 inlineEditingSwitched = this.props.options.inlineEditing !== nextProps.options.inlineEditing;
|
||||||
const shouldShowAdvancedTypesSwitched =
|
const shouldShowAdvancedTypesSwitched =
|
||||||
@ -235,6 +242,11 @@ export class CanvasPanel extends Component<Props, State> {
|
|||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
moveableActionCallback = (updated: boolean) => {
|
||||||
|
this.setState({ moveableAction: updated });
|
||||||
|
this.forceUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
closeInlineEdit = () => {
|
closeInlineEdit = () => {
|
||||||
this.setState({ openInlineEdit: false });
|
this.setState({ openInlineEdit: false });
|
||||||
isInlineEditOpen = false;
|
isInlineEditOpen = false;
|
||||||
|
@ -7,18 +7,14 @@ import { CanvasConnection } from 'app/features/canvas/element';
|
|||||||
import { ElementState } from 'app/features/canvas/runtime/element';
|
import { ElementState } from 'app/features/canvas/runtime/element';
|
||||||
import { Scene } from 'app/features/canvas/runtime/scene';
|
import { Scene } from 'app/features/canvas/runtime/scene';
|
||||||
|
|
||||||
|
import { getConnections } from './utils';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setSVGRef: (anchorElement: SVGSVGElement) => void;
|
setSVGRef: (anchorElement: SVGSVGElement) => void;
|
||||||
setLineRef: (anchorElement: SVGLineElement) => void;
|
setLineRef: (anchorElement: SVGLineElement) => void;
|
||||||
scene: Scene;
|
scene: Scene;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface ConnectionInfo {
|
|
||||||
source: ElementState;
|
|
||||||
target: ElementState;
|
|
||||||
info: CanvasConnection;
|
|
||||||
}
|
|
||||||
|
|
||||||
let idCounter = 0;
|
let idCounter = 0;
|
||||||
export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
|
export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
@ -89,22 +85,7 @@ export const ConnectionSVG = ({ setSVGRef, setLineRef, scene }: Props) => {
|
|||||||
|
|
||||||
// Flat list of all connections
|
// Flat list of all connections
|
||||||
const findConnections = useCallback(() => {
|
const findConnections = useCallback(() => {
|
||||||
const connections: ConnectionInfo[] = [];
|
return getConnections(scene.byName);
|
||||||
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;
|
|
||||||
}, [scene.byName]);
|
}, [scene.byName]);
|
||||||
|
|
||||||
// Figure out target and then target's relative coordinates drawing (if no target do parent)
|
// 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 { CONNECTION_ANCHOR_ALT, ConnectionAnchors } from './ConnectionAnchors';
|
||||||
import { ConnectionSVG } from './ConnectionSVG';
|
import { ConnectionSVG } from './ConnectionSVG';
|
||||||
|
import { isConnectionSource, isConnectionTarget } from './utils';
|
||||||
|
|
||||||
export class Connections {
|
export class Connections {
|
||||||
scene: Scene;
|
scene: Scene;
|
||||||
@ -216,6 +217,11 @@ export class Connections {
|
|||||||
this.scene.selecto?.rootContainer?.addEventListener('mousemove', this.connectionListener);
|
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() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { CanvasConnection } from '../../../features/canvas';
|
||||||
import { ElementState } from '../../../features/canvas/runtime/element';
|
import { ElementState } from '../../../features/canvas/runtime/element';
|
||||||
|
|
||||||
export enum LayerActionID {
|
export enum LayerActionID {
|
||||||
@ -31,3 +32,9 @@ export interface CanvasTooltipPayload {
|
|||||||
element: ElementState | undefined;
|
element: ElementState | undefined;
|
||||||
isOpen?: boolean;
|
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 { Scene, SelectionParams } from '../../../features/canvas/runtime/scene';
|
||||||
import { DimensionContext } from '../../../features/dimensions';
|
import { DimensionContext } from '../../../features/dimensions';
|
||||||
|
|
||||||
import { AnchorPoint } from './types';
|
import { AnchorPoint, ConnectionInfo } from './types';
|
||||||
|
|
||||||
export function doSelect(scene: Scene, element: ElementState | FrameState) {
|
export function doSelect(scene: Scene, element: ElementState | FrameState) {
|
||||||
try {
|
try {
|
||||||
@ -129,3 +129,31 @@ export function getDataLinks(ctx: DimensionContext, cfg: TextConfig, textData: s
|
|||||||
|
|
||||||
return links;
|
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