NIFI-13141: (#8752) - Adding a dragging flag to not reset positioning for labels and connection bends if the user is currently performing an action.

This closes #8752
This commit is contained in:
Matt Gilman 2024-05-06 16:15:15 -04:00 committed by GitHub
parent 030435d996
commit e7facda912
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 291 additions and 199 deletions

View File

@ -343,8 +343,9 @@ export class ConnectionManager {
* Saves the connection entry specified by d with the new configuration specified * Saves the connection entry specified by d with the new configuration specified
* in connection. * in connection.
* *
* @param {type} d * @param d
* @param {type} connection * @param connection
* @param restoreOnFailure
*/ */
private save(d: any, connection: any, restoreOnFailure?: any): void { private save(d: any, connection: any, restoreOnFailure?: any): void {
const updateConnection: UpdateComponentRequest = { const updateConnection: UpdateComponentRequest = {
@ -616,7 +617,7 @@ export class ConnectionManager {
// if we are currently dragging the endpoint to a new target, use that // if we are currently dragging the endpoint to a new target, use that
// position, otherwise we need to calculate it for the current target // position, otherwise we need to calculate it for the current target
if (d.end?.dragging) { if (d.end?.endPointDragging) {
// since we're dragging, use the same object thats bound to the endpoint drag event // since we're dragging, use the same object thats bound to the endpoint drag event
end = d.end; end = d.end;
@ -1844,196 +1845,231 @@ export class ConnectionManager {
// handle bend point drag events // handle bend point drag events
this.bendPointDrag = d3 this.bendPointDrag = d3
.drag() .drag()
.on('start', function (event) { .on('start', function (this: any, event) {
// stop further propagation // stop further propagation
event.sourceEvent.stopPropagation(); event.sourceEvent.stopPropagation();
// indicate dragging start
const connection: any = d3.select(this.parentNode);
const connectionData: any = connection.datum();
connectionData.dragging = true;
}) })
.on('drag', function (this: any, event, d: any) { .on('drag', function (this: any, event, d: any) {
self.snapEnabled = !event.sourceEvent.shiftKey; const connection: any = d3.select(this.parentNode);
d.x = self.snapEnabled const connectionData: any = connection.datum();
? Math.round(event.x / ConnectionManager.SNAP_ALIGNMENT_PIXELS) *
ConnectionManager.SNAP_ALIGNMENT_PIXELS
: event.x;
d.y = self.snapEnabled
? Math.round(event.y / ConnectionManager.SNAP_ALIGNMENT_PIXELS) *
ConnectionManager.SNAP_ALIGNMENT_PIXELS
: event.y;
// redraw this connection if (connectionData.dragging) {
self.updateConnections(d3.select(this.parentNode), { self.snapEnabled = !event.sourceEvent.shiftKey;
updatePath: true, d.x = self.snapEnabled
updateLabel: false ? Math.round(event.x / ConnectionManager.SNAP_ALIGNMENT_PIXELS) *
}); ConnectionManager.SNAP_ALIGNMENT_PIXELS
: event.x;
d.y = self.snapEnabled
? Math.round(event.y / ConnectionManager.SNAP_ALIGNMENT_PIXELS) *
ConnectionManager.SNAP_ALIGNMENT_PIXELS
: event.y;
// redraw this connection
self.updateConnections(d3.select(this.parentNode), {
updatePath: true,
updateLabel: false
});
}
}) })
.on('end', function (this: any, event) { .on('end', function (this: any, event) {
const connection: any = d3.select(this.parentNode); const connection: any = d3.select(this.parentNode);
const connectionData: any = connection.datum(); const connectionData: any = connection.datum();
const bends: Position[] = connection.selectAll('rect.midpoint').data();
// ensure the bend lengths are the same if (connectionData.dragging) {
if (bends.length === connectionData.component.bends.length) { const bends: Position[] = connection.selectAll('rect.midpoint').data();
// determine if the bend points have moved
let different = false;
for (let i = 0; i < bends.length && !different; i++) {
if (
bends[i].x !== connectionData.component.bends[i].x ||
bends[i].y !== connectionData.component.bends[i].y
) {
different = true;
}
}
// only save the updated bends if necessary // ensure the bend lengths are the same
if (different) { if (bends.length === connectionData.component.bends.length) {
self.save( // determine if the bend points have moved
connectionData, let different = false;
{ for (let i = 0; i < bends.length && !different; i++) {
id: connectionData.id, if (
bends: bends bends[i].x !== connectionData.component.bends[i].x ||
}, bends[i].y !== connectionData.component.bends[i].y
{ ) {
bends: [...connectionData.component.bends] different = true;
} }
); }
// only save the updated bends if necessary
if (different) {
self.save(
connectionData,
{
id: connectionData.id,
bends: bends
},
{
bends: [...connectionData.component.bends]
}
);
}
} }
} }
// stop further propagation // stop further propagation
event.sourceEvent.stopPropagation(); event.sourceEvent.stopPropagation();
// indicate dragging complete
connectionData.dragging = false;
}); });
// handle endpoint drag events // handle endpoint drag events
this.endpointDrag = d3 this.endpointDrag = d3
.drag() .drag()
.on('start', function (event, d: any) { .on('start', function (this: any, event, d: any) {
// indicate that dragging has begun // indicate that end point dragging has begun
d.dragging = true; d.endPointDragging = true;
// stop further propagation // stop further propagation
event.sourceEvent.stopPropagation(); event.sourceEvent.stopPropagation();
// indicate dragging start
const connection: any = d3.select(this.parentNode);
const connectionData: any = connection.datum();
connectionData.dragging = true;
}) })
.on('drag', function (this: any, event, d: any) { .on('drag', function (this: any, event, d: any) {
d.x = event.x - 8; const connection: any = d3.select(this.parentNode);
d.y = event.y - 8; const connectionData: any = connection.datum();
// ensure the new destination is valid if (connectionData.dragging) {
d3.select('g.hover').classed('connectable-destination', function () { d.x = event.x - 8;
return self.canvasUtils.isValidConnectionDestination(d3.select(this)); d.y = event.y - 8;
});
// redraw this connection // ensure the new destination is valid
self.updateConnections(d3.select(this.parentNode), { d3.select('g.hover').classed('connectable-destination', function () {
updatePath: true, return self.canvasUtils.isValidConnectionDestination(d3.select(this));
updateLabel: false });
});
// redraw this connection
self.updateConnections(d3.select(this.parentNode), {
updatePath: true,
updateLabel: false
});
}
}) })
.on('end', function (this: any, event, d: any) { .on('end', function (this: any, event, d: any) {
// indicate that dragging as stopped // indicate that end point dragging as stopped
d.dragging = false; d.endPointDragging = false;
// get the corresponding connection // get the corresponding connection
const connection: any = d3.select(this.parentNode); const connection: any = d3.select(this.parentNode);
const connectionData: any = connection.datum(); const connectionData: any = connection.datum();
// attempt to select a new destination if (connectionData.dragging) {
const destination: any = d3.select('g.connectable-destination'); // attempt to select a new destination
const destination: any = d3.select('g.connectable-destination');
// resets the connection if we're not over a new destination // resets the connection if we're not over a new destination
if (destination.empty()) { if (destination.empty()) {
self.updateConnections(connection, { self.updateConnections(connection, {
updatePath: true, updatePath: true,
updateLabel: false updateLabel: false
}); });
} else { } else {
const destinationData: any = destination.datum(); const destinationData: any = destination.datum();
// prompt for the new port if appropriate // prompt for the new port if appropriate
if ( if (
self.canvasUtils.isProcessGroup(destination) || self.canvasUtils.isProcessGroup(destination) ||
self.canvasUtils.isRemoteProcessGroup(destination) self.canvasUtils.isRemoteProcessGroup(destination)
) { ) {
// when the new destination is a group, show the edit connection dialog // when the new destination is a group, show the edit connection dialog
// to allow the user to select the desired port // to allow the user to select the desired port
self.store.dispatch( self.store.dispatch(
openEditConnectionDialog({ openEditConnectionDialog({
request: { request: {
type: ComponentType.Connection, type: ComponentType.Connection,
uri: connectionData.uri, uri: connectionData.uri,
entity: { ...connectionData }, entity: { ...connectionData },
newDestination: { newDestination: {
type: destinationData.type, type: destinationData.type,
groupId: destinationData.id, groupId: destinationData.id,
name: destinationData.permissions.canRead name: destinationData.permissions.canRead
? destinationData.component.name ? destinationData.component.name
: destinationData.id : destinationData.id
}
}
})
);
} else {
const destinationType: string = self.canvasUtils.getConnectableTypeForDestination(
destinationData.type
);
const payload: any = {
revision: self.client.getRevision(connectionData),
disconnectedNodeAcknowledged:
self.clusterConnectionService.isDisconnectionAcknowledged(),
component: {
id: connectionData.id,
destination: {
id: destinationData.id,
groupId: self.currentProcessGroupId,
type: destinationType
} }
} }
})
);
} else {
const destinationType: string = self.canvasUtils.getConnectableTypeForDestination(
destinationData.type
);
const payload: any = {
revision: self.client.getRevision(connectionData),
disconnectedNodeAcknowledged: self.clusterConnectionService.isDisconnectionAcknowledged(),
component: {
id: connectionData.id,
destination: {
id: destinationData.id,
groupId: self.currentProcessGroupId,
type: destinationType
}
}
};
// if this is a self loop and there are less than 2 bends, add them
if (connectionData.bends.length < 2 && connectionData.sourceId === destinationData.id) {
const rightCenter: any = {
x: destinationData.position.x + destinationData.dimensions.width,
y: destinationData.position.y + destinationData.dimensions.height / 2
}; };
payload.component.bends = []; // if this is a self loop and there are less than 2 bends, add them
payload.component.bends.push({ if (connectionData.bends.length < 2 && connectionData.sourceId === destinationData.id) {
x: rightCenter.x + ConnectionManager.SELF_LOOP_X_OFFSET, const rightCenter: any = {
y: rightCenter.y - ConnectionManager.SELF_LOOP_Y_OFFSET x: destinationData.position.x + destinationData.dimensions.width,
}); y: destinationData.position.y + destinationData.dimensions.height / 2
payload.component.bends.push({ };
x: rightCenter.x + ConnectionManager.SELF_LOOP_X_OFFSET,
y: rightCenter.y + ConnectionManager.SELF_LOOP_Y_OFFSET
});
}
self.store.dispatch( payload.component.bends = [];
updateConnection({ payload.component.bends.push({
request: { x: rightCenter.x + ConnectionManager.SELF_LOOP_X_OFFSET,
id: connectionData.id, y: rightCenter.y - ConnectionManager.SELF_LOOP_Y_OFFSET
type: ComponentType.Connection, });
uri: connectionData.uri, payload.component.bends.push({
previousDestination: connectionData.component.destination, x: rightCenter.x + ConnectionManager.SELF_LOOP_X_OFFSET,
payload, y: rightCenter.y + ConnectionManager.SELF_LOOP_Y_OFFSET
errorStrategy: 'snackbar' });
} }
})
); self.store.dispatch(
updateConnection({
request: {
id: connectionData.id,
type: ComponentType.Connection,
uri: connectionData.uri,
previousDestination: connectionData.component.destination,
payload,
errorStrategy: 'snackbar'
}
})
);
}
} }
} }
// stop further propagation // stop further propagation
event.sourceEvent.stopPropagation(); event.sourceEvent.stopPropagation();
// indicate dragging complete
connectionData.dragging = false;
}); });
// label drag behavior // label drag behavior
this.labelDrag = d3 this.labelDrag = d3
.drag() .drag()
.on('start', function (event) { .on('start', function (event, d: any) {
// stop further propagation // stop further propagation
event.sourceEvent.stopPropagation(); event.sourceEvent.stopPropagation();
// indicate dragging start
d.dragging = true;
}) })
.on('drag', function (this: any, event, d: any) { .on('drag', function (this: any, event, d: any) {
if (d && d.bends.length > 1) { if (d.dragging && d.bends.length > 1) {
// get the dragged component // get the dragged component
let drag: any = d3.select('rect.label-drag'); let drag: any = d3.select('rect.label-drag');
@ -2113,7 +2149,7 @@ export class ConnectionManager {
} }
}) })
.on('end', function (this: any, event, d: any) { .on('end', function (this: any, event, d: any) {
if (d.bends.length > 1) { if (d.dragging && d.bends.length > 1) {
// get the drag selection // get the drag selection
const drag: any = d3.select('rect.label-drag'); const drag: any = d3.select('rect.label-drag');
@ -2140,6 +2176,9 @@ export class ConnectionManager {
// stop further propagation // stop further propagation
event.sourceEvent.stopPropagation(); event.sourceEvent.stopPropagation();
// indicate dragging complete
d.dragging = false;
}); });
this.store this.store
@ -2187,12 +2226,38 @@ export class ConnectionManager {
private set(connections: any): void { private set(connections: any): void {
// update the connections // update the connections
this.connections = connections.map((connection: any) => { this.connections = connections.map((connection: any) => {
const currentConnection: any = this.connections.find((c: any) => c.id === connection.id);
// only consider newer when the version is greater which indicates the component configuration has changed.
// when this happens we should override the current dragging action so that the new changes can be realized.
const isNewerRevision = connection.revision.version > currentConnection?.revision.version;
let dragging = false;
if (currentConnection?.dragging && !isNewerRevision) {
dragging = true;
}
let bends: Position[];
if (dragging) {
bends = currentConnection.bends;
} else {
bends = connection.bends.map((bend: Position) => {
return { ...bend };
});
}
let end: Position | undefined;
if (dragging) {
end = currentConnection.end;
}
return { return {
...connection, ...connection,
type: ComponentType.Connection, type: ComponentType.Connection,
bends: connection.bends.map((bend: Position) => { dragging,
return { ...bend }; labelIndex: dragging ? currentConnection.labelIndex : connection.labelIndex,
}) bends,
end
}; };
}); });

View File

@ -304,94 +304,121 @@ export class LabelManager {
// handle bend point drag events // handle bend point drag events
this.labelPointDrag = d3 this.labelPointDrag = d3
.drag() .drag()
.on('start', function (event) { .on('start', function (this: any, event) {
// stop further propagation // stop further propagation
event.sourceEvent.stopPropagation(); event.sourceEvent.stopPropagation();
// indicate dragging start
const label = d3.select(this.parentNode);
const labelData: any = label.datum();
labelData.dragging = true;
}) })
.on('drag', function (this: any, event) { .on('drag', function (this: any, event) {
const label = d3.select(this.parentNode); const label = d3.select(this.parentNode);
const labelData: any = label.datum(); const labelData: any = label.datum();
// update the dimensions and ensure they are still within bounds if (labelData.dragging) {
// snap between aligned sizes unless the user is holding shift // update the dimensions and ensure they are still within bounds
self.snapEnabled = !event.sourceEvent.shiftKey; // snap between aligned sizes unless the user is holding shift
labelData.dimensions.width = Math.max( self.snapEnabled = !event.sourceEvent.shiftKey;
LabelManager.MIN_WIDTH, labelData.dimensions.width = Math.max(
self.snapEnabled LabelManager.MIN_WIDTH,
? Math.round(event.x / LabelManager.SNAP_ALIGNMENT_PIXELS) * LabelManager.SNAP_ALIGNMENT_PIXELS self.snapEnabled
: event.x ? Math.round(event.x / LabelManager.SNAP_ALIGNMENT_PIXELS) *
); LabelManager.SNAP_ALIGNMENT_PIXELS
labelData.dimensions.height = Math.max( : event.x
LabelManager.MIN_HEIGHT, );
self.snapEnabled labelData.dimensions.height = Math.max(
? Math.round(event.y / LabelManager.SNAP_ALIGNMENT_PIXELS) * LabelManager.SNAP_ALIGNMENT_PIXELS LabelManager.MIN_HEIGHT,
: event.y self.snapEnabled
); ? Math.round(event.y / LabelManager.SNAP_ALIGNMENT_PIXELS) *
LabelManager.SNAP_ALIGNMENT_PIXELS
: event.y
);
// redraw this connection // redraw this connection
self.updateLabels(label); self.updateLabels(label);
}
}) })
.on('end', function (this: any, event) { .on('end', function (this: any, event) {
const label = d3.select(this.parentNode); const label = d3.select(this.parentNode);
const labelData: any = label.datum(); const labelData: any = label.datum();
// determine if the width has changed if (labelData.dragging) {
let different = false; // determine if the width has changed
const widthSet = !!labelData.component.width; let different = false;
if (widthSet || labelData.dimensions.width !== labelData.component.width) { const widthSet = !!labelData.component.width;
different = true; if (widthSet || labelData.dimensions.width !== labelData.component.width) {
} different = true;
}
// determine if the height has changed // determine if the height has changed
const heightSet = !!labelData.component.height; const heightSet = !!labelData.component.height;
if ((!different && heightSet) || labelData.dimensions.height !== labelData.component.height) { if ((!different && heightSet) || labelData.dimensions.height !== labelData.component.height) {
different = true; different = true;
} }
// only save the updated dimensions if necessary // only save the updated dimensions if necessary
if (different) { if (different) {
const updateLabel: UpdateComponentRequest = { const updateLabel: UpdateComponentRequest = {
id: labelData.id, id: labelData.id,
type: ComponentType.Label, type: ComponentType.Label,
uri: labelData.uri, uri: labelData.uri,
payload: { payload: {
revision: self.client.getRevision(labelData), revision: self.client.getRevision(labelData),
disconnectedNodeAcknowledged: self.clusterConnectionService.isDisconnectionAcknowledged(), disconnectedNodeAcknowledged:
component: { self.clusterConnectionService.isDisconnectionAcknowledged(),
id: labelData.id, component: {
width: labelData.dimensions.width, id: labelData.id,
height: labelData.dimensions.height width: labelData.dimensions.width,
} height: labelData.dimensions.height
}, }
restoreOnFailure: { },
dimensions: { restoreOnFailure: {
width: widthSet ? labelData.component.width : LabelManager.INITIAL_WIDTH, dimensions: {
height: heightSet ? labelData.component.height : LabelManager.INITIAL_HEIGHT width: widthSet ? labelData.component.width : LabelManager.INITIAL_WIDTH,
} height: heightSet ? labelData.component.height : LabelManager.INITIAL_HEIGHT
}, }
errorStrategy: 'snackbar' },
}; errorStrategy: 'snackbar'
};
self.store.dispatch( self.store.dispatch(
updateComponent({ updateComponent({
request: updateLabel request: updateLabel
}) })
); );
}
} }
// stop further propagation // stop further propagation
event.sourceEvent.stopPropagation(); event.sourceEvent.stopPropagation();
// indicate dragging complete
labelData.dragging = false;
}); });
} }
private set(labels: any): void { private set(labels: any): void {
// update the labels // update the labels
this.labels = labels.map((label: any) => { this.labels = labels.map((label: any) => {
const currentLabel: any = this.labels.find((l: any) => l.id === label.id);
// only consider newer when the version is greater which indicates the component configuration has changed.
// when this happens we should override the current dragging action so that the new changes can be realized.
const isNewerRevision = label.revision.version > currentLabel?.revision.version;
let dragging = false;
if (currentLabel?.dragging && !isNewerRevision) {
dragging = true;
}
return { return {
...label, ...label,
type: ComponentType.Label, type: ComponentType.Label,
dragging,
dimensions: { dimensions: {
...label.dimensions ...(dragging ? currentLabel.dimensions : label.dimensions)
} }
}; };
}); });

View File

@ -28,7 +28,7 @@
<div>Relationships</div> <div>Relationships</div>
<div class="flex flex-col gap-y-3"> <div class="flex flex-col gap-y-3">
@for (item of relationshipItems; track item; let i = $index) { @for (item of relationshipItems; track item; let i = $index) {
@if (isDisabled && item.selected || !isDisabled) { @if ((isDisabled && item.selected) || !isDisabled) {
<div class="flex flex-col gap-y-1.5"> <div class="flex flex-col gap-y-1.5">
<div class="flex items-center gap-x-2"> <div class="flex items-center gap-x-2">
<mat-checkbox <mat-checkbox