mirror of https://github.com/apache/nifi.git
NIFI-12737: Removing all transitions when updating the Canvas transform (#8355)
* NIFI-12737: - Recording last canvas URL in local storage. - Using last canvas URL in global menu Canvas item. - Conditionally applying transitions when centering components. - Always applying transitions during zoom events (1:1, fit, zoom in/out). - Adding support to center more than one component. * NIFI-12737: - Fixing bug when attempting to click on the search result of the currently selected component. - Handling centering of a single selection different from a bulk selection as it performs betters with Connections. This closes #8355
This commit is contained in:
parent
91f339bf0f
commit
13c70c0f30
|
@ -20,7 +20,7 @@ import { CanvasUtils } from './canvas-utils.service';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { CanvasState } from '../state';
|
import { CanvasState } from '../state';
|
||||||
import {
|
import {
|
||||||
centerSelectedComponent,
|
centerSelectedComponents,
|
||||||
deleteComponents,
|
deleteComponents,
|
||||||
enterProcessGroup,
|
enterProcessGroup,
|
||||||
getParameterContextsAndOpenGroupComponentsDialog,
|
getParameterContextsAndOpenGroupComponentsDialog,
|
||||||
|
@ -928,12 +928,12 @@ export class CanvasContextMenu implements ContextMenuDefinitionProvider {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
condition: (selection: any) => {
|
condition: (selection: any) => {
|
||||||
return selection.size() === 1 && !this.canvasUtils.isConnection(selection);
|
return !selection.empty();
|
||||||
},
|
},
|
||||||
clazz: 'fa fa-crosshairs',
|
clazz: 'fa fa-crosshairs',
|
||||||
text: 'Center in view',
|
text: 'Center in view',
|
||||||
action: () => {
|
action: () => {
|
||||||
this.store.dispatch(centerSelectedComponent());
|
this.store.dispatch(centerSelectedComponents({ request: { allowTransition: true } }));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -590,6 +590,28 @@ export class CanvasUtils {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the position for centering a connection based on the presence of bends.
|
||||||
|
*
|
||||||
|
* @param d connection data
|
||||||
|
*/
|
||||||
|
public getPositionForCenteringConnection(d: any): Position {
|
||||||
|
let x, y;
|
||||||
|
if (d.bends.length > 0) {
|
||||||
|
const i: number = Math.min(Math.max(0, d.labelIndex), d.bends.length - 1);
|
||||||
|
x = d.bends[i].x;
|
||||||
|
y = d.bends[i].y;
|
||||||
|
} else {
|
||||||
|
x = (d.start.x + d.end.x) / 2;
|
||||||
|
y = (d.start.y + d.end.y) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the component id of the source of this processor. If the connection is attached
|
* Returns the component id of the source of this processor. If the connection is attached
|
||||||
* to a port in a [sub|remote] group, the component id will be that of the group. Otherwise
|
* to a port in a [sub|remote] group, the component id will be that of the group. Otherwise
|
||||||
|
@ -1484,6 +1506,7 @@ export class CanvasUtils {
|
||||||
});
|
});
|
||||||
return canStopTransmitting;
|
return canStopTransmitting;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the components in the specified selection can be operated.
|
* Determines whether the components in the specified selection can be operated.
|
||||||
*
|
*
|
||||||
|
|
|
@ -51,6 +51,7 @@ export class CanvasView {
|
||||||
private behavior: any;
|
private behavior: any;
|
||||||
|
|
||||||
private birdseyeTranslateInProgress = false;
|
private birdseyeTranslateInProgress = false;
|
||||||
|
private allowTransition = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private store: Store<CanvasState>,
|
private store: Store<CanvasState>,
|
||||||
|
@ -85,6 +86,10 @@ export class CanvasView {
|
||||||
this.svg = svg;
|
this.svg = svg;
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
|
|
||||||
|
this.k = INITIAL_SCALE;
|
||||||
|
this.x = INITIAL_TRANSLATE.x;
|
||||||
|
this.y = INITIAL_TRANSLATE.y;
|
||||||
|
|
||||||
this.labelManager.init();
|
this.labelManager.init();
|
||||||
this.funnelManager.init();
|
this.funnelManager.init();
|
||||||
this.portManager.init(viewContainerRef);
|
this.portManager.init(viewContainerRef);
|
||||||
|
@ -118,7 +123,7 @@ export class CanvasView {
|
||||||
|
|
||||||
// refresh the canvas
|
// refresh the canvas
|
||||||
refreshed = self.refresh({
|
refreshed = self.refresh({
|
||||||
transition: self.shouldTransition(event.sourceEvent),
|
transition: self.shouldTransition(),
|
||||||
refreshComponents: false,
|
refreshComponents: false,
|
||||||
refreshBirdseye: false
|
refreshBirdseye: false
|
||||||
});
|
});
|
||||||
|
@ -170,18 +175,73 @@ export class CanvasView {
|
||||||
return this.birdseyeTranslateInProgress;
|
return this.birdseyeTranslateInProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
// see if the scale has changed during this zoom event,
|
private shouldTransition(): boolean {
|
||||||
// we want to only transition when zooming in/out as running
|
|
||||||
// the transitions during pan events is undesirable
|
|
||||||
private shouldTransition(sourceEvent: any): boolean {
|
|
||||||
if (this.birdseyeTranslateInProgress) {
|
if (this.birdseyeTranslateInProgress) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceEvent) {
|
return this.allowTransition;
|
||||||
return sourceEvent.type === 'wheel' || sourceEvent.type === 'mousewheel';
|
}
|
||||||
|
|
||||||
|
public isSelectedComponentOnScreen(): boolean {
|
||||||
|
const canvasContainer: any = document.getElementById('canvas-container');
|
||||||
|
|
||||||
|
if (canvasContainer == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection: any = this.canvasUtils.getSelection();
|
||||||
|
if (selection.size() !== 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const d = selection.datum();
|
||||||
|
|
||||||
|
let translate = [this.x, this.y];
|
||||||
|
const scale = this.k;
|
||||||
|
|
||||||
|
// scale the translation
|
||||||
|
translate = [translate[0] / scale, translate[1] / scale];
|
||||||
|
|
||||||
|
// get the normalized screen width and height
|
||||||
|
const screenWidth = canvasContainer.offsetWidth / scale;
|
||||||
|
const screenHeight = canvasContainer.offsetHeight / scale;
|
||||||
|
|
||||||
|
// calculate the screen bounds one screens worth in each direction
|
||||||
|
const screenLeft = -translate[0];
|
||||||
|
const screenTop = -translate[1];
|
||||||
|
const screenRight = screenLeft + screenWidth;
|
||||||
|
const screenBottom = screenTop + screenHeight;
|
||||||
|
|
||||||
|
if (this.canvasUtils.isConnection(selection)) {
|
||||||
|
let connectionX, connectionY;
|
||||||
|
if (d.bends.length > 0) {
|
||||||
|
const i: number = Math.min(Math.max(0, d.labelIndex), d.bends.length - 1);
|
||||||
|
connectionX = d.bends[i].x;
|
||||||
|
connectionY = d.bends[i].y;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
connectionX = (d.start.x + d.end.x) / 2;
|
||||||
|
connectionY = (d.start.y + d.end.y) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
screenLeft < connectionX &&
|
||||||
|
screenRight > connectionX &&
|
||||||
|
screenTop < connectionY &&
|
||||||
|
screenBottom > connectionY
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const componentLeft: number = d.position.x;
|
||||||
|
const componentTop: number = d.position.y;
|
||||||
|
const componentRight: number = componentLeft + d.dimensions.width;
|
||||||
|
const componentBottom: number = componentTop + d.dimensions.height;
|
||||||
|
|
||||||
|
// determine if the component is now visible
|
||||||
|
return (
|
||||||
|
screenLeft < componentRight &&
|
||||||
|
screenRight > componentLeft &&
|
||||||
|
screenTop < componentBottom &&
|
||||||
|
screenBottom > componentTop
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,15 +290,7 @@ export class CanvasView {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let x, y;
|
const { x, y } = self.canvasUtils.getPositionForCenteringConnection(d);
|
||||||
if (d.bends.length > 0) {
|
|
||||||
const i: number = Math.min(Math.max(0, d.labelIndex), d.bends.length - 1);
|
|
||||||
x = d.bends[i].x;
|
|
||||||
y = d.bends[i].y;
|
|
||||||
} else {
|
|
||||||
x = (d.start.x + d.end.x) / 2;
|
|
||||||
y = (d.start.y + d.end.y) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
return screenLeft < x && screenRight > x && screenTop < y && screenBottom > y;
|
return screenLeft < x && screenRight > x && screenTop < y && screenBottom > y;
|
||||||
};
|
};
|
||||||
|
@ -293,7 +345,7 @@ export class CanvasView {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not a component should be rendered based solely on the current scale.
|
* Whether a component should be rendered based solely on the current scale.
|
||||||
*
|
*
|
||||||
* @returns {Boolean}
|
* @returns {Boolean}
|
||||||
*/
|
*/
|
||||||
|
@ -301,25 +353,38 @@ export class CanvasView {
|
||||||
return this.k >= CanvasView.MIN_SCALE_TO_RENDER;
|
return this.k >= CanvasView.MIN_SCALE_TO_RENDER;
|
||||||
}
|
}
|
||||||
|
|
||||||
public centerSelectedComponent(): void {
|
public centerSelectedComponents(allowTransition: boolean): void {
|
||||||
|
const canvasContainer: any = document.getElementById('canvas-container');
|
||||||
|
if (canvasContainer == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const selection: any = this.canvasUtils.getSelection();
|
const selection: any = this.canvasUtils.getSelection();
|
||||||
|
if (selection.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let bbox;
|
||||||
if (selection.size() === 1) {
|
if (selection.size() === 1) {
|
||||||
let box;
|
bbox = this.getSingleSelectionBoundingClientRect(selection);
|
||||||
|
} else {
|
||||||
|
bbox = this.getBulkSelectionBoundingClientRect(selection, canvasContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.allowTransition = allowTransition;
|
||||||
|
this.centerBoundingBox(bbox);
|
||||||
|
this.allowTransition = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSingleSelectionBoundingClientRect(selection: any): any {
|
||||||
|
let bbox;
|
||||||
if (this.canvasUtils.isConnection(selection)) {
|
if (this.canvasUtils.isConnection(selection)) {
|
||||||
let x, y;
|
|
||||||
const d = selection.datum();
|
const d = selection.datum();
|
||||||
|
|
||||||
// get the position of the connection label
|
// get the position of the connection label
|
||||||
if (d.bends.length > 0) {
|
const { x, y } = this.canvasUtils.getPositionForCenteringConnection(d);
|
||||||
const i: number = Math.min(Math.max(0, d.labelIndex), d.bends.length - 1);
|
|
||||||
x = d.bends[i].x;
|
|
||||||
y = d.bends[i].y;
|
|
||||||
} else {
|
|
||||||
x = (d.start.x + d.end.x) / 2;
|
|
||||||
y = (d.start.y + d.end.y) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
box = {
|
bbox = {
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
width: 1,
|
width: 1,
|
||||||
|
@ -329,7 +394,7 @@ export class CanvasView {
|
||||||
const selectionData = selection.datum();
|
const selectionData = selection.datum();
|
||||||
const selectionPosition = selectionData.position;
|
const selectionPosition = selectionData.position;
|
||||||
|
|
||||||
box = {
|
bbox = {
|
||||||
x: selectionPosition.x,
|
x: selectionPosition.x,
|
||||||
y: selectionPosition.y,
|
y: selectionPosition.y,
|
||||||
width: selectionData.dimensions.width,
|
width: selectionData.dimensions.width,
|
||||||
|
@ -337,9 +402,44 @@ export class CanvasView {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// center on the component
|
return bbox;
|
||||||
this.centerBoundingBox(box);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a BoundingClientRect, normalized to the canvas, that encompasses all nodes in a given selection.
|
||||||
|
*/
|
||||||
|
private getBulkSelectionBoundingClientRect(selection: any, canvasContainer: any): any {
|
||||||
|
const canvasBoundingBox: any = canvasContainer.getBoundingClientRect();
|
||||||
|
|
||||||
|
const initialBBox: any = {
|
||||||
|
x: Number.MAX_VALUE,
|
||||||
|
y: Number.MAX_VALUE,
|
||||||
|
right: Number.MIN_VALUE,
|
||||||
|
bottom: Number.MIN_VALUE
|
||||||
|
};
|
||||||
|
|
||||||
|
const bbox = selection.nodes().reduce((aggregateBBox: any, node: any) => {
|
||||||
|
const rect = node.getBoundingClientRect();
|
||||||
|
aggregateBBox.x = Math.min(rect.x, aggregateBBox.x);
|
||||||
|
aggregateBBox.y = Math.min(rect.y, aggregateBBox.y);
|
||||||
|
aggregateBBox.right = Math.max(rect.right, aggregateBBox.right);
|
||||||
|
aggregateBBox.bottom = Math.max(rect.bottom, aggregateBBox.bottom);
|
||||||
|
|
||||||
|
return aggregateBBox;
|
||||||
|
}, initialBBox);
|
||||||
|
|
||||||
|
// normalize the bounding box with scale and translate
|
||||||
|
bbox.x = (bbox.x - this.x) / this.k;
|
||||||
|
bbox.y = (bbox.y - canvasBoundingBox.top - this.y) / this.k;
|
||||||
|
bbox.right = (bbox.right - this.x) / this.k;
|
||||||
|
bbox.bottom = (bbox.bottom - canvasBoundingBox.top - this.y) / this.k;
|
||||||
|
|
||||||
|
bbox.width = bbox.right - bbox.x;
|
||||||
|
bbox.height = bbox.bottom - bbox.y;
|
||||||
|
bbox.top = bbox.y;
|
||||||
|
bbox.left = bbox.x;
|
||||||
|
|
||||||
|
return bbox;
|
||||||
}
|
}
|
||||||
|
|
||||||
private centerBoundingBox(boundingBox: any): void {
|
private centerBoundingBox(boundingBox: any): void {
|
||||||
|
@ -430,14 +530,18 @@ export class CanvasView {
|
||||||
* Zooms in a single zoom increment.
|
* Zooms in a single zoom increment.
|
||||||
*/
|
*/
|
||||||
public zoomIn(): void {
|
public zoomIn(): void {
|
||||||
|
this.allowTransition = true;
|
||||||
this.scale(CanvasView.INCREMENT);
|
this.scale(CanvasView.INCREMENT);
|
||||||
|
this.allowTransition = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zooms out a single zoom increment.
|
* Zooms out a single zoom increment.
|
||||||
*/
|
*/
|
||||||
public zoomOut(): void {
|
public zoomOut(): void {
|
||||||
|
this.allowTransition = true;
|
||||||
this.scale(1 / CanvasView.INCREMENT);
|
this.scale(1 / CanvasView.INCREMENT);
|
||||||
|
this.allowTransition = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -476,7 +580,7 @@ export class CanvasView {
|
||||||
graphTop -= 50;
|
graphTop -= 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
// center as appropriate
|
this.allowTransition = true;
|
||||||
this.centerBoundingBox({
|
this.centerBoundingBox({
|
||||||
x: graphLeft - translate[0] / scale,
|
x: graphLeft - translate[0] / scale,
|
||||||
y: graphTop - translate[1] / scale,
|
y: graphTop - translate[1] / scale,
|
||||||
|
@ -484,6 +588,7 @@ export class CanvasView {
|
||||||
height: canvasHeight / newScale,
|
height: canvasHeight / newScale,
|
||||||
scale: newScale
|
scale: newScale
|
||||||
});
|
});
|
||||||
|
this.allowTransition = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -530,8 +635,9 @@ export class CanvasView {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// center as appropriate
|
this.allowTransition = true;
|
||||||
this.centerBoundingBox(box);
|
this.centerBoundingBox(box);
|
||||||
|
this.allowTransition = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -109,15 +109,7 @@ export class ConnectionManager {
|
||||||
private getLabelPosition(connectionLabel: any): Position {
|
private getLabelPosition(connectionLabel: any): Position {
|
||||||
const d = connectionLabel.datum();
|
const d = connectionLabel.datum();
|
||||||
|
|
||||||
let x, y;
|
let { x, y } = this.canvasUtils.getPositionForCenteringConnection(d);
|
||||||
if (d.bends.length > 0) {
|
|
||||||
const i: number = Math.min(Math.max(0, d.labelIndex), d.bends.length - 1);
|
|
||||||
x = d.bends[i].x;
|
|
||||||
y = d.bends[i].y;
|
|
||||||
} else {
|
|
||||||
x = (d.start.x + d.end.x) / 2;
|
|
||||||
y = (d.start.y + d.end.y) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// offset to account for the label dimensions
|
// offset to account for the label dimensions
|
||||||
x -= ConnectionManager.DIMENSIONS.width / 2;
|
x -= ConnectionManager.DIMENSIONS.width / 2;
|
||||||
|
|
|
@ -70,7 +70,8 @@ import {
|
||||||
UploadProcessGroupRequest,
|
UploadProcessGroupRequest,
|
||||||
NavigateToQueueListing,
|
NavigateToQueueListing,
|
||||||
StartProcessGroupResponse,
|
StartProcessGroupResponse,
|
||||||
StopProcessGroupResponse
|
StopProcessGroupResponse,
|
||||||
|
CenterComponentRequest
|
||||||
} from './index';
|
} from './index';
|
||||||
import { StatusHistoryRequest } from '../../../../state/status-history';
|
import { StatusHistoryRequest } from '../../../../state/status-history';
|
||||||
|
|
||||||
|
@ -186,7 +187,10 @@ export const removeSelectedComponents = createAction(
|
||||||
props<{ request: SelectComponentsRequest }>()
|
props<{ request: SelectComponentsRequest }>()
|
||||||
);
|
);
|
||||||
|
|
||||||
export const centerSelectedComponent = createAction(`${CANVAS_PREFIX} Center Selected Component`);
|
export const centerSelectedComponents = createAction(
|
||||||
|
`${CANVAS_PREFIX} Center Selected Components`,
|
||||||
|
props<{ request: CenterComponentRequest }>()
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Create Component Actions
|
Create Component Actions
|
||||||
|
@ -425,11 +429,27 @@ export const setTransitionRequired = createAction(
|
||||||
props<{ transitionRequired: boolean }>()
|
props<{ transitionRequired: boolean }>()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* skipTransform is used when handling URL events for loading the current PG and component [bulk] selection. since the
|
||||||
|
* URL is the source of truth we need to indicate skipTransform when the URL changes based on the user selection on
|
||||||
|
* the canvas. However, we do not want the transform skipped when using link to open or a particular part of the flow.
|
||||||
|
* In these cases, we want the transform to be applied so the viewport is restored or the component(s) is centered.
|
||||||
|
*/
|
||||||
export const setSkipTransform = createAction(
|
export const setSkipTransform = createAction(
|
||||||
`${CANVAS_PREFIX} Set Skip Transform`,
|
`${CANVAS_PREFIX} Set Skip Transform`,
|
||||||
props<{ skipTransform: boolean }>()
|
props<{ skipTransform: boolean }>()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* allowTransition is a flag that can be set that indicates if a transition should be used when applying a transform.
|
||||||
|
* By default, restoring the viewport or selecting/centering components will not use a transition unless explicitly
|
||||||
|
* specified. Zoom based transforms (like fit or 1:1) will always use a transition.
|
||||||
|
*/
|
||||||
|
export const setAllowTransition = createAction(
|
||||||
|
`${CANVAS_PREFIX} Set Allow Transition`,
|
||||||
|
props<{ allowTransition: boolean }>()
|
||||||
|
);
|
||||||
|
|
||||||
export const navigateToComponent = createAction(
|
export const navigateToComponent = createAction(
|
||||||
`${CANVAS_PREFIX} Navigate To Component`,
|
`${CANVAS_PREFIX} Navigate To Component`,
|
||||||
props<{ request: NavigateToComponentRequest }>()
|
props<{ request: NavigateToComponentRequest }>()
|
||||||
|
|
|
@ -636,7 +636,12 @@ export class FlowEffects {
|
||||||
map((action) => action.request),
|
map((action) => action.request),
|
||||||
concatLatestFrom(() => this.store.select(selectCurrentProcessGroupId)),
|
concatLatestFrom(() => this.store.select(selectCurrentProcessGroupId)),
|
||||||
tap(([request, processGroupId]) => {
|
tap(([request, processGroupId]) => {
|
||||||
this.router.navigate(['/process-groups', processGroupId, request.type, request.id, 'edit']);
|
const url = ['/process-groups', processGroupId, request.type, request.id, 'edit'];
|
||||||
|
if (this.canvasView.isSelectedComponentOnScreen()) {
|
||||||
|
this.store.dispatch(FlowActions.navigateWithoutTransform({ url }));
|
||||||
|
} else {
|
||||||
|
this.router.navigate(url);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
{ dispatch: false }
|
{ dispatch: false }
|
||||||
|
@ -1771,15 +1776,15 @@ export class FlowEffects {
|
||||||
{ dispatch: false }
|
{ dispatch: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
centerSelectedComponent$ = createEffect(
|
centerSelectedComponents$ = createEffect(() =>
|
||||||
() =>
|
|
||||||
this.actions$.pipe(
|
this.actions$.pipe(
|
||||||
ofType(FlowActions.centerSelectedComponent),
|
ofType(FlowActions.centerSelectedComponents),
|
||||||
tap(() => {
|
map((action) => action.request),
|
||||||
this.canvasView.centerSelectedComponent();
|
tap((request) => {
|
||||||
})
|
this.canvasView.centerSelectedComponents(request.allowTransition);
|
||||||
),
|
}),
|
||||||
{ dispatch: false }
|
switchMap(() => of(FlowActions.setAllowTransition({ allowTransition: false })))
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
navigateToProvenanceForComponent$ = createEffect(
|
navigateToProvenanceForComponent$ = createEffect(
|
||||||
|
|
|
@ -41,6 +41,7 @@ import {
|
||||||
resetFlowState,
|
resetFlowState,
|
||||||
runOnce,
|
runOnce,
|
||||||
runOnceSuccess,
|
runOnceSuccess,
|
||||||
|
setAllowTransition,
|
||||||
setDragging,
|
setDragging,
|
||||||
setNavigationCollapsed,
|
setNavigationCollapsed,
|
||||||
setOperationCollapsed,
|
setOperationCollapsed,
|
||||||
|
@ -137,6 +138,7 @@ export const initialState: FlowState = {
|
||||||
saving: false,
|
saving: false,
|
||||||
transitionRequired: false,
|
transitionRequired: false,
|
||||||
skipTransform: false,
|
skipTransform: false,
|
||||||
|
allowTransition: false,
|
||||||
navigationCollapsed: false,
|
navigationCollapsed: false,
|
||||||
operationCollapsed: false,
|
operationCollapsed: false,
|
||||||
error: null,
|
error: null,
|
||||||
|
@ -297,15 +299,19 @@ export const flowReducer = createReducer(
|
||||||
}),
|
}),
|
||||||
on(setDragging, (state, { dragging }) => ({
|
on(setDragging, (state, { dragging }) => ({
|
||||||
...state,
|
...state,
|
||||||
dragging: dragging
|
dragging
|
||||||
})),
|
})),
|
||||||
on(setTransitionRequired, (state, { transitionRequired }) => ({
|
on(setTransitionRequired, (state, { transitionRequired }) => ({
|
||||||
...state,
|
...state,
|
||||||
transitionRequired: transitionRequired
|
transitionRequired
|
||||||
})),
|
})),
|
||||||
on(setSkipTransform, (state, { skipTransform }) => ({
|
on(setSkipTransform, (state, { skipTransform }) => ({
|
||||||
...state,
|
...state,
|
||||||
skipTransform: skipTransform
|
skipTransform
|
||||||
|
})),
|
||||||
|
on(setAllowTransition, (state, { allowTransition }) => ({
|
||||||
|
...state,
|
||||||
|
allowTransition
|
||||||
})),
|
})),
|
||||||
on(navigateWithoutTransform, (state) => ({
|
on(navigateWithoutTransform, (state) => ({
|
||||||
...state,
|
...state,
|
||||||
|
@ -313,11 +319,11 @@ export const flowReducer = createReducer(
|
||||||
})),
|
})),
|
||||||
on(setNavigationCollapsed, (state, { navigationCollapsed }) => ({
|
on(setNavigationCollapsed, (state, { navigationCollapsed }) => ({
|
||||||
...state,
|
...state,
|
||||||
navigationCollapsed: navigationCollapsed
|
navigationCollapsed
|
||||||
})),
|
})),
|
||||||
on(setOperationCollapsed, (state, { operationCollapsed }) => ({
|
on(setOperationCollapsed, (state, { operationCollapsed }) => ({
|
||||||
...state,
|
...state,
|
||||||
operationCollapsed: operationCollapsed
|
operationCollapsed
|
||||||
})),
|
})),
|
||||||
on(startComponentSuccess, stopComponentSuccess, (state, { response }) => {
|
on(startComponentSuccess, stopComponentSuccess, (state, { response }) => {
|
||||||
return produce(state, (draftState) => {
|
return produce(state, (draftState) => {
|
||||||
|
|
|
@ -80,7 +80,7 @@ export const selectAnySelectedComponentIds = createSelector(selectCurrentRoute,
|
||||||
|
|
||||||
export const selectBulkSelectedComponentIds = createSelector(selectCurrentRoute, (route) => {
|
export const selectBulkSelectedComponentIds = createSelector(selectCurrentRoute, (route) => {
|
||||||
const ids: string[] = [];
|
const ids: string[] = [];
|
||||||
// only handle either bulk component route
|
// only handle bulk component route
|
||||||
if (route?.params.ids) {
|
if (route?.params.ids) {
|
||||||
ids.push(...route.params.ids.split(','));
|
ids.push(...route.params.ids.split(','));
|
||||||
}
|
}
|
||||||
|
@ -140,6 +140,8 @@ export const selectDragging = createSelector(selectFlowState, (state: FlowState)
|
||||||
|
|
||||||
export const selectSkipTransform = createSelector(selectFlowState, (state: FlowState) => state.skipTransform);
|
export const selectSkipTransform = createSelector(selectFlowState, (state: FlowState) => state.skipTransform);
|
||||||
|
|
||||||
|
export const selectAllowTransition = createSelector(selectFlowState, (state: FlowState) => state.allowTransition);
|
||||||
|
|
||||||
export const selectFunnels = createSelector(
|
export const selectFunnels = createSelector(
|
||||||
selectFlowState,
|
selectFlowState,
|
||||||
(state: FlowState) => state.flow.processGroupFlow?.flow.funnels
|
(state: FlowState) => state.flow.processGroupFlow?.flow.funnels
|
||||||
|
|
|
@ -40,6 +40,10 @@ export interface SelectComponentsRequest {
|
||||||
components: SelectedComponent[];
|
components: SelectedComponent[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CenterComponentRequest {
|
||||||
|
allowTransition: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Load Process Group
|
Load Process Group
|
||||||
*/
|
*/
|
||||||
|
@ -473,6 +477,7 @@ export interface FlowState {
|
||||||
dragging: boolean;
|
dragging: boolean;
|
||||||
transitionRequired: boolean;
|
transitionRequired: boolean;
|
||||||
skipTransform: boolean;
|
skipTransform: boolean;
|
||||||
|
allowTransition: boolean;
|
||||||
saving: boolean;
|
saving: boolean;
|
||||||
navigationCollapsed: boolean;
|
navigationCollapsed: boolean;
|
||||||
operationCollapsed: boolean;
|
operationCollapsed: boolean;
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { CanvasState } from '../../state';
|
||||||
import { Position } from '../../state/shared';
|
import { Position } from '../../state/shared';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import {
|
import {
|
||||||
centerSelectedComponent,
|
centerSelectedComponents,
|
||||||
deselectAllComponents,
|
deselectAllComponents,
|
||||||
editComponent,
|
editComponent,
|
||||||
editCurrentProcessGroup,
|
editCurrentProcessGroup,
|
||||||
|
@ -38,6 +38,7 @@ import { selectTransform } from '../../state/transform/transform.selectors';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
import { SelectedComponent } from '../../state/flow';
|
import { SelectedComponent } from '../../state/flow';
|
||||||
import {
|
import {
|
||||||
|
selectAllowTransition,
|
||||||
selectBulkSelectedComponentIds,
|
selectBulkSelectedComponentIds,
|
||||||
selectConnection,
|
selectConnection,
|
||||||
selectCurrentProcessGroupId,
|
selectCurrentProcessGroupId,
|
||||||
|
@ -57,13 +58,15 @@ import {
|
||||||
selectViewStatusHistoryComponent
|
selectViewStatusHistoryComponent
|
||||||
} from '../../state/flow/flow.selectors';
|
} from '../../state/flow/flow.selectors';
|
||||||
import { filter, map, switchMap, take } from 'rxjs';
|
import { filter, map, switchMap, take } from 'rxjs';
|
||||||
import { restoreViewport, zoomFit } from '../../state/transform/transform.actions';
|
import { restoreViewport } from '../../state/transform/transform.actions';
|
||||||
import { ComponentType, isDefinedAndNotNull } from '../../../../state/shared';
|
import { ComponentType, isDefinedAndNotNull } from '../../../../state/shared';
|
||||||
import { initialState } from '../../state/flow/flow.reducer';
|
import { initialState } from '../../state/flow/flow.reducer';
|
||||||
import { CanvasContextMenu } from '../../service/canvas-context-menu.service';
|
import { CanvasContextMenu } from '../../service/canvas-context-menu.service';
|
||||||
import { getStatusHistoryAndOpenDialog } from '../../../../state/status-history/status-history.actions';
|
import { getStatusHistoryAndOpenDialog } from '../../../../state/status-history/status-history.actions';
|
||||||
import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
|
import { loadFlowConfiguration } from '../../../../state/flow-configuration/flow-configuration.actions';
|
||||||
import { concatLatestFrom } from '@ngrx/effects';
|
import { concatLatestFrom } from '@ngrx/effects';
|
||||||
|
import { selectUrl } from '../../../../state/router/router.selectors';
|
||||||
|
import { Storage } from '../../../../service/storage.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'fd-canvas',
|
selector: 'fd-canvas',
|
||||||
|
@ -81,6 +84,7 @@ export class Canvas implements OnInit, OnDestroy {
|
||||||
private viewContainerRef: ViewContainerRef,
|
private viewContainerRef: ViewContainerRef,
|
||||||
private store: Store<CanvasState>,
|
private store: Store<CanvasState>,
|
||||||
private canvasView: CanvasView,
|
private canvasView: CanvasView,
|
||||||
|
private storage: Storage,
|
||||||
public canvasContextMenu: CanvasContextMenu
|
public canvasContextMenu: CanvasContextMenu
|
||||||
) {
|
) {
|
||||||
this.store
|
this.store
|
||||||
|
@ -90,6 +94,13 @@ export class Canvas implements OnInit, OnDestroy {
|
||||||
this.scale = transform.scale;
|
this.scale = transform.scale;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.store
|
||||||
|
.select(selectUrl)
|
||||||
|
.pipe(takeUntilDestroyed())
|
||||||
|
.subscribe((route) => {
|
||||||
|
this.storage.setItem('current-canvas-route', route);
|
||||||
|
});
|
||||||
|
|
||||||
// load the process group from the route
|
// load the process group from the route
|
||||||
this.store
|
this.store
|
||||||
.select(selectProcessGroupIdFromRoute)
|
.select(selectProcessGroupIdFromRoute)
|
||||||
|
@ -133,14 +144,17 @@ export class Canvas implements OnInit, OnDestroy {
|
||||||
filter((processGroupId) => processGroupId != initialState.id),
|
filter((processGroupId) => processGroupId != initialState.id),
|
||||||
switchMap(() => this.store.select(selectSingleSelectedComponent)),
|
switchMap(() => this.store.select(selectSingleSelectedComponent)),
|
||||||
filter((selectedComponent) => selectedComponent != null),
|
filter((selectedComponent) => selectedComponent != null),
|
||||||
concatLatestFrom(() => this.store.select(selectSkipTransform)),
|
concatLatestFrom(() => [
|
||||||
|
this.store.select(selectSkipTransform),
|
||||||
|
this.store.select(selectAllowTransition)
|
||||||
|
]),
|
||||||
takeUntilDestroyed()
|
takeUntilDestroyed()
|
||||||
)
|
)
|
||||||
.subscribe(([, skipTransform]) => {
|
.subscribe(([, skipTransform, allowTransition]) => {
|
||||||
if (skipTransform) {
|
if (skipTransform) {
|
||||||
this.store.dispatch(setSkipTransform({ skipTransform: false }));
|
this.store.dispatch(setSkipTransform({ skipTransform: false }));
|
||||||
} else {
|
} else {
|
||||||
this.store.dispatch(centerSelectedComponent());
|
this.store.dispatch(centerSelectedComponents({ request: { allowTransition } }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -151,14 +165,17 @@ export class Canvas implements OnInit, OnDestroy {
|
||||||
filter((processGroupId) => processGroupId != initialState.id),
|
filter((processGroupId) => processGroupId != initialState.id),
|
||||||
switchMap(() => this.store.select(selectBulkSelectedComponentIds)),
|
switchMap(() => this.store.select(selectBulkSelectedComponentIds)),
|
||||||
filter((ids) => ids.length > 0),
|
filter((ids) => ids.length > 0),
|
||||||
concatLatestFrom(() => this.store.select(selectSkipTransform)),
|
concatLatestFrom(() => [
|
||||||
|
this.store.select(selectSkipTransform),
|
||||||
|
this.store.select(selectAllowTransition)
|
||||||
|
]),
|
||||||
takeUntilDestroyed()
|
takeUntilDestroyed()
|
||||||
)
|
)
|
||||||
.subscribe(([, skipTransform]) => {
|
.subscribe(([, skipTransform, allowTransition]) => {
|
||||||
if (skipTransform) {
|
if (skipTransform) {
|
||||||
this.store.dispatch(setSkipTransform({ skipTransform: false }));
|
this.store.dispatch(setSkipTransform({ skipTransform: false }));
|
||||||
} else {
|
} else {
|
||||||
this.store.dispatch(zoomFit());
|
this.store.dispatch(centerSelectedComponents({ request: { allowTransition } }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -18,27 +18,41 @@
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { FlowStatus } from './flow-status.component';
|
import { FlowStatus } from './flow-status.component';
|
||||||
import { Search } from '../search/search.component';
|
|
||||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
|
import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
|
||||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
|
import { initialState } from '../../../../state/flow/flow.reducer';
|
||||||
|
|
||||||
describe('FlowStatus', () => {
|
describe('FlowStatus', () => {
|
||||||
let component: FlowStatus;
|
let component: FlowStatus;
|
||||||
let fixture: ComponentFixture<FlowStatus>;
|
let fixture: ComponentFixture<FlowStatus>;
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'search',
|
||||||
|
standalone: true,
|
||||||
|
template: ''
|
||||||
|
})
|
||||||
|
class MockSearch {}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
FlowStatus,
|
FlowStatus,
|
||||||
Search,
|
MockSearch,
|
||||||
HttpClientTestingModule,
|
HttpClientTestingModule,
|
||||||
CdkOverlayOrigin,
|
CdkOverlayOrigin,
|
||||||
CdkConnectedOverlay,
|
CdkConnectedOverlay,
|
||||||
MatAutocompleteModule,
|
MatAutocompleteModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule
|
ReactiveFormsModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
provideMockStore({
|
||||||
|
initialState
|
||||||
|
})
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(FlowStatus);
|
fixture = TestBed.createComponent(FlowStatus);
|
||||||
|
|
|
@ -186,10 +186,21 @@
|
||||||
</li>
|
</li>
|
||||||
<!-- TODO - Consider showing more context of match like existing UI -->
|
<!-- TODO - Consider showing more context of match like existing UI -->
|
||||||
<li *ngFor="let result of results" class="ml-2 py-1">
|
<li *ngFor="let result of results" class="ml-2 py-1">
|
||||||
<a *ngIf="!result.parentGroup; else resultLink" [routerLink]="['/process-groups', result.id]">
|
<a *ngIf="!result.parentGroup; else componentLink" [routerLink]="['/process-groups', result.id]">
|
||||||
{{ result.name }}
|
{{ result.name }}
|
||||||
</a>
|
</a>
|
||||||
<ng-template #resultLink>
|
<ng-template #componentLink>
|
||||||
|
<a
|
||||||
|
*ngIf="
|
||||||
|
result.parentGroup.id == currentProcessGroupId;
|
||||||
|
else componentInDifferentProcessGroupLink
|
||||||
|
"
|
||||||
|
(click)="componentLinkClicked(path, result.id)"
|
||||||
|
[routerLink]="['/process-groups', result.parentGroup.id, path, result.id]">
|
||||||
|
{{ result.name ? result.name : result.id }}
|
||||||
|
</a>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template #componentInDifferentProcessGroupLink>
|
||||||
<a [routerLink]="['/process-groups', result.parentGroup.id, path, result.id]">
|
<a [routerLink]="['/process-groups', result.parentGroup.id, path, result.id]">
|
||||||
{{ result.name ? result.name : result.id }}
|
{{ result.name ? result.name : result.id }}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -22,6 +22,8 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
|
import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
|
import { initialState } from '../../../../state/flow/flow.reducer';
|
||||||
|
|
||||||
describe('Search', () => {
|
describe('Search', () => {
|
||||||
let component: Search;
|
let component: Search;
|
||||||
|
@ -37,6 +39,11 @@ describe('Search', () => {
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
CdkConnectedOverlay,
|
CdkConnectedOverlay,
|
||||||
MatAutocompleteModule
|
MatAutocompleteModule
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
provideMockStore({
|
||||||
|
initialState
|
||||||
|
})
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
fixture = TestBed.createComponent(Search);
|
fixture = TestBed.createComponent(Search);
|
||||||
|
|
|
@ -32,6 +32,11 @@ import { NgForOf, NgIf, NgTemplateOutlet } from '@angular/common';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { CanvasState } from '../../../../state';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { centerSelectedComponents, setAllowTransition } from '../../../../state/flow/flow.actions';
|
||||||
|
import { selectCurrentRoute } from '../../../../../../state/router/router.selectors';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'search',
|
selector: 'search',
|
||||||
|
@ -86,11 +91,25 @@ export class Search implements OnInit {
|
||||||
parameterProviderNodeResults: ComponentSearchResult[] = [];
|
parameterProviderNodeResults: ComponentSearchResult[] = [];
|
||||||
parameterResults: ComponentSearchResult[] = [];
|
parameterResults: ComponentSearchResult[] = [];
|
||||||
|
|
||||||
|
selectedComponentType: ComponentType | null = null;
|
||||||
|
selectedComponentId: string | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private formBuilder: FormBuilder,
|
private formBuilder: FormBuilder,
|
||||||
private searchService: SearchService
|
private searchService: SearchService,
|
||||||
|
private store: Store<CanvasState>
|
||||||
) {
|
) {
|
||||||
this.searchForm = this.formBuilder.group({ searchBar: '' });
|
this.searchForm = this.formBuilder.group({ searchBar: '' });
|
||||||
|
|
||||||
|
this.store
|
||||||
|
.select(selectCurrentRoute)
|
||||||
|
.pipe(takeUntilDestroyed())
|
||||||
|
.subscribe((route) => {
|
||||||
|
if (route?.params) {
|
||||||
|
this.selectedComponentId = route.params.id;
|
||||||
|
this.selectedComponentType = route.params.type;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
@ -169,4 +188,12 @@ export class Search implements OnInit {
|
||||||
this.parameterProviderNodeResults = [];
|
this.parameterProviderNodeResults = [];
|
||||||
this.parameterResults = [];
|
this.parameterResults = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentLinkClicked(componentType: ComponentType, id: string): void {
|
||||||
|
if (componentType == this.selectedComponentType && id == this.selectedComponentId) {
|
||||||
|
this.store.dispatch(centerSelectedComponents({ request: { allowTransition: true } }));
|
||||||
|
} else {
|
||||||
|
this.store.dispatch(setAllowTransition({ allowTransition: true }));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
<i class="fa fa-navicon"></i>
|
<i class="fa fa-navicon"></i>
|
||||||
</button>
|
</button>
|
||||||
<mat-menu #globalMenu="matMenu" xPosition="before">
|
<mat-menu #globalMenu="matMenu" xPosition="before">
|
||||||
<button mat-menu-item class="global-menu-item" [routerLink]="['/']">
|
<button mat-menu-item class="global-menu-item" [routerLink]="getCanvasLink()">
|
||||||
<i class="icon icon-drop mr-2"></i>
|
<i class="icon icon-drop mr-2"></i>
|
||||||
Canvas
|
Canvas
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -32,6 +32,7 @@ import { MatButtonModule } from '@angular/material/button';
|
||||||
import { NiFiState } from '../../../state';
|
import { NiFiState } from '../../../state';
|
||||||
import { selectFlowConfiguration } from '../../../state/flow-configuration/flow-configuration.selectors';
|
import { selectFlowConfiguration } from '../../../state/flow-configuration/flow-configuration.selectors';
|
||||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||||
|
import { Storage } from '../../../service/storage.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'navigation',
|
selector: 'navigation',
|
||||||
|
@ -59,6 +60,7 @@ export class Navigation {
|
||||||
private store: Store<NiFiState>,
|
private store: Store<NiFiState>,
|
||||||
private authStorage: AuthStorage,
|
private authStorage: AuthStorage,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
|
private storage: Storage,
|
||||||
@Inject(DOCUMENT) private _document: Document
|
@Inject(DOCUMENT) private _document: Document
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@ -94,6 +96,11 @@ export class Navigation {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCanvasLink(): string {
|
||||||
|
const canvasRoute = this.storage.getItem('current-canvas-route');
|
||||||
|
return canvasRoute || '/';
|
||||||
|
}
|
||||||
|
|
||||||
toggleTheme(value = !this.isDarkMode) {
|
toggleTheme(value = !this.isDarkMode) {
|
||||||
this.isDarkMode = value;
|
this.isDarkMode = value;
|
||||||
this._document.body.classList.toggle('dark-theme', value);
|
this._document.body.classList.toggle('dark-theme', value);
|
||||||
|
|
Loading…
Reference in New Issue