mirror of https://github.com/apache/nifi.git
[NIFI-13158] support for keyboard shortcuts (#8770)
* removed aliasing of 'this' from canvas.component.ts * removed aliasing of 'this' from canvas-utils.ts * reuse common actions between operation and context menu This closes #8770
This commit is contained in:
parent
45098ed859
commit
0f39428209
|
@ -0,0 +1,469 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CanvasUtils } from './canvas-utils.service';
|
||||
import {
|
||||
copy,
|
||||
deleteComponents,
|
||||
disableComponents,
|
||||
disableCurrentProcessGroup,
|
||||
enableComponents,
|
||||
enableCurrentProcessGroup,
|
||||
getParameterContextsAndOpenGroupComponentsDialog,
|
||||
leaveProcessGroup,
|
||||
navigateToEditComponent,
|
||||
navigateToEditCurrentProcessGroup,
|
||||
navigateToManageComponentPolicies,
|
||||
paste,
|
||||
reloadFlow,
|
||||
selectComponents,
|
||||
startComponents,
|
||||
startCurrentProcessGroup,
|
||||
stopComponents,
|
||||
stopCurrentProcessGroup
|
||||
} from '../state/flow/flow.actions';
|
||||
import {
|
||||
CopyComponentRequest,
|
||||
DeleteComponentRequest,
|
||||
DisableComponentRequest,
|
||||
EnableComponentRequest,
|
||||
MoveComponentRequest,
|
||||
PasteRequest,
|
||||
SelectedComponent,
|
||||
StartComponentRequest,
|
||||
StopComponentRequest
|
||||
} from '../state/flow';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CanvasState } from '../state';
|
||||
import * as d3 from 'd3';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { CanvasView } from './canvas-view.service';
|
||||
import { ComponentType } from '../../../state/shared';
|
||||
import { Client } from '../../../service/client.service';
|
||||
|
||||
export type CanvasConditionFunction = (selection: d3.Selection<any, any, any, any>) => boolean;
|
||||
export type CanvasActionFunction = (selection: d3.Selection<any, any, any, any>, extraArgs?: any) => void;
|
||||
|
||||
export interface CanvasAction {
|
||||
id: string;
|
||||
condition: CanvasConditionFunction;
|
||||
action: CanvasActionFunction;
|
||||
}
|
||||
|
||||
export interface CanvasActions {
|
||||
[key: string]: CanvasAction;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CanvasActionsService {
|
||||
private _actions: CanvasActions = {
|
||||
delete: {
|
||||
id: 'delete',
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
return this.canvasUtils.areDeletable(selection);
|
||||
},
|
||||
action: (selection: d3.Selection<any, any, any, any>) => {
|
||||
if (selection.size() === 1) {
|
||||
const selectionData = selection.datum();
|
||||
this.store.dispatch(
|
||||
deleteComponents({
|
||||
request: [
|
||||
{
|
||||
id: selectionData.id,
|
||||
type: selectionData.type,
|
||||
uri: selectionData.uri,
|
||||
entity: selectionData
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const requests: DeleteComponentRequest[] = [];
|
||||
selection.each((d: any) => {
|
||||
requests.push({
|
||||
id: d.id,
|
||||
type: d.type,
|
||||
uri: d.uri,
|
||||
entity: d
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
deleteComponents({
|
||||
request: requests
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
refresh: {
|
||||
id: 'refresh',
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
return this.canvasUtils.emptySelection(selection);
|
||||
},
|
||||
action: () => {
|
||||
this.store.dispatch(reloadFlow());
|
||||
}
|
||||
},
|
||||
leaveGroup: {
|
||||
id: 'leaveGroup',
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
const dialogsAreOpen = this.dialog.openDialogs.length > 0;
|
||||
return this.canvasUtils.isNotRootGroupAndEmptySelection(selection) && !dialogsAreOpen;
|
||||
},
|
||||
action: () => {
|
||||
this.store.dispatch(leaveProcessGroup());
|
||||
}
|
||||
},
|
||||
copy: {
|
||||
id: 'copy',
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
return this.canvasUtils.isCopyable(selection);
|
||||
},
|
||||
action: (selection: d3.Selection<any, any, any, any>) => {
|
||||
const origin = this.canvasUtils.getOrigin(selection);
|
||||
const dimensions = this.canvasView.getSelectionBoundingClientRect(selection);
|
||||
|
||||
const components: CopyComponentRequest[] = [];
|
||||
selection.each((d) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
type: d.type,
|
||||
uri: d.uri,
|
||||
entity: d
|
||||
});
|
||||
});
|
||||
|
||||
this.store.dispatch(
|
||||
copy({
|
||||
request: {
|
||||
components,
|
||||
origin,
|
||||
dimensions
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
paste: {
|
||||
id: 'paste',
|
||||
condition: () => {
|
||||
return this.canvasUtils.isPastable();
|
||||
},
|
||||
action: (selection, extraArgs) => {
|
||||
const pasteRequest: PasteRequest = {};
|
||||
if (extraArgs?.pasteLocation) {
|
||||
pasteRequest.pasteLocation = extraArgs.pasteLocation;
|
||||
}
|
||||
this.store.dispatch(
|
||||
paste({
|
||||
request: pasteRequest
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
selectAll: {
|
||||
id: 'selectAll',
|
||||
condition: () => {
|
||||
return true;
|
||||
},
|
||||
action: () => {
|
||||
const selectedComponents = this.select(d3.selectAll('g.component, g.connection'));
|
||||
this.store.dispatch(
|
||||
selectComponents({
|
||||
request: {
|
||||
components: selectedComponents
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
configure: {
|
||||
id: 'configure',
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
return this.canvasUtils.isConfigurable(selection);
|
||||
},
|
||||
action: (selection: d3.Selection<any, any, any, any>) => {
|
||||
if (selection.empty()) {
|
||||
this.store.dispatch(navigateToEditCurrentProcessGroup());
|
||||
} else {
|
||||
const selectionData = selection.datum();
|
||||
this.store.dispatch(
|
||||
navigateToEditComponent({
|
||||
request: {
|
||||
type: selectionData.type,
|
||||
id: selectionData.id
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
manageAccess: {
|
||||
id: 'manageAccess',
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
return this.canvasUtils.canManagePolicies(selection);
|
||||
},
|
||||
action: (selection: d3.Selection<any, any, any, any>, extraArgs?) => {
|
||||
if (selection.empty()) {
|
||||
if (extraArgs?.processGroupId) {
|
||||
this.store.dispatch(
|
||||
navigateToManageComponentPolicies({
|
||||
request: {
|
||||
resource: 'process-groups',
|
||||
id: extraArgs.processGroupId
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const selectionData = selection.datum();
|
||||
const componentType: ComponentType = selectionData.type;
|
||||
|
||||
let resource = 'process-groups';
|
||||
switch (componentType) {
|
||||
case ComponentType.Processor:
|
||||
resource = 'processors';
|
||||
break;
|
||||
case ComponentType.InputPort:
|
||||
resource = 'input-ports';
|
||||
break;
|
||||
case ComponentType.OutputPort:
|
||||
resource = 'output-ports';
|
||||
break;
|
||||
case ComponentType.Funnel:
|
||||
resource = 'funnels';
|
||||
break;
|
||||
case ComponentType.Label:
|
||||
resource = 'labels';
|
||||
break;
|
||||
case ComponentType.RemoteProcessGroup:
|
||||
resource = 'remote-process-groups';
|
||||
break;
|
||||
}
|
||||
|
||||
this.store.dispatch(
|
||||
navigateToManageComponentPolicies({
|
||||
request: {
|
||||
resource,
|
||||
id: selectionData.id
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
start: {
|
||||
id: 'start',
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
if (!selection) {
|
||||
return false;
|
||||
}
|
||||
return this.canvasUtils.areAnyRunnable(selection);
|
||||
},
|
||||
action: (selection: d3.Selection<any, any, any, any>) => {
|
||||
if (selection.empty()) {
|
||||
// attempting to start the current process group
|
||||
this.store.dispatch(startCurrentProcessGroup());
|
||||
} else {
|
||||
const components: StartComponentRequest[] = [];
|
||||
const startable = this.canvasUtils.getStartable(selection);
|
||||
startable.each((d: any) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
uri: d.uri,
|
||||
type: d.type,
|
||||
revision: this.client.getRevision(d),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
startComponents({
|
||||
request: {
|
||||
components
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
stop: {
|
||||
id: 'stop',
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
return this.canvasUtils.areAnyStoppable(selection);
|
||||
},
|
||||
action: (selection: d3.Selection<any, any, any, any>) => {
|
||||
if (selection.empty()) {
|
||||
// attempting to start the current process group
|
||||
this.store.dispatch(stopCurrentProcessGroup());
|
||||
} else {
|
||||
const components: StopComponentRequest[] = [];
|
||||
const stoppable = this.canvasUtils.getStoppable(selection);
|
||||
stoppable.each((d: any) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
uri: d.uri,
|
||||
type: d.type,
|
||||
revision: this.client.getRevision(d),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
stopComponents({
|
||||
request: {
|
||||
components
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
enable: {
|
||||
id: 'enable',
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
return this.canvasUtils.canEnable(selection);
|
||||
},
|
||||
action: (selection: d3.Selection<any, any, any, any>) => {
|
||||
if (selection.empty()) {
|
||||
// attempting to enable the current process group
|
||||
this.store.dispatch(enableCurrentProcessGroup());
|
||||
} else {
|
||||
const components: EnableComponentRequest[] = [];
|
||||
const enableable = this.canvasUtils.filterEnable(selection);
|
||||
enableable.each((d: any) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
uri: d.uri,
|
||||
type: d.type,
|
||||
revision: this.client.getRevision(d),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
enableComponents({
|
||||
request: {
|
||||
components
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
disable: {
|
||||
id: 'disable',
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
return this.canvasUtils.canDisable(selection);
|
||||
},
|
||||
action: (selection: d3.Selection<any, any, any, any>) => {
|
||||
if (selection.empty()) {
|
||||
// attempting to disable the current process group
|
||||
this.store.dispatch(disableCurrentProcessGroup());
|
||||
} else {
|
||||
const components: DisableComponentRequest[] = [];
|
||||
const disableable = this.canvasUtils.filterDisable(selection);
|
||||
disableable.each((d: any) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
uri: d.uri,
|
||||
type: d.type,
|
||||
revision: this.client.getRevision(d),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
disableComponents({
|
||||
request: {
|
||||
components
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
group: {
|
||||
id: 'group',
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
return this.canvasUtils.isDisconnected(selection) && this.canvasUtils.canModify(selection);
|
||||
},
|
||||
action: (selection: d3.Selection<any, any, any, any>) => {
|
||||
const moveComponents: MoveComponentRequest[] = [];
|
||||
selection.each(function (d: any) {
|
||||
moveComponents.push({
|
||||
id: d.id,
|
||||
type: d.type,
|
||||
uri: d.uri,
|
||||
entity: d
|
||||
});
|
||||
});
|
||||
|
||||
// move the selection into the group
|
||||
this.store.dispatch(
|
||||
getParameterContextsAndOpenGroupComponentsDialog({
|
||||
request: {
|
||||
moveComponents,
|
||||
position: this.canvasUtils.getOrigin(selection)
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
constructor(
|
||||
private store: Store<CanvasState>,
|
||||
private canvasUtils: CanvasUtils,
|
||||
private canvasView: CanvasView,
|
||||
private dialog: MatDialog,
|
||||
private client: Client
|
||||
) {}
|
||||
|
||||
private select(selection: d3.Selection<any, any, any, any>) {
|
||||
const selectedComponents: SelectedComponent[] = [];
|
||||
if (selection) {
|
||||
selection.each((d: any) => {
|
||||
selectedComponents.push({
|
||||
id: d.id,
|
||||
componentType: d.type
|
||||
});
|
||||
});
|
||||
}
|
||||
return selectedComponents;
|
||||
}
|
||||
|
||||
getAction(id: string): CanvasAction | null {
|
||||
if (this._actions && this._actions[id]) {
|
||||
return this._actions[id];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
getActionFunction(id: string): CanvasActionFunction {
|
||||
if (this._actions && this._actions[id]) {
|
||||
return this._actions[id].action;
|
||||
}
|
||||
return () => {};
|
||||
}
|
||||
|
||||
getConditionFunction(id: string): CanvasConditionFunction {
|
||||
if (this._actions && this._actions[id]) {
|
||||
return this._actions[id].condition;
|
||||
}
|
||||
return () => false;
|
||||
}
|
||||
}
|
|
@ -21,12 +21,9 @@ import { Store } from '@ngrx/store';
|
|||
import { CanvasState } from '../state';
|
||||
import {
|
||||
centerSelectedComponents,
|
||||
deleteComponents,
|
||||
downloadFlow,
|
||||
enterProcessGroup,
|
||||
getParameterContextsAndOpenGroupComponentsDialog,
|
||||
goToRemoteProcessGroup,
|
||||
leaveProcessGroup,
|
||||
moveComponents,
|
||||
moveToFront,
|
||||
navigateToAdvancedProcessorUi,
|
||||
|
@ -34,8 +31,8 @@ import {
|
|||
navigateToControllerServicesForProcessGroup,
|
||||
navigateToEditComponent,
|
||||
navigateToEditCurrentProcessGroup,
|
||||
navigateToManageComponentPolicies,
|
||||
navigateToManageRemotePorts,
|
||||
navigateToParameterContext,
|
||||
navigateToProvenanceForComponent,
|
||||
navigateToQueueListing,
|
||||
navigateToViewStatusHistoryForComponent,
|
||||
|
@ -46,36 +43,18 @@ import {
|
|||
openRevertLocalChangesDialogRequest,
|
||||
openSaveVersionDialogRequest,
|
||||
openShowLocalChangesDialogRequest,
|
||||
reloadFlow,
|
||||
replayLastProvenanceEvent,
|
||||
requestRefreshRemoteProcessGroup,
|
||||
runOnce,
|
||||
startComponents,
|
||||
startCurrentProcessGroup,
|
||||
stopComponents,
|
||||
stopCurrentProcessGroup,
|
||||
stopVersionControlRequest,
|
||||
copy,
|
||||
paste,
|
||||
terminateThreads,
|
||||
navigateToParameterContext,
|
||||
enableCurrentProcessGroup,
|
||||
enableComponents,
|
||||
disableCurrentProcessGroup,
|
||||
disableComponents
|
||||
terminateThreads
|
||||
} from '../state/flow/flow.actions';
|
||||
import { ComponentType } from '../../../state/shared';
|
||||
import {
|
||||
ConfirmStopVersionControlRequest,
|
||||
CopyComponentRequest,
|
||||
DeleteComponentRequest,
|
||||
DisableComponentRequest,
|
||||
EnableComponentRequest,
|
||||
MoveComponentRequest,
|
||||
OpenChangeVersionDialogRequest,
|
||||
OpenLocalChangesDialogRequest,
|
||||
StartComponentRequest,
|
||||
StopComponentRequest
|
||||
OpenLocalChangesDialogRequest
|
||||
} from '../state/flow';
|
||||
import {
|
||||
ContextMenuDefinition,
|
||||
|
@ -88,6 +67,7 @@ import { navigateToComponentDocumentation } from '../../../state/documentation/d
|
|||
import * as d3 from 'd3';
|
||||
import { Client } from '../../../service/client.service';
|
||||
import { CanvasView } from './canvas-view.service';
|
||||
import { CanvasActionsService } from './canvas-actions.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
||||
|
@ -400,49 +380,25 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
id: 'root',
|
||||
menuItems: [
|
||||
{
|
||||
condition: (selection: any) => {
|
||||
return this.canvasUtils.emptySelection(selection);
|
||||
},
|
||||
condition: this.canvasActionsService.getConditionFunction('refresh'),
|
||||
clazz: 'fa fa-refresh',
|
||||
text: 'Refresh',
|
||||
action: () => {
|
||||
this.store.dispatch(reloadFlow());
|
||||
}
|
||||
action: this.canvasActionsService.getActionFunction('refresh')
|
||||
},
|
||||
{
|
||||
condition: (selection: any) => {
|
||||
return this.canvasUtils.isNotRootGroupAndEmptySelection(selection);
|
||||
},
|
||||
condition: this.canvasActionsService.getConditionFunction('leaveGroup'),
|
||||
clazz: 'fa fa-level-up',
|
||||
text: 'Leave group',
|
||||
action: () => {
|
||||
this.store.dispatch(leaveProcessGroup());
|
||||
}
|
||||
action: this.canvasActionsService.getActionFunction('leaveGroup')
|
||||
},
|
||||
{
|
||||
isSeparator: true
|
||||
},
|
||||
{
|
||||
condition: (selection: any) => {
|
||||
return this.canvasUtils.isConfigurable(selection);
|
||||
},
|
||||
condition: this.canvasActionsService.getConditionFunction('configure'),
|
||||
clazz: 'fa fa-gear',
|
||||
text: 'Configure',
|
||||
action: (selection: any) => {
|
||||
if (selection.empty()) {
|
||||
this.store.dispatch(navigateToEditCurrentProcessGroup());
|
||||
} else {
|
||||
const selectionData = selection.datum();
|
||||
this.store.dispatch(
|
||||
navigateToEditComponent({
|
||||
request: {
|
||||
type: selectionData.type,
|
||||
id: selectionData.id
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
action: this.canvasActionsService.getActionFunction('configure')
|
||||
},
|
||||
{
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
|
@ -585,35 +541,11 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
startable.filter((d: any) => d.type === ComponentType.RemoteProcessGroup).size() ===
|
||||
startable.size();
|
||||
|
||||
return this.canvasUtils.areAnyRunnable(selection) && !allRpgs;
|
||||
return this.canvasActionsService.getConditionFunction('start')(selection) && !allRpgs;
|
||||
},
|
||||
clazz: 'fa fa-play',
|
||||
text: 'Start',
|
||||
action: (selection: any) => {
|
||||
if (selection.empty()) {
|
||||
// attempting to start the current process group
|
||||
this.store.dispatch(startCurrentProcessGroup());
|
||||
} else {
|
||||
const components: StartComponentRequest[] = [];
|
||||
const startable = this.canvasUtils.getStartable(selection);
|
||||
startable.each((d: any) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
uri: d.uri,
|
||||
type: d.type,
|
||||
revision: this.client.getRevision(d),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
startComponents({
|
||||
request: {
|
||||
components
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
action: this.canvasActionsService.getActionFunction('start')
|
||||
},
|
||||
{
|
||||
condition: (selection: any) => {
|
||||
|
@ -627,35 +559,11 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
stoppable.filter((d: any) => d.type === ComponentType.RemoteProcessGroup).size() ===
|
||||
stoppable.size();
|
||||
|
||||
return this.canvasUtils.areAnyStoppable(selection) && !allRpgs;
|
||||
return this.canvasActionsService.getConditionFunction('stop')(selection) && !allRpgs;
|
||||
},
|
||||
clazz: 'fa fa-stop',
|
||||
text: 'Stop',
|
||||
action: (selection: any) => {
|
||||
if (selection.empty()) {
|
||||
// attempting to start the current process group
|
||||
this.store.dispatch(stopCurrentProcessGroup());
|
||||
} else {
|
||||
const components: StopComponentRequest[] = [];
|
||||
const stoppable = this.canvasUtils.getStoppable(selection);
|
||||
stoppable.each((d: any) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
uri: d.uri,
|
||||
type: d.type,
|
||||
revision: this.client.getRevision(d),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
stopComponents({
|
||||
request: {
|
||||
components
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
action: this.canvasActionsService.getActionFunction('stop')
|
||||
},
|
||||
{
|
||||
condition: (selection: any) => {
|
||||
|
@ -697,68 +605,16 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
}
|
||||
},
|
||||
{
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
return this.canvasUtils.canEnable(selection);
|
||||
},
|
||||
condition: this.canvasActionsService.getConditionFunction('enable'),
|
||||
clazz: 'fa fa-flash',
|
||||
text: 'Enable',
|
||||
action: (selection: d3.Selection<any, any, any, any>) => {
|
||||
if (selection.empty()) {
|
||||
// attempting to enable the current process group
|
||||
this.store.dispatch(enableCurrentProcessGroup());
|
||||
} else {
|
||||
const components: EnableComponentRequest[] = [];
|
||||
const enableable = this.canvasUtils.filterEnable(selection);
|
||||
enableable.each((d: any) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
uri: d.uri,
|
||||
type: d.type,
|
||||
revision: this.client.getRevision(d),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
enableComponents({
|
||||
request: {
|
||||
components
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
action: this.canvasActionsService.getActionFunction('enable')
|
||||
},
|
||||
{
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
return this.canvasUtils.canDisable(selection);
|
||||
},
|
||||
condition: this.canvasActionsService.getConditionFunction('disable'),
|
||||
clazz: 'icon icon-enable-false',
|
||||
text: 'Disable',
|
||||
action: (selection: d3.Selection<any, any, any, any>) => {
|
||||
if (selection.empty()) {
|
||||
// attempting to disable the current process group
|
||||
this.store.dispatch(disableCurrentProcessGroup());
|
||||
} else {
|
||||
const components: DisableComponentRequest[] = [];
|
||||
const disableable = this.canvasUtils.filterDisable(selection);
|
||||
disableable.each((d: any) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
uri: d.uri,
|
||||
type: d.type,
|
||||
revision: this.client.getRevision(d),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
disableComponents({
|
||||
request: {
|
||||
components
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
action: this.canvasActionsService.getActionFunction('disable')
|
||||
},
|
||||
{
|
||||
condition: (selection: any) => {
|
||||
|
@ -766,27 +622,7 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
},
|
||||
clazz: 'fa fa-bullseye',
|
||||
text: 'Enable transmission',
|
||||
action: (selection: d3.Selection<any, any, any, any>) => {
|
||||
const components: StartComponentRequest[] = [];
|
||||
const startable = this.canvasUtils.getStartable(selection);
|
||||
startable.each((d: any) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
uri: d.uri,
|
||||
type: d.type,
|
||||
revision: this.client.getRevision(d),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
});
|
||||
|
||||
this.store.dispatch(
|
||||
startComponents({
|
||||
request: {
|
||||
components
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
action: this.canvasActionsService.getActionFunction('start')
|
||||
},
|
||||
{
|
||||
condition: (selection: any) => {
|
||||
|
@ -794,27 +630,7 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
},
|
||||
clazz: 'icon icon-transmit-false',
|
||||
text: 'Disable transmission',
|
||||
action: (selection: d3.Selection<any, any, any, any>) => {
|
||||
const components: StopComponentRequest[] = [];
|
||||
|
||||
const stoppable = this.canvasUtils.getStoppable(selection);
|
||||
stoppable.each((d: any) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
uri: d.uri,
|
||||
type: d.type,
|
||||
revision: this.client.getRevision(d),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
stopComponents({
|
||||
request: {
|
||||
components
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
action: this.canvasActionsService.getActionFunction('stop')
|
||||
},
|
||||
{
|
||||
isSeparator: true
|
||||
|
@ -1023,50 +839,9 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
clazz: 'fa fa-key',
|
||||
text: 'Manage access policies',
|
||||
action: (selection: any) => {
|
||||
if (selection.empty()) {
|
||||
this.store.dispatch(
|
||||
navigateToManageComponentPolicies({
|
||||
request: {
|
||||
resource: 'process-groups',
|
||||
id: this.canvasUtils.getProcessGroupId()
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const selectionData = selection.datum();
|
||||
const componentType: ComponentType = selectionData.type;
|
||||
|
||||
let resource = 'process-groups';
|
||||
switch (componentType) {
|
||||
case ComponentType.Processor:
|
||||
resource = 'processors';
|
||||
break;
|
||||
case ComponentType.InputPort:
|
||||
resource = 'input-ports';
|
||||
break;
|
||||
case ComponentType.OutputPort:
|
||||
resource = 'output-ports';
|
||||
break;
|
||||
case ComponentType.Funnel:
|
||||
resource = 'funnels';
|
||||
break;
|
||||
case ComponentType.Label:
|
||||
resource = 'labels';
|
||||
break;
|
||||
case ComponentType.RemoteProcessGroup:
|
||||
resource = 'remote-process-groups';
|
||||
break;
|
||||
}
|
||||
|
||||
this.store.dispatch(
|
||||
navigateToManageComponentPolicies({
|
||||
request: {
|
||||
resource,
|
||||
id: selectionData.id
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
this.canvasActionsService.getActionFunction('manageAccess')(selection, {
|
||||
processGroupId: this.canvasUtils.getProcessGroupId()
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1243,7 +1018,7 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
isSeparator: true
|
||||
},
|
||||
{
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
condition: () => {
|
||||
return this.canvasUtils.isNotRootGroup();
|
||||
},
|
||||
clazz: 'fa fa-arrows',
|
||||
|
@ -1272,32 +1047,10 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
}
|
||||
},
|
||||
{
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
return this.canvasUtils.isDisconnected(selection) && this.canvasUtils.canModify(selection);
|
||||
},
|
||||
condition: this.canvasActionsService.getConditionFunction('group'),
|
||||
clazz: 'fa icon-group',
|
||||
text: 'Group',
|
||||
action: (selection: d3.Selection<any, any, any, any>) => {
|
||||
const moveComponents: MoveComponentRequest[] = [];
|
||||
selection.each(function (d: any) {
|
||||
moveComponents.push({
|
||||
id: d.id,
|
||||
type: d.type,
|
||||
uri: d.uri,
|
||||
entity: d
|
||||
});
|
||||
});
|
||||
|
||||
// move the selection into the group
|
||||
this.store.dispatch(
|
||||
getParameterContextsAndOpenGroupComponentsDialog({
|
||||
request: {
|
||||
moveComponents,
|
||||
position: this.canvasUtils.getOrigin(selection)
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
action: this.canvasActionsService.getActionFunction('group')
|
||||
},
|
||||
{
|
||||
isSeparator: true
|
||||
|
@ -1311,39 +1064,14 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
isSeparator: true
|
||||
},
|
||||
{
|
||||
condition: (selection: d3.Selection<any, any, any, any>) => {
|
||||
return this.canvasUtils.isCopyable(selection);
|
||||
},
|
||||
condition: this.canvasActionsService.getConditionFunction('copy'),
|
||||
clazz: 'fa fa-copy',
|
||||
text: 'Copy',
|
||||
action: (selection: d3.Selection<any, any, any, any>) => {
|
||||
const origin = this.canvasUtils.getOrigin(selection);
|
||||
const dimensions = this.canvasView.getSelectionBoundingClientRect(selection);
|
||||
|
||||
const components: CopyComponentRequest[] = [];
|
||||
selection.each((d) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
type: d.type,
|
||||
uri: d.uri,
|
||||
entity: d
|
||||
});
|
||||
});
|
||||
|
||||
this.store.dispatch(
|
||||
copy({
|
||||
request: {
|
||||
components,
|
||||
origin,
|
||||
dimensions
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
action: this.canvasActionsService.getActionFunction('copy')
|
||||
},
|
||||
{
|
||||
condition: () => {
|
||||
return this.canvasUtils.isPastable();
|
||||
return this.canvasActionsService.getConditionFunction('paste')(d3.select(null));
|
||||
},
|
||||
clazz: 'fa fa-paste',
|
||||
text: 'Paste',
|
||||
|
@ -1351,13 +1079,7 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
if (event) {
|
||||
const pasteLocation = this.canvasView.getCanvasPosition({ x: event.pageX, y: event.pageY });
|
||||
if (pasteLocation) {
|
||||
this.store.dispatch(
|
||||
paste({
|
||||
request: {
|
||||
pasteLocation
|
||||
}
|
||||
})
|
||||
);
|
||||
this.canvasActionsService.getActionFunction('paste')(selection, { pasteLocation });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1408,43 +1130,10 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
}
|
||||
},
|
||||
{
|
||||
condition: (selection: any) => {
|
||||
return this.canvasUtils.areDeletable(selection);
|
||||
},
|
||||
condition: this.canvasActionsService.getConditionFunction('delete'),
|
||||
clazz: 'fa fa-trash',
|
||||
text: 'Delete',
|
||||
action: (selection: any) => {
|
||||
if (selection.size() === 1) {
|
||||
const selectionData = selection.datum();
|
||||
this.store.dispatch(
|
||||
deleteComponents({
|
||||
request: [
|
||||
{
|
||||
id: selectionData.id,
|
||||
type: selectionData.type,
|
||||
uri: selectionData.uri,
|
||||
entity: selectionData
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const requests: DeleteComponentRequest[] = [];
|
||||
selection.each(function (d: any) {
|
||||
requests.push({
|
||||
id: d.id,
|
||||
type: d.type,
|
||||
uri: d.uri,
|
||||
entity: d
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
deleteComponents({
|
||||
request: requests
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
action: this.canvasActionsService.getActionFunction('delete')
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -1455,7 +1144,8 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
|||
private store: Store<CanvasState>,
|
||||
private canvasUtils: CanvasUtils,
|
||||
private client: Client,
|
||||
private canvasView: CanvasView
|
||||
private canvasView: CanvasView,
|
||||
private canvasActionsService: CanvasActionsService
|
||||
) {
|
||||
this.allMenus = new Map<string, ContextMenuDefinition>();
|
||||
this.allMenus.set(this.ROOT_MENU.id, this.ROOT_MENU);
|
||||
|
|
|
@ -347,15 +347,14 @@ export class CanvasUtils {
|
|||
* @argument {selection} selection The selection
|
||||
* @return {boolean} Whether the selection is deletable
|
||||
*/
|
||||
public areDeletable(selection: any): boolean {
|
||||
public areDeletable(selection: d3.Selection<any, any, any, any>): boolean {
|
||||
if (selection.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const self: CanvasUtils = this;
|
||||
let isDeletable = true;
|
||||
selection.each(function (this: any) {
|
||||
if (!self.isDeletable(d3.select(this))) {
|
||||
selection.each((data, index, nodes) => {
|
||||
if (!this.isDeletable(d3.select(nodes[index]))) {
|
||||
isDeletable = false;
|
||||
}
|
||||
});
|
||||
|
@ -826,8 +825,6 @@ export class CanvasUtils {
|
|||
return false;
|
||||
}
|
||||
|
||||
const self: CanvasUtils = this;
|
||||
|
||||
const connections: Map<string, any> = new Map<string, any>();
|
||||
const components: Map<string, any> = new Map<string, any>();
|
||||
|
||||
|
@ -835,23 +832,23 @@ export class CanvasUtils {
|
|||
|
||||
// include connections
|
||||
selection
|
||||
.filter(function (d: any) {
|
||||
.filter((d: any) => {
|
||||
return d.type === 'Connection';
|
||||
})
|
||||
.each(function (d: any) {
|
||||
.each((d: any) => {
|
||||
connections.set(d.id, d);
|
||||
});
|
||||
|
||||
// include components and ensure their connections are included
|
||||
selection
|
||||
.filter(function (d: any) {
|
||||
.filter((d: any) => {
|
||||
return d.type !== 'Connection';
|
||||
})
|
||||
.each(function (d: any) {
|
||||
.each((d: any) => {
|
||||
components.set(d.id, d.component);
|
||||
|
||||
// check all connections of this component
|
||||
self.getComponentConnections(d.id).forEach((connection) => {
|
||||
this.getComponentConnections(d.id).forEach((connection) => {
|
||||
if (!connections.has(connection.id)) {
|
||||
isDisconnected = false;
|
||||
}
|
||||
|
@ -864,8 +861,8 @@ export class CanvasUtils {
|
|||
if (isDisconnected) {
|
||||
// determine whether this connection and its components are included within the selection
|
||||
isDisconnected =
|
||||
components.has(self.getConnectionSourceComponentId(connection)) &&
|
||||
components.has(self.getConnectionDestinationComponentId(connection));
|
||||
components.has(this.getConnectionSourceComponentId(connection)) &&
|
||||
components.has(this.getConnectionDestinationComponentId(connection));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -37,7 +37,8 @@ import {
|
|||
switchMap,
|
||||
take,
|
||||
takeUntil,
|
||||
tap
|
||||
tap,
|
||||
throttleTime
|
||||
} from 'rxjs';
|
||||
import {
|
||||
CopyComponentRequest,
|
||||
|
@ -160,6 +161,7 @@ export class FlowEffects {
|
|||
reloadFlow$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(FlowActions.reloadFlow),
|
||||
throttleTime(1000),
|
||||
concatLatestFrom(() => this.store.select(selectCurrentProcessGroupId)),
|
||||
switchMap(([, processGroupId]) => {
|
||||
return of(
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Component, HostListener, OnDestroy, OnInit } from '@angular/core';
|
||||
import { CanvasState } from '../../state';
|
||||
import { Position } from '../../state/shared';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
@ -66,6 +66,8 @@ import { getStatusHistoryAndOpenDialog } from '../../../../state/status-history/
|
|||
import { concatLatestFrom } from '@ngrx/operators';
|
||||
import { selectUrl } from '../../../../state/router/router.selectors';
|
||||
import { Storage } from '../../../../service/storage.service';
|
||||
import { CanvasUtils } from '../../service/canvas-utils.service';
|
||||
import { CanvasActionsService } from '../../service/canvas-actions.service';
|
||||
|
||||
@Component({
|
||||
selector: 'fd-canvas',
|
||||
|
@ -83,7 +85,9 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
private store: Store<CanvasState>,
|
||||
private canvasView: CanvasView,
|
||||
private storage: Storage,
|
||||
public canvasContextMenu: CanvasContextMenu
|
||||
private canvasUtils: CanvasUtils,
|
||||
public canvasContextMenu: CanvasContextMenu,
|
||||
private canvasActionsService: CanvasActionsService
|
||||
) {
|
||||
this.store
|
||||
.select(selectTransform)
|
||||
|
@ -286,20 +290,18 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
private createSvg(): void {
|
||||
const self: Canvas = this;
|
||||
|
||||
this.svg = d3
|
||||
.select('#canvas-container')
|
||||
.append('svg')
|
||||
.attr('class', 'canvas-svg')
|
||||
.on('contextmenu', function (event) {
|
||||
.on('contextmenu', (event) => {
|
||||
// reset the canvas click flag
|
||||
self.canvasClicked = false;
|
||||
this.canvasClicked = false;
|
||||
|
||||
// if this context menu click was on the canvas (and not a nested
|
||||
// element) we need to clear the selection
|
||||
if (event.target === self.svg.node()) {
|
||||
self.store.dispatch(deselectAllComponents());
|
||||
if (event.target === this.svg.node()) {
|
||||
this.store.dispatch(deselectAllComponents());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -318,7 +320,7 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
.data(['normal', 'ghost', 'unauthorized', 'full'])
|
||||
.enter()
|
||||
.append('marker')
|
||||
.attr('id', function (d: string) {
|
||||
.attr('id', (d: string) => {
|
||||
return d;
|
||||
})
|
||||
.attr('viewBox', '0 0 6 6')
|
||||
|
@ -327,7 +329,7 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
.attr('markerWidth', 6)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto')
|
||||
.attr('class', function (d: string) {
|
||||
.attr('class', (d: string) => {
|
||||
if (d === 'ghost') {
|
||||
return 'ghost surface-color';
|
||||
} else if (d === 'unauthorized') {
|
||||
|
@ -419,8 +421,6 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
private initCanvas(): void {
|
||||
const self: Canvas = this;
|
||||
|
||||
const t = [INITIAL_TRANSLATE.x, INITIAL_TRANSLATE.y];
|
||||
this.canvas = this.svg
|
||||
.append('g')
|
||||
|
@ -430,8 +430,8 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
|
||||
// handle canvas events
|
||||
this.svg
|
||||
.on('mousedown.selection', function (event: MouseEvent) {
|
||||
self.canvasClicked = true;
|
||||
.on('mousedown.selection', (event: MouseEvent) => {
|
||||
this.canvasClicked = true;
|
||||
|
||||
if (event.button !== 0) {
|
||||
// prevent further propagation (to parents and others handlers
|
||||
|
@ -442,8 +442,8 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
|
||||
// show selection box if shift is held down
|
||||
if (event.shiftKey) {
|
||||
const position: any = d3.pointer(event, self.canvas.node());
|
||||
self.canvas
|
||||
const position: any = d3.pointer(event, this.canvas.node());
|
||||
this.canvas
|
||||
.append('rect')
|
||||
.attr('rx', 6)
|
||||
.attr('ry', 6)
|
||||
|
@ -452,11 +452,11 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
.attr('class', 'component-selection')
|
||||
.attr('width', 0)
|
||||
.attr('height', 0)
|
||||
.attr('stroke-width', function () {
|
||||
return 1 / self.scale;
|
||||
.attr('stroke-width', () => {
|
||||
return 1 / this.scale;
|
||||
})
|
||||
.attr('stroke-dasharray', function () {
|
||||
return 4 / self.scale;
|
||||
.attr('stroke-dasharray', () => {
|
||||
return 4 / this.scale;
|
||||
})
|
||||
.datum(position);
|
||||
|
||||
|
@ -468,7 +468,7 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
event.preventDefault();
|
||||
}
|
||||
})
|
||||
.on('mousemove.selection', function (event: MouseEvent) {
|
||||
.on('mousemove.selection', (event: MouseEvent) => {
|
||||
// update selection box if shift is held down
|
||||
if (event.shiftKey) {
|
||||
// get the selection box
|
||||
|
@ -476,7 +476,7 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
if (!selectionBox.empty()) {
|
||||
// get the original position
|
||||
const originalPosition: any = selectionBox.datum();
|
||||
const position: any = d3.pointer(event, self.canvas.node());
|
||||
const position: any = d3.pointer(event, this.canvas.node());
|
||||
|
||||
const d: any = {};
|
||||
if (originalPosition[0] < position[0]) {
|
||||
|
@ -503,17 +503,17 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
})
|
||||
.on('mouseup.selection', function (this: any) {
|
||||
.on('mouseup.selection', () => {
|
||||
// ensure this originated from clicking the canvas, not a component.
|
||||
// when clicking on a component, the event propagation is stopped so
|
||||
// it never reaches the canvas. we cannot do this however on up events
|
||||
// since the drag events break down
|
||||
if (!self.canvasClicked) {
|
||||
if (!this.canvasClicked) {
|
||||
return;
|
||||
}
|
||||
|
||||
// reset the canvas click flag
|
||||
self.canvasClicked = false;
|
||||
this.canvasClicked = false;
|
||||
|
||||
// get the selection box
|
||||
const selectionBox: any = d3.select('rect.component-selection');
|
||||
|
@ -528,10 +528,11 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
};
|
||||
|
||||
// see if a component should be selected or not
|
||||
d3.selectAll('g.component').each(function (d: any) {
|
||||
d3.selectAll('g.component').each((d: any, i, nodes) => {
|
||||
const item = nodes[i];
|
||||
// consider it selected if its already selected or enclosed in the bounding box
|
||||
if (
|
||||
d3.select(this).classed('selected') ||
|
||||
d3.select(item).classed('selected') ||
|
||||
(d.position.x >= selectionBoundingBox.x &&
|
||||
d.position.x + d.dimensions.width <=
|
||||
selectionBoundingBox.x + selectionBoundingBox.width &&
|
||||
|
@ -547,21 +548,22 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
});
|
||||
|
||||
// see if a connection should be selected or not
|
||||
d3.selectAll('g.connection').each(function (d: any) {
|
||||
d3.selectAll('g.connection').each((d: any, i, nodes) => {
|
||||
// consider all points
|
||||
const points: Position[] = [d.start].concat(d.bends, [d.end]);
|
||||
|
||||
// determine the bounding box
|
||||
const x: any = d3.extent(points, function (pt: Position) {
|
||||
const x: any = d3.extent(points, (pt: Position) => {
|
||||
return pt.x;
|
||||
});
|
||||
const y: any = d3.extent(points, function (pt: Position) {
|
||||
const y: any = d3.extent(points, (pt: Position) => {
|
||||
return pt.y;
|
||||
});
|
||||
|
||||
const item = nodes[i];
|
||||
// consider it selected if its already selected or enclosed in the bounding box
|
||||
if (
|
||||
d3.select(this).classed('selected') ||
|
||||
d3.select(item).classed('selected') ||
|
||||
(x[0] >= selectionBoundingBox.x &&
|
||||
x[1] <= selectionBoundingBox.x + selectionBoundingBox.width &&
|
||||
y[0] >= selectionBoundingBox.y &&
|
||||
|
@ -575,7 +577,7 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
});
|
||||
|
||||
// dispatch the selected components
|
||||
self.store.dispatch(
|
||||
this.store.dispatch(
|
||||
selectComponents({
|
||||
request: {
|
||||
components: selection
|
||||
|
@ -593,4 +595,82 @@ export class Canvas implements OnInit, OnDestroy {
|
|||
this.store.dispatch(resetFlowState());
|
||||
this.store.dispatch(stopProcessGroupPolling());
|
||||
}
|
||||
|
||||
private executeAction(actionId: string, bypassCondition?: boolean): boolean {
|
||||
const selection = this.canvasUtils.getSelection();
|
||||
const canvasAction = this.canvasActionsService.getAction(actionId);
|
||||
if (canvasAction) {
|
||||
if (bypassCondition || canvasAction.condition(selection)) {
|
||||
canvasAction.action(selection);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.delete', ['$event'])
|
||||
handleKeyDownDelete() {
|
||||
this.executeAction('delete');
|
||||
}
|
||||
@HostListener('window:keydown.backspace', ['$event'])
|
||||
handleKeyDownBackspace() {
|
||||
this.executeAction('delete');
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.control.r', ['$event'])
|
||||
handleKeyDownCtrlR(event: KeyboardEvent) {
|
||||
if (this.executeAction('refresh', true)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
@HostListener('window:keydown.meta.r', ['$event'])
|
||||
handleKeyDownMetaR(event: KeyboardEvent) {
|
||||
if (this.executeAction('refresh', true)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.escape', ['$event'])
|
||||
handleKeyDownEsc() {
|
||||
this.executeAction('leaveGroup');
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.control.c', ['$event'])
|
||||
handleKeyDownCtrlC(event: KeyboardEvent) {
|
||||
if (this.executeAction('copy')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
@HostListener('window:keydown.meta.c', ['$event'])
|
||||
handleKeyDownMetaC(event: KeyboardEvent) {
|
||||
if (this.executeAction('copy')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.control.v', ['$event'])
|
||||
handleKeyDownCtrlV(event: KeyboardEvent) {
|
||||
if (this.executeAction('paste')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
@HostListener('window:keydown.meta.v', ['$event'])
|
||||
handleKeyDownMetaV(event: KeyboardEvent) {
|
||||
if (this.executeAction('paste')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.control.a', ['$event'])
|
||||
handleKeyDownCtrlA(event: KeyboardEvent) {
|
||||
if (this.executeAction('selectAll')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
@HostListener('window:keydown.meta.a', ['$event'])
|
||||
handleKeyDownMetaA(event: KeyboardEvent) {
|
||||
if (this.executeAction('selectAll')) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,45 +16,19 @@
|
|||
*/
|
||||
|
||||
import { Component, Input } from '@angular/core';
|
||||
import {
|
||||
copy,
|
||||
deleteComponents,
|
||||
disableComponents,
|
||||
disableCurrentProcessGroup,
|
||||
enableComponents,
|
||||
enableCurrentProcessGroup,
|
||||
getParameterContextsAndOpenGroupComponentsDialog,
|
||||
navigateToEditComponent,
|
||||
navigateToEditCurrentProcessGroup,
|
||||
navigateToManageComponentPolicies,
|
||||
paste,
|
||||
setOperationCollapsed,
|
||||
startComponents,
|
||||
startCurrentProcessGroup,
|
||||
stopComponents,
|
||||
stopCurrentProcessGroup
|
||||
} from '../../../../state/flow/flow.actions';
|
||||
import { setOperationCollapsed } from '../../../../state/flow/flow.actions';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { CanvasState } from '../../../../state';
|
||||
import { CanvasUtils } from '../../../../service/canvas-utils.service';
|
||||
import { initialState } from '../../../../state/flow/flow.reducer';
|
||||
import { Storage } from '../../../../../../service/storage.service';
|
||||
import {
|
||||
CopyComponentRequest,
|
||||
DeleteComponentRequest,
|
||||
DisableComponentRequest,
|
||||
EnableComponentRequest,
|
||||
MoveComponentRequest,
|
||||
StartComponentRequest,
|
||||
StopComponentRequest
|
||||
} from '../../../../state/flow';
|
||||
|
||||
import { BreadcrumbEntity } from '../../../../state/shared';
|
||||
import { ComponentType } from '../../../../../../state/shared';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import * as d3 from 'd3';
|
||||
import { CanvasView } from '../../../../service/canvas-view.service';
|
||||
import { Client } from '../../../../../../service/client.service';
|
||||
import { CanvasActionsService } from '../../../../service/canvas-actions.service';
|
||||
|
||||
@Component({
|
||||
selector: 'operation-control',
|
||||
|
@ -77,7 +51,8 @@ export class OperationControl {
|
|||
public canvasUtils: CanvasUtils,
|
||||
private canvasView: CanvasView,
|
||||
private client: Client,
|
||||
private storage: Storage
|
||||
private storage: Storage,
|
||||
private canvasActionsService: CanvasActionsService
|
||||
) {
|
||||
try {
|
||||
const item: { [key: string]: boolean } | null = this.storage.getItem(
|
||||
|
@ -199,23 +174,11 @@ export class OperationControl {
|
|||
}
|
||||
|
||||
canConfigure(selection: d3.Selection<any, any, any, any>): boolean {
|
||||
return this.canvasUtils.isConfigurable(selection);
|
||||
return this.canvasActionsService.getConditionFunction('configure')(selection);
|
||||
}
|
||||
|
||||
configure(selection: d3.Selection<any, any, any, any>): void {
|
||||
if (selection.empty()) {
|
||||
this.store.dispatch(navigateToEditCurrentProcessGroup());
|
||||
} else {
|
||||
const selectionData = selection.datum();
|
||||
this.store.dispatch(
|
||||
navigateToEditComponent({
|
||||
request: {
|
||||
type: selectionData.type,
|
||||
id: selectionData.id
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
this.canvasActionsService.getActionFunction('configure')(selection);
|
||||
}
|
||||
|
||||
supportsManagedAuthorizer(): boolean {
|
||||
|
@ -223,114 +186,29 @@ export class OperationControl {
|
|||
}
|
||||
|
||||
canManageAccess(selection: d3.Selection<any, any, any, any>): boolean {
|
||||
return this.canvasUtils.canManagePolicies(selection);
|
||||
return this.canvasActionsService.getConditionFunction('manageAccess')(selection);
|
||||
}
|
||||
|
||||
manageAccess(selection: d3.Selection<any, any, any, any>): void {
|
||||
if (selection.empty()) {
|
||||
this.store.dispatch(
|
||||
navigateToManageComponentPolicies({
|
||||
request: {
|
||||
resource: 'process-groups',
|
||||
id: this.breadcrumbEntity.id
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const selectionData = selection.datum();
|
||||
const componentType: ComponentType = selectionData.type;
|
||||
|
||||
let resource = 'process-groups';
|
||||
switch (componentType) {
|
||||
case ComponentType.Processor:
|
||||
resource = 'processors';
|
||||
break;
|
||||
case ComponentType.InputPort:
|
||||
resource = 'input-ports';
|
||||
break;
|
||||
case ComponentType.OutputPort:
|
||||
resource = 'output-ports';
|
||||
break;
|
||||
case ComponentType.Funnel:
|
||||
resource = 'funnels';
|
||||
break;
|
||||
case ComponentType.Label:
|
||||
resource = 'labels';
|
||||
break;
|
||||
case ComponentType.RemoteProcessGroup:
|
||||
resource = 'remote-process-groups';
|
||||
break;
|
||||
}
|
||||
|
||||
this.store.dispatch(
|
||||
navigateToManageComponentPolicies({
|
||||
request: {
|
||||
resource,
|
||||
id: selectionData.id
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
this.canvasActionsService.getActionFunction('manageAccess')(selection, {
|
||||
processGroupId: this.breadcrumbEntity.id
|
||||
});
|
||||
}
|
||||
|
||||
canEnable(selection: d3.Selection<any, any, any, any>): boolean {
|
||||
return this.canvasUtils.canEnable(selection);
|
||||
return this.canvasActionsService.getConditionFunction('enable')(selection);
|
||||
}
|
||||
|
||||
enable(selection: d3.Selection<any, any, any, any>): void {
|
||||
if (selection.empty()) {
|
||||
// attempting to enable the current process group
|
||||
this.store.dispatch(enableCurrentProcessGroup());
|
||||
} else {
|
||||
const components: EnableComponentRequest[] = [];
|
||||
const enableable = this.canvasUtils.filterEnable(selection);
|
||||
enableable.each((d: any) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
uri: d.uri,
|
||||
type: d.type,
|
||||
revision: this.client.getRevision(d),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
enableComponents({
|
||||
request: {
|
||||
components
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
this.canvasActionsService.getActionFunction('enable')(selection);
|
||||
}
|
||||
|
||||
canDisable(selection: d3.Selection<any, any, any, any>): boolean {
|
||||
return this.canvasUtils.canDisable(selection);
|
||||
return this.canvasActionsService.getConditionFunction('disable')(selection);
|
||||
}
|
||||
|
||||
disable(selection: d3.Selection<any, any, any, any>): void {
|
||||
if (selection.empty()) {
|
||||
// attempting to disable the current process group
|
||||
this.store.dispatch(disableCurrentProcessGroup());
|
||||
} else {
|
||||
const components: DisableComponentRequest[] = [];
|
||||
const disableable = this.canvasUtils.filterDisable(selection);
|
||||
disableable.each((d: any) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
uri: d.uri,
|
||||
type: d.type,
|
||||
revision: this.client.getRevision(d),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
disableComponents({
|
||||
request: {
|
||||
components
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
this.canvasActionsService.getActionFunction('disable')(selection);
|
||||
}
|
||||
|
||||
canStart(selection: d3.Selection<any, any, any, any>): boolean {
|
||||
|
@ -338,126 +216,39 @@ export class OperationControl {
|
|||
}
|
||||
|
||||
start(selection: d3.Selection<any, any, any, any>): void {
|
||||
if (selection.empty()) {
|
||||
// attempting to start the current process group
|
||||
this.store.dispatch(startCurrentProcessGroup());
|
||||
} else {
|
||||
const components: StartComponentRequest[] = [];
|
||||
const startable = this.canvasUtils.getStartable(selection);
|
||||
startable.each((d: any) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
uri: d.uri,
|
||||
type: d.type,
|
||||
revision: this.client.getRevision(d),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
startComponents({
|
||||
request: {
|
||||
components
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
this.canvasActionsService.getActionFunction('start')(selection);
|
||||
}
|
||||
|
||||
canStop(selection: d3.Selection<any, any, any, any>): boolean {
|
||||
return this.canvasUtils.areAnyStoppable(selection);
|
||||
return this.canvasActionsService.getConditionFunction('stop')(selection);
|
||||
}
|
||||
|
||||
stop(selection: d3.Selection<any, any, any, any>): void {
|
||||
if (selection.empty()) {
|
||||
// attempting to start the current process group
|
||||
this.store.dispatch(stopCurrentProcessGroup());
|
||||
} else {
|
||||
const components: StopComponentRequest[] = [];
|
||||
const stoppable = this.canvasUtils.getStoppable(selection);
|
||||
stoppable.each((d: any) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
uri: d.uri,
|
||||
type: d.type,
|
||||
revision: this.client.getRevision(d),
|
||||
errorStrategy: 'snackbar'
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
stopComponents({
|
||||
request: {
|
||||
components
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
this.canvasActionsService.getActionFunction('stop')(selection);
|
||||
}
|
||||
|
||||
canCopy(selection: d3.Selection<any, any, any, any>): boolean {
|
||||
return this.canvasUtils.isCopyable(selection);
|
||||
return this.canvasActionsService.getConditionFunction('copy')(selection);
|
||||
}
|
||||
|
||||
copy(selection: d3.Selection<any, any, any, any>): void {
|
||||
const components: CopyComponentRequest[] = [];
|
||||
selection.each((d) => {
|
||||
components.push({
|
||||
id: d.id,
|
||||
type: d.type,
|
||||
uri: d.uri,
|
||||
entity: d
|
||||
});
|
||||
});
|
||||
|
||||
const origin = this.canvasUtils.getOrigin(selection);
|
||||
const dimensions = this.canvasView.getSelectionBoundingClientRect(selection);
|
||||
|
||||
this.store.dispatch(
|
||||
copy({
|
||||
request: {
|
||||
components,
|
||||
origin,
|
||||
dimensions
|
||||
}
|
||||
})
|
||||
);
|
||||
this.canvasActionsService.getActionFunction('copy')(selection);
|
||||
}
|
||||
|
||||
canPaste(): boolean {
|
||||
return this.canvasUtils.isPastable();
|
||||
return this.canvasActionsService.getConditionFunction('paste')(d3.select(null));
|
||||
}
|
||||
|
||||
paste(): void {
|
||||
this.store.dispatch(
|
||||
paste({
|
||||
request: {}
|
||||
})
|
||||
);
|
||||
return this.canvasActionsService.getActionFunction('paste')(d3.select(null));
|
||||
}
|
||||
|
||||
canGroup(selection: d3.Selection<any, any, any, any>): boolean {
|
||||
return this.canvasUtils.isDisconnected(selection);
|
||||
return this.canvasActionsService.getConditionFunction('group')(selection);
|
||||
}
|
||||
|
||||
group(selection: d3.Selection<any, any, any, any>): void {
|
||||
const moveComponents: MoveComponentRequest[] = [];
|
||||
selection.each(function (d: any) {
|
||||
moveComponents.push({
|
||||
id: d.id,
|
||||
type: d.type,
|
||||
uri: d.uri,
|
||||
entity: d
|
||||
});
|
||||
});
|
||||
|
||||
// move the selection into the group
|
||||
this.store.dispatch(
|
||||
getParameterContextsAndOpenGroupComponentsDialog({
|
||||
request: {
|
||||
moveComponents,
|
||||
position: this.canvasUtils.getOrigin(selection)
|
||||
}
|
||||
})
|
||||
);
|
||||
this.canvasActionsService.getActionFunction('group')(selection);
|
||||
}
|
||||
|
||||
canColor(selection: d3.Selection<any, any, any, any>): boolean {
|
||||
|
@ -470,39 +261,10 @@ export class OperationControl {
|
|||
}
|
||||
|
||||
canDelete(selection: d3.Selection<any, any, any, any>): boolean {
|
||||
return this.canvasUtils.areDeletable(selection);
|
||||
return this.canvasActionsService.getConditionFunction('delete')(selection);
|
||||
}
|
||||
|
||||
delete(selection: d3.Selection<any, any, any, any>): void {
|
||||
if (selection.size() === 1) {
|
||||
const selectionData = selection.datum();
|
||||
this.store.dispatch(
|
||||
deleteComponents({
|
||||
request: [
|
||||
{
|
||||
id: selectionData.id,
|
||||
type: selectionData.type,
|
||||
uri: selectionData.uri,
|
||||
entity: selectionData
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const requests: DeleteComponentRequest[] = [];
|
||||
selection.each(function (d: any) {
|
||||
requests.push({
|
||||
id: d.id,
|
||||
type: d.type,
|
||||
uri: d.uri,
|
||||
entity: d
|
||||
});
|
||||
});
|
||||
this.store.dispatch(
|
||||
deleteComponents({
|
||||
request: requests
|
||||
})
|
||||
);
|
||||
}
|
||||
this.canvasActionsService.getActionFunction('delete')(selection);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<div
|
||||
class="context-menu pt-2 pb-2 mat-elevation-z8 primary-color"
|
||||
[class.show-focused]="showFocused$ | async"
|
||||
(keydown)="keydown()"
|
||||
(keydown)="keydown($event)"
|
||||
cdkMenu>
|
||||
@for (item of getMenuItems(menuId); track item) {
|
||||
@if (item.isSeparator) {
|
||||
|
|
|
@ -114,10 +114,13 @@ export class ContextMenu implements OnInit {
|
|||
return !!menuItemDefinition.subMenuId;
|
||||
}
|
||||
|
||||
keydown(): void {
|
||||
keydown(event: KeyboardEvent): void {
|
||||
// TODO - Currently the first item in the context menu is auto focused. By default, this is rendered with an
|
||||
// outline. This appears to be an issue with the cdkMenu/cdkMenuItem so we are working around it by manually
|
||||
// overriding styles.
|
||||
if (event.key === 'Escape') {
|
||||
event.stopPropagation();
|
||||
}
|
||||
this.showFocused.next(true);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue