Canvas: Connections positioning ux improvements (#62516)

This commit is contained in:
Adela Almasan 2023-01-30 15:50:10 -06:00 committed by GitHub
parent bf2cf76cad
commit a92c081a33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 101 additions and 31 deletions

View File

@ -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) => {

View File

@ -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;

View File

@ -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)

View File

@ -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 (
<>

View File

@ -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;
}

View File

@ -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;
}