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
* in connection.
*
* @param {type} d
* @param {type} connection
* @param d
* @param connection
* @param restoreOnFailure
*/
private save(d: any, connection: any, restoreOnFailure?: any): void {
const updateConnection: UpdateComponentRequest = {
@ -616,7 +617,7 @@ export class ConnectionManager {
// if we are currently dragging the endpoint to a new target, use that
// 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
end = d.end;
@ -1844,196 +1845,231 @@ export class ConnectionManager {
// handle bend point drag events
this.bendPointDrag = d3
.drag()
.on('start', function (event) {
.on('start', function (this: any, event) {
// stop further propagation
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) {
self.snapEnabled = !event.sourceEvent.shiftKey;
d.x = self.snapEnabled
? 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;
const connection: any = d3.select(this.parentNode);
const connectionData: any = connection.datum();
// redraw this connection
self.updateConnections(d3.select(this.parentNode), {
updatePath: true,
updateLabel: false
});
if (connectionData.dragging) {
self.snapEnabled = !event.sourceEvent.shiftKey;
d.x = self.snapEnabled
? 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) {
const connection: any = d3.select(this.parentNode);
const connectionData: any = connection.datum();
const bends: Position[] = connection.selectAll('rect.midpoint').data();
// ensure the bend lengths are the same
if (bends.length === connectionData.component.bends.length) {
// 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;
}
}
if (connectionData.dragging) {
const bends: Position[] = connection.selectAll('rect.midpoint').data();
// only save the updated bends if necessary
if (different) {
self.save(
connectionData,
{
id: connectionData.id,
bends: bends
},
{
bends: [...connectionData.component.bends]
// ensure the bend lengths are the same
if (bends.length === connectionData.component.bends.length) {
// 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
if (different) {
self.save(
connectionData,
{
id: connectionData.id,
bends: bends
},
{
bends: [...connectionData.component.bends]
}
);
}
}
}
// stop further propagation
event.sourceEvent.stopPropagation();
// indicate dragging complete
connectionData.dragging = false;
});
// handle endpoint drag events
this.endpointDrag = d3
.drag()
.on('start', function (event, d: any) {
// indicate that dragging has begun
d.dragging = true;
.on('start', function (this: any, event, d: any) {
// indicate that end point dragging has begun
d.endPointDragging = true;
// stop further propagation
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) {
d.x = event.x - 8;
d.y = event.y - 8;
const connection: any = d3.select(this.parentNode);
const connectionData: any = connection.datum();
// ensure the new destination is valid
d3.select('g.hover').classed('connectable-destination', function () {
return self.canvasUtils.isValidConnectionDestination(d3.select(this));
});
if (connectionData.dragging) {
d.x = event.x - 8;
d.y = event.y - 8;
// redraw this connection
self.updateConnections(d3.select(this.parentNode), {
updatePath: true,
updateLabel: false
});
// ensure the new destination is valid
d3.select('g.hover').classed('connectable-destination', function () {
return self.canvasUtils.isValidConnectionDestination(d3.select(this));
});
// redraw this connection
self.updateConnections(d3.select(this.parentNode), {
updatePath: true,
updateLabel: false
});
}
})
.on('end', function (this: any, event, d: any) {
// indicate that dragging as stopped
d.dragging = false;
// indicate that end point dragging as stopped
d.endPointDragging = false;
// get the corresponding connection
const connection: any = d3.select(this.parentNode);
const connectionData: any = connection.datum();
// attempt to select a new destination
const destination: any = d3.select('g.connectable-destination');
if (connectionData.dragging) {
// 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
if (destination.empty()) {
self.updateConnections(connection, {
updatePath: true,
updateLabel: false
});
} else {
const destinationData: any = destination.datum();
// resets the connection if we're not over a new destination
if (destination.empty()) {
self.updateConnections(connection, {
updatePath: true,
updateLabel: false
});
} else {
const destinationData: any = destination.datum();
// prompt for the new port if appropriate
if (
self.canvasUtils.isProcessGroup(destination) ||
self.canvasUtils.isRemoteProcessGroup(destination)
) {
// when the new destination is a group, show the edit connection dialog
// to allow the user to select the desired port
self.store.dispatch(
openEditConnectionDialog({
request: {
type: ComponentType.Connection,
uri: connectionData.uri,
entity: { ...connectionData },
newDestination: {
type: destinationData.type,
groupId: destinationData.id,
name: destinationData.permissions.canRead
? destinationData.component.name
: destinationData.id
// prompt for the new port if appropriate
if (
self.canvasUtils.isProcessGroup(destination) ||
self.canvasUtils.isRemoteProcessGroup(destination)
) {
// when the new destination is a group, show the edit connection dialog
// to allow the user to select the desired port
self.store.dispatch(
openEditConnectionDialog({
request: {
type: ComponentType.Connection,
uri: connectionData.uri,
entity: { ...connectionData },
newDestination: {
type: destinationData.type,
groupId: destinationData.id,
name: destinationData.permissions.canRead
? destinationData.component.name
: 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 = [];
payload.component.bends.push({
x: rightCenter.x + ConnectionManager.SELF_LOOP_X_OFFSET,
y: rightCenter.y - ConnectionManager.SELF_LOOP_Y_OFFSET
});
payload.component.bends.push({
x: rightCenter.x + ConnectionManager.SELF_LOOP_X_OFFSET,
y: rightCenter.y + ConnectionManager.SELF_LOOP_Y_OFFSET
});
}
// 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
};
self.store.dispatch(
updateConnection({
request: {
id: connectionData.id,
type: ComponentType.Connection,
uri: connectionData.uri,
previousDestination: connectionData.component.destination,
payload,
errorStrategy: 'snackbar'
}
})
);
payload.component.bends = [];
payload.component.bends.push({
x: rightCenter.x + ConnectionManager.SELF_LOOP_X_OFFSET,
y: rightCenter.y - ConnectionManager.SELF_LOOP_Y_OFFSET
});
payload.component.bends.push({
x: rightCenter.x + ConnectionManager.SELF_LOOP_X_OFFSET,
y: rightCenter.y + ConnectionManager.SELF_LOOP_Y_OFFSET
});
}
self.store.dispatch(
updateConnection({
request: {
id: connectionData.id,
type: ComponentType.Connection,
uri: connectionData.uri,
previousDestination: connectionData.component.destination,
payload,
errorStrategy: 'snackbar'
}
})
);
}
}
}
// stop further propagation
event.sourceEvent.stopPropagation();
// indicate dragging complete
connectionData.dragging = false;
});
// label drag behavior
this.labelDrag = d3
.drag()
.on('start', function (event) {
.on('start', function (event, d: any) {
// stop further propagation
event.sourceEvent.stopPropagation();
// indicate dragging start
d.dragging = true;
})
.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
let drag: any = d3.select('rect.label-drag');
@ -2113,7 +2149,7 @@ export class ConnectionManager {
}
})
.on('end', function (this: any, event, d: any) {
if (d.bends.length > 1) {
if (d.dragging && d.bends.length > 1) {
// get the drag selection
const drag: any = d3.select('rect.label-drag');
@ -2140,6 +2176,9 @@ export class ConnectionManager {
// stop further propagation
event.sourceEvent.stopPropagation();
// indicate dragging complete
d.dragging = false;
});
this.store
@ -2187,12 +2226,38 @@ export class ConnectionManager {
private set(connections: any): void {
// update the connections
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 {
...connection,
type: ComponentType.Connection,
bends: connection.bends.map((bend: Position) => {
return { ...bend };
})
dragging,
labelIndex: dragging ? currentConnection.labelIndex : connection.labelIndex,
bends,
end
};
});

View File

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

View File

@ -28,7 +28,7 @@
<div>Relationships</div>
<div class="flex flex-col gap-y-3">
@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 items-center gap-x-2">
<mat-checkbox