From e7facda91294fc47bd189cd34f11c4fcaf1cb334 Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Mon, 6 May 2024 16:15:15 -0400 Subject: [PATCH] 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 --- .../manager/connection-manager.service.ts | 345 +++++++++++------- .../service/manager/label-manager.service.ts | 143 +++++--- .../source-processor.component.html | 2 +- 3 files changed, 291 insertions(+), 199 deletions(-) diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.ts index 209b777b9c..20d1fdcd8a 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/connection-manager.service.ts @@ -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 }; }); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/label-manager.service.ts b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/label-manager.service.ts index 86428e57aa..c68926ae81 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/label-manager.service.ts +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/service/manager/label-manager.service.ts @@ -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) } }; }); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/connection/source/source-processor/source-processor.component.html b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/connection/source/source-processor/source-processor.component.html index 191e1e1860..539dfd3a78 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/connection/source/source-processor/source-processor.component.html +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-frontend/src/main/nifi/src/app/pages/flow-designer/ui/canvas/items/connection/source/source-processor/source-processor.component.html @@ -28,7 +28,7 @@
Relationships
@for (item of relationshipItems; track item; let i = $index) { - @if (isDisabled && item.selected || !isDisabled) { + @if ((isDisabled && item.selected) || !isDisabled) {