feat(dep-graph): add experimental collapse edges option (#9004)
This commit is contained in:
parent
2f78f29483
commit
31d51c36fd
12
dep-graph/client-e2e/cypress-release.json
Normal file
12
dep-graph/client-e2e/cypress-release.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"fileServerFolder": ".",
|
||||||
|
"fixturesFolder": "./src/fixtures",
|
||||||
|
"integrationFolder": "./src/release-integration",
|
||||||
|
"modifyObstructiveCode": false,
|
||||||
|
"pluginsFile": "./src/plugins/index",
|
||||||
|
"supportFile": "./src/support/index.ts",
|
||||||
|
"video": true,
|
||||||
|
"videosFolder": "../../dist/cypress/dep-graph/client-e2e/videos",
|
||||||
|
"screenshotsFolder": "../../dist/cypress/dep-graph/client-e2e/screenshots",
|
||||||
|
"chromeWebSecurity": false
|
||||||
|
}
|
||||||
@ -21,6 +21,15 @@
|
|||||||
"baseUrl": "http://localhost:4200"
|
"baseUrl": "http://localhost:4200"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"e2e-release-disabled": {
|
||||||
|
"executor": "@nrwl/cypress:cypress",
|
||||||
|
"options": {
|
||||||
|
"cypressConfig": "dep-graph/client-e2e/cypress-release.json",
|
||||||
|
"tsConfig": "dep-graph/client-e2e/tsconfig.e2e.json",
|
||||||
|
"devServerTarget": "dep-graph-client:serve-for-e2e:release",
|
||||||
|
"baseUrl": "http://localhost:4200"
|
||||||
|
}
|
||||||
|
},
|
||||||
"lint": {
|
"lint": {
|
||||||
"executor": "@nrwl/linter:eslint",
|
"executor": "@nrwl/linter:eslint",
|
||||||
"outputs": ["{options.outputFile}"],
|
"outputs": ["{options.outputFile}"],
|
||||||
|
|||||||
@ -68,7 +68,7 @@ describe('dep-graph-client', () => {
|
|||||||
|
|
||||||
describe('selecting a different project', () => {
|
describe('selecting a different project', () => {
|
||||||
it('should change the available projects', () => {
|
it('should change the available projects', () => {
|
||||||
getProjectItems().should('have.length', 55);
|
getProjectItems().should('have.length', 53);
|
||||||
cy.get('[data-cy=project-select]').select('Ocean', { force: true });
|
cy.get('[data-cy=project-select]').select('Ocean', { force: true });
|
||||||
getProjectItems().should('have.length', 124);
|
getProjectItems().should('have.length', 124);
|
||||||
});
|
});
|
||||||
@ -77,14 +77,14 @@ describe('dep-graph-client', () => {
|
|||||||
describe('select all button', () => {
|
describe('select all button', () => {
|
||||||
it('should check all project items', () => {
|
it('should check all project items', () => {
|
||||||
getSelectAllButton().scrollIntoView().click({ force: true });
|
getSelectAllButton().scrollIntoView().click({ force: true });
|
||||||
getCheckedProjectItems().should('have.length', 55);
|
getCheckedProjectItems().should('have.length', 53);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deselect all button', () => {
|
describe('deselect all button', () => {
|
||||||
it('should uncheck all project items', () => {
|
it('should uncheck all project items', () => {
|
||||||
getDeselectAllButton().click();
|
getDeselectAllButton().click();
|
||||||
getUncheckedProjectItems().should('have.length', 55);
|
getUncheckedProjectItems().should('have.length', 53);
|
||||||
getSelectProjectsMessage().should('be.visible');
|
getSelectProjectsMessage().should('be.visible');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -156,7 +156,7 @@ describe('dep-graph-client', () => {
|
|||||||
cy.get('[data-project="nx-dev"]').prev('button').click({ force: true });
|
cy.get('[data-project="nx-dev"]').prev('button').click({ force: true });
|
||||||
getUnfocusProjectButton().click();
|
getUnfocusProjectButton().click();
|
||||||
|
|
||||||
getUncheckedProjectItems().should('have.length', 55);
|
getUncheckedProjectItems().should('have.length', 53);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -263,6 +263,6 @@ describe('loading dep-graph client with url params', () => {
|
|||||||
// wait for first graph to finish loading
|
// wait for first graph to finish loading
|
||||||
cy.wait('@getGraph');
|
cy.wait('@getGraph');
|
||||||
|
|
||||||
getCheckedProjectItems().should('have.length', 55);
|
getCheckedProjectItems().should('have.length', 53);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
18
dep-graph/client-e2e/src/release-integration/release.spec.ts
Normal file
18
dep-graph/client-e2e/src/release-integration/release.spec.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
describe('dep-graph-client release', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.intercept('/assets/graphs/*').as('getGraph');
|
||||||
|
|
||||||
|
cy.visit('/');
|
||||||
|
|
||||||
|
// wait for first graph to finish loading
|
||||||
|
cy.wait('@getGraph');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not display experimental features', () => {
|
||||||
|
cy.get('experimental-features').should('not.exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not display the debugger', () => {
|
||||||
|
cy.get('debugger-panel').should('not.exist');
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -97,6 +97,12 @@
|
|||||||
"npx ts-node -P ./scripts/tsconfig.scripts.json ./scripts/copy-dep-graph-environment.ts watch",
|
"npx ts-node -P ./scripts/tsconfig.scripts.json ./scripts/copy-dep-graph-environment.ts watch",
|
||||||
"nx serve-base dep-graph-client"
|
"nx serve-base dep-graph-client"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"commands": [
|
||||||
|
"npx ts-node -P ./scripts/tsconfig.scripts.json ./scripts/copy-dep-graph-environment.ts release",
|
||||||
|
"nx serve-base dep-graph-client"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -117,6 +123,13 @@
|
|||||||
"nx serve-base dep-graph-client"
|
"nx serve-base dep-graph-client"
|
||||||
],
|
],
|
||||||
"readyWhen": "No issues found."
|
"readyWhen": "No issues found."
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"commands": [
|
||||||
|
"npx ts-node -P ./scripts/tsconfig.scripts.json ./scripts/copy-dep-graph-environment.ts release",
|
||||||
|
"nx serve-base dep-graph-client"
|
||||||
|
],
|
||||||
|
"readyWhen": "No issues found."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export const DebuggerPanel = memo(function ({
|
|||||||
}: DebuggerPanelProps) {
|
}: DebuggerPanelProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id="debugger-panel"
|
data-cy="debugger-panel"
|
||||||
className="
|
className="
|
||||||
flex-column
|
flex-column
|
||||||
flex
|
flex
|
||||||
|
|||||||
18
dep-graph/client/src/app/experimental-feature.tsx
Normal file
18
dep-graph/client/src/app/experimental-feature.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { useEnvironmentConfig } from './hooks/use-environment-config';
|
||||||
|
|
||||||
|
function ExperimentalFeature(props) {
|
||||||
|
const environment = useEnvironmentConfig();
|
||||||
|
const showExperimentalFeatures =
|
||||||
|
environment.appConfig.showExperimentalFeatures;
|
||||||
|
|
||||||
|
return showExperimentalFeatures ? (
|
||||||
|
<div data-cy="experimental-features" className="bg-purple-200 pb-2">
|
||||||
|
<h3 className="mt-4 cursor-text px-4 py-2 text-sm font-semibold uppercase tracking-wide text-gray-900 lg:text-xs ">
|
||||||
|
Experimental Features
|
||||||
|
</h3>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExperimentalFeature;
|
||||||
@ -22,6 +22,7 @@ export interface Environment {
|
|||||||
|
|
||||||
export interface AppConfig {
|
export interface AppConfig {
|
||||||
showDebugger: boolean;
|
showDebugger: boolean;
|
||||||
|
showExperimentalFeatures: boolean;
|
||||||
projectGraphs: ProjectGraphList[];
|
projectGraphs: ProjectGraphList[];
|
||||||
defaultProjectGraph: string;
|
defaultProjectGraph: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ export const initialContext: DepGraphContext = {
|
|||||||
searchDepth: 1,
|
searchDepth: 1,
|
||||||
searchDepthEnabled: false,
|
searchDepthEnabled: false,
|
||||||
groupByFolder: false,
|
groupByFolder: false,
|
||||||
|
collapseEdges: false,
|
||||||
workspaceLayout: {
|
workspaceLayout: {
|
||||||
libsDir: '',
|
libsDir: '',
|
||||||
appsDir: '',
|
appsDir: '',
|
||||||
@ -66,6 +67,7 @@ export const depGraphMachine = Machine<
|
|||||||
affectedProjects: ctx.affectedProjects,
|
affectedProjects: ctx.affectedProjects,
|
||||||
workspaceLayout: ctx.workspaceLayout,
|
workspaceLayout: ctx.workspaceLayout,
|
||||||
groupByFolder: ctx.groupByFolder,
|
groupByFolder: ctx.groupByFolder,
|
||||||
|
collapseEdges: ctx.collapseEdges,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
to: (context) => context.graphActor,
|
to: (context) => context.graphActor,
|
||||||
@ -112,6 +114,39 @@ export const depGraphMachine = Machine<
|
|||||||
focusProject: {
|
focusProject: {
|
||||||
target: 'focused',
|
target: 'focused',
|
||||||
},
|
},
|
||||||
|
setCollapseEdges: {
|
||||||
|
actions: [
|
||||||
|
'setCollapseEdges',
|
||||||
|
send(
|
||||||
|
(ctx, event) => ({
|
||||||
|
type: 'notifyGraphUpdateGraph',
|
||||||
|
projects: ctx.projects,
|
||||||
|
dependencies: ctx.dependencies,
|
||||||
|
affectedProjects: ctx.affectedProjects,
|
||||||
|
workspaceLayout: ctx.workspaceLayout,
|
||||||
|
groupByFolder: ctx.groupByFolder,
|
||||||
|
collapseEdges: ctx.collapseEdges,
|
||||||
|
selectedProjects: ctx.selectedProjects,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
to: (context) => context.graphActor,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
send(
|
||||||
|
(ctx, event) => {
|
||||||
|
if (event.type !== 'setCollapseEdges') return;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'notifyRouteCollapseEdges',
|
||||||
|
collapseEdges: event.collapseEdges,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: (context) => context.routeSetterActor,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
setGroupByFolder: {
|
setGroupByFolder: {
|
||||||
actions: [
|
actions: [
|
||||||
'setGroupByFolder',
|
'setGroupByFolder',
|
||||||
@ -123,6 +158,7 @@ export const depGraphMachine = Machine<
|
|||||||
affectedProjects: ctx.affectedProjects,
|
affectedProjects: ctx.affectedProjects,
|
||||||
workspaceLayout: ctx.workspaceLayout,
|
workspaceLayout: ctx.workspaceLayout,
|
||||||
groupByFolder: ctx.groupByFolder,
|
groupByFolder: ctx.groupByFolder,
|
||||||
|
collapseEdges: ctx.collapseEdges,
|
||||||
selectedProjects: ctx.selectedProjects,
|
selectedProjects: ctx.selectedProjects,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
@ -183,6 +219,11 @@ export const depGraphMachine = Machine<
|
|||||||
|
|
||||||
ctx.groupByFolder = event.groupByFolder;
|
ctx.groupByFolder = event.groupByFolder;
|
||||||
}),
|
}),
|
||||||
|
setCollapseEdges: assign((ctx, event) => {
|
||||||
|
if (event.type !== 'setCollapseEdges') return;
|
||||||
|
|
||||||
|
ctx.collapseEdges = event.collapseEdges;
|
||||||
|
}),
|
||||||
incrementSearchDepth: assign((ctx) => {
|
incrementSearchDepth: assign((ctx) => {
|
||||||
ctx.searchDepthEnabled = true;
|
ctx.searchDepthEnabled = true;
|
||||||
ctx.searchDepth = ctx.searchDepth + 1;
|
ctx.searchDepth = ctx.searchDepth + 1;
|
||||||
|
|||||||
@ -24,6 +24,7 @@ export class GraphService {
|
|||||||
private renderGraph: cy.Core;
|
private renderGraph: cy.Core;
|
||||||
|
|
||||||
private openTooltip: Instance = null;
|
private openTooltip: Instance = null;
|
||||||
|
private collapseEdges = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private tooltipService: GraphTooltipService,
|
private tooltipService: GraphTooltipService,
|
||||||
@ -52,7 +53,8 @@ export class GraphService {
|
|||||||
event.groupByFolder,
|
event.groupByFolder,
|
||||||
event.workspaceLayout,
|
event.workspaceLayout,
|
||||||
event.dependencies,
|
event.dependencies,
|
||||||
event.affectedProjects
|
event.affectedProjects,
|
||||||
|
event.collapseEdges
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -62,7 +64,8 @@ export class GraphService {
|
|||||||
event.groupByFolder,
|
event.groupByFolder,
|
||||||
event.workspaceLayout,
|
event.workspaceLayout,
|
||||||
event.dependencies,
|
event.dependencies,
|
||||||
event.affectedProjects
|
event.affectedProjects,
|
||||||
|
event.collapseEdges
|
||||||
);
|
);
|
||||||
this.setShownProjects(
|
this.setShownProjects(
|
||||||
event.selectedProjects.length > 0
|
event.selectedProjects.length > 0
|
||||||
@ -112,9 +115,11 @@ export class GraphService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (this.renderGraph) {
|
if (this.renderGraph) {
|
||||||
this.renderGraph
|
const elements = this.renderGraph.elements().sort((a, b) => {
|
||||||
.elements()
|
return a.id().localeCompare(b.id());
|
||||||
.sort((a, b) => a.id().localeCompare(b.id()))
|
});
|
||||||
|
|
||||||
|
elements
|
||||||
.layout({
|
.layout({
|
||||||
name: 'dagre',
|
name: 'dagre',
|
||||||
nodeDimensionsIncludeLabels: true,
|
nodeDimensionsIncludeLabels: true,
|
||||||
@ -125,6 +130,80 @@ export class GraphService {
|
|||||||
} as CytoscapeDagreConfig)
|
} as CytoscapeDagreConfig)
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
|
if (this.collapseEdges) {
|
||||||
|
this.renderGraph.remove(this.renderGraph.edges());
|
||||||
|
|
||||||
|
elements.edges().forEach((edge) => {
|
||||||
|
const sourceNode = edge.source();
|
||||||
|
const targetNode = edge.target();
|
||||||
|
|
||||||
|
if (
|
||||||
|
sourceNode.parent().first().id() ===
|
||||||
|
targetNode.parent().first().id()
|
||||||
|
) {
|
||||||
|
this.renderGraph.add(edge);
|
||||||
|
} else {
|
||||||
|
let sourceAncestors, targetAncestors;
|
||||||
|
const commonAncestors = edge.connectedNodes().commonAncestors();
|
||||||
|
|
||||||
|
if (commonAncestors.length > 0) {
|
||||||
|
sourceAncestors = sourceNode
|
||||||
|
.ancestors()
|
||||||
|
.filter((anc) => !commonAncestors.contains(anc));
|
||||||
|
targetAncestors = targetNode
|
||||||
|
.ancestors()
|
||||||
|
.filter((anc) => !commonAncestors.contains(anc));
|
||||||
|
} else {
|
||||||
|
sourceAncestors = sourceNode.ancestors();
|
||||||
|
targetAncestors = targetNode.ancestors();
|
||||||
|
}
|
||||||
|
|
||||||
|
let sourceId, targetId;
|
||||||
|
|
||||||
|
if (sourceAncestors.length > 0 && targetAncestors.length === 0) {
|
||||||
|
sourceId = sourceAncestors.last().id();
|
||||||
|
targetId = targetNode.id();
|
||||||
|
} else if (
|
||||||
|
targetAncestors.length > 0 &&
|
||||||
|
sourceAncestors.length === 0
|
||||||
|
) {
|
||||||
|
sourceId = sourceNode.id();
|
||||||
|
targetId = targetAncestors.last().id();
|
||||||
|
} else {
|
||||||
|
sourceId = sourceAncestors.last().id();
|
||||||
|
targetId = targetAncestors.last().id();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceId !== undefined && targetId !== undefined) {
|
||||||
|
const edgeId = `${sourceId}|${targetId}`;
|
||||||
|
|
||||||
|
if (this.renderGraph.$id(edgeId).length === 0) {
|
||||||
|
const ancestorEdge: cy.EdgeDefinition = {
|
||||||
|
group: 'edges',
|
||||||
|
data: {
|
||||||
|
id: edgeId,
|
||||||
|
source: sourceId,
|
||||||
|
target: targetId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.renderGraph.add(ancestorEdge);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`Couldn't figure out how to draw edge ${edge.id()}`);
|
||||||
|
console.log(
|
||||||
|
'source ancestors',
|
||||||
|
sourceAncestors.map((anc) => anc.id())
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
'target ancestors',
|
||||||
|
targetAncestors.map((anc) => anc.id())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.renderGraph.fit().center().resize();
|
this.renderGraph.fit().center().resize();
|
||||||
|
|
||||||
selectedProjectNames = this.renderGraph
|
selectedProjectNames = this.renderGraph
|
||||||
@ -284,12 +363,14 @@ export class GraphService {
|
|||||||
if (!!currentFocusedProjectName) {
|
if (!!currentFocusedProjectName) {
|
||||||
this.renderGraph.$id(currentFocusedProjectName).addClass('focused');
|
this.renderGraph.$id(currentFocusedProjectName).addClass('focused');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.renderGraph.on('zoom', () => {
|
this.renderGraph.on('zoom', () => {
|
||||||
if (this.openTooltip) {
|
if (this.openTooltip) {
|
||||||
this.openTooltip.hide();
|
this.openTooltip.hide();
|
||||||
this.openTooltip = null;
|
this.openTooltip = null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.listenForProjectNodeClicks();
|
this.listenForProjectNodeClicks();
|
||||||
this.listenForProjectNodeHovers();
|
this.listenForProjectNodeHovers();
|
||||||
}
|
}
|
||||||
@ -330,8 +411,10 @@ export class GraphService {
|
|||||||
groupByFolder: boolean,
|
groupByFolder: boolean,
|
||||||
workspaceLayout,
|
workspaceLayout,
|
||||||
dependencies: Record<string, ProjectGraphDependency[]>,
|
dependencies: Record<string, ProjectGraphDependency[]>,
|
||||||
affectedProjectIds: string[]
|
affectedProjectIds: string[],
|
||||||
|
collapseEdges: boolean
|
||||||
) {
|
) {
|
||||||
|
this.collapseEdges = collapseEdges;
|
||||||
this.tooltipService.hideAll();
|
this.tooltipService.hideAll();
|
||||||
|
|
||||||
this.generateCytoscapeLayout(
|
this.generateCytoscapeLayout(
|
||||||
|
|||||||
@ -35,6 +35,7 @@ export type DepGraphUIEvents =
|
|||||||
| { type: 'deselectAll' }
|
| { type: 'deselectAll' }
|
||||||
| { type: 'selectAffected' }
|
| { type: 'selectAffected' }
|
||||||
| { type: 'setGroupByFolder'; groupByFolder: boolean }
|
| { type: 'setGroupByFolder'; groupByFolder: boolean }
|
||||||
|
| { type: 'setCollapseEdges'; collapseEdges: boolean }
|
||||||
| { type: 'setIncludeProjectsByPath'; includeProjectsByPath: boolean }
|
| { type: 'setIncludeProjectsByPath'; includeProjectsByPath: boolean }
|
||||||
| { type: 'incrementSearchDepth' }
|
| { type: 'incrementSearchDepth' }
|
||||||
| { type: 'decrementSearchDepth' }
|
| { type: 'decrementSearchDepth' }
|
||||||
@ -73,6 +74,7 @@ export type GraphRenderEvents =
|
|||||||
appsDir: string;
|
appsDir: string;
|
||||||
};
|
};
|
||||||
groupByFolder: boolean;
|
groupByFolder: boolean;
|
||||||
|
collapseEdges: boolean;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'notifyGraphUpdateGraph';
|
type: 'notifyGraphUpdateGraph';
|
||||||
@ -84,6 +86,7 @@ export type GraphRenderEvents =
|
|||||||
appsDir: string;
|
appsDir: string;
|
||||||
};
|
};
|
||||||
groupByFolder: boolean;
|
groupByFolder: boolean;
|
||||||
|
collapseEdges: boolean;
|
||||||
selectedProjects: string[];
|
selectedProjects: string[];
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
@ -124,6 +127,10 @@ export type RouteEvents =
|
|||||||
type: 'notifyRouteGroupByFolder';
|
type: 'notifyRouteGroupByFolder';
|
||||||
groupByFolder: boolean;
|
groupByFolder: boolean;
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'notifyRouteCollapseEdges';
|
||||||
|
collapseEdges: boolean;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: 'notifyRouteSearchDepth';
|
type: 'notifyRouteSearchDepth';
|
||||||
searchDepthEnabled: boolean;
|
searchDepthEnabled: boolean;
|
||||||
@ -154,6 +161,7 @@ export interface DepGraphContext {
|
|||||||
searchDepth: number;
|
searchDepth: number;
|
||||||
searchDepthEnabled: boolean;
|
searchDepthEnabled: boolean;
|
||||||
groupByFolder: boolean;
|
groupByFolder: boolean;
|
||||||
|
collapseEdges: boolean;
|
||||||
workspaceLayout: {
|
workspaceLayout: {
|
||||||
libsDir: string;
|
libsDir: string;
|
||||||
appsDir: string;
|
appsDir: string;
|
||||||
|
|||||||
@ -21,11 +21,10 @@ function parseSearchParamsToEvents(searchParams: string): DepGraphUIEvents[] {
|
|||||||
case 'groupByFolder':
|
case 'groupByFolder':
|
||||||
events.push({ type: 'setGroupByFolder', groupByFolder: true });
|
events.push({ type: 'setGroupByFolder', groupByFolder: true });
|
||||||
break;
|
break;
|
||||||
|
case 'collapseEdges':
|
||||||
|
events.push({ type: 'setCollapseEdges', collapseEdges: true });
|
||||||
|
break;
|
||||||
case 'searchDepth':
|
case 'searchDepth':
|
||||||
// events.push({
|
|
||||||
// type: 'setSearchDepthEnabled',
|
|
||||||
// searchDepthEnabled: true,
|
|
||||||
// });
|
|
||||||
events.push({
|
events.push({
|
||||||
type: 'setSearchDepth',
|
type: 'setSearchDepth',
|
||||||
searchDepth: parseInt(value),
|
searchDepth: parseInt(value),
|
||||||
|
|||||||
@ -3,7 +3,12 @@ import { createBrowserHistory } from 'history';
|
|||||||
import { Machine } from 'xstate';
|
import { Machine } from 'xstate';
|
||||||
import { RouteEvents } from './interfaces';
|
import { RouteEvents } from './interfaces';
|
||||||
|
|
||||||
type ParamKeys = 'focus' | 'groupByFolder' | 'searchDepth' | 'select';
|
type ParamKeys =
|
||||||
|
| 'focus'
|
||||||
|
| 'groupByFolder'
|
||||||
|
| 'searchDepth'
|
||||||
|
| 'select'
|
||||||
|
| 'collapseEdges';
|
||||||
type ParamRecord = Record<ParamKeys, string | null>;
|
type ParamRecord = Record<ParamKeys, string | null>;
|
||||||
|
|
||||||
function reduceParamRecordToQueryString(params: ParamRecord): string {
|
function reduceParamRecordToQueryString(params: ParamRecord): string {
|
||||||
@ -25,6 +30,7 @@ export const createRouteMachine = () => {
|
|||||||
const paramRecord: ParamRecord = {
|
const paramRecord: ParamRecord = {
|
||||||
focus: params.get('focus'),
|
focus: params.get('focus'),
|
||||||
groupByFolder: params.get('groupByFolder'),
|
groupByFolder: params.get('groupByFolder'),
|
||||||
|
collapseEdges: params.get('collapseEdges'),
|
||||||
searchDepth: params.get('searchDepth'),
|
searchDepth: params.get('searchDepth'),
|
||||||
select: params.get('select'),
|
select: params.get('select'),
|
||||||
};
|
};
|
||||||
@ -48,6 +54,7 @@ export const createRouteMachine = () => {
|
|||||||
groupByFolder: null,
|
groupByFolder: null,
|
||||||
searchDepth: null,
|
searchDepth: null,
|
||||||
select: null,
|
select: null,
|
||||||
|
collapseEdges: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
always: {
|
always: {
|
||||||
@ -98,6 +105,11 @@ export const createRouteMachine = () => {
|
|||||||
ctx.params.groupByFolder = event.groupByFolder ? 'true' : null;
|
ctx.params.groupByFolder = event.groupByFolder ? 'true' : null;
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
notifyRouteCollapseEdges: {
|
||||||
|
actions: assign((ctx, event) => {
|
||||||
|
ctx.params.collapseEdges = event.collapseEdges ? 'true' : null;
|
||||||
|
}),
|
||||||
|
},
|
||||||
notifyRouteSearchDepth: {
|
notifyRouteSearchDepth: {
|
||||||
actions: assign((ctx, event) => {
|
actions: assign((ctx, event) => {
|
||||||
ctx.params.searchDepth = event.searchDepthEnabled
|
ctx.params.searchDepth = event.searchDepthEnabled
|
||||||
|
|||||||
@ -39,6 +39,9 @@ export const includePathSelector: DepGraphSelector<boolean> = (state) =>
|
|||||||
export const groupByFolderSelector: DepGraphSelector<boolean> = (state) =>
|
export const groupByFolderSelector: DepGraphSelector<boolean> = (state) =>
|
||||||
state.context.groupByFolder;
|
state.context.groupByFolder;
|
||||||
|
|
||||||
|
export const collapseEdgesSelector: DepGraphSelector<boolean> = (state) =>
|
||||||
|
state.context.collapseEdges;
|
||||||
|
|
||||||
export const textFilterSelector: DepGraphSelector<string> = (state) =>
|
export const textFilterSelector: DepGraphSelector<string> = (state) =>
|
||||||
state.context.textFilter;
|
state.context.textFilter;
|
||||||
|
|
||||||
|
|||||||
41
dep-graph/client/src/app/sidebar/collapse-edges-panel.tsx
Normal file
41
dep-graph/client/src/app/sidebar/collapse-edges-panel.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
export interface CollapseEdgesPanelProps {
|
||||||
|
collapseEdges: boolean;
|
||||||
|
collapseEdgesChanged: (checked: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CollapseEdgesPanel = memo(
|
||||||
|
({ collapseEdges, collapseEdgesChanged }: CollapseEdgesPanelProps) => {
|
||||||
|
return (
|
||||||
|
<div className="px-4">
|
||||||
|
<div className="flex items-start">
|
||||||
|
<div className="flex h-5 items-center">
|
||||||
|
<input
|
||||||
|
id="collapseEdges"
|
||||||
|
name="collapseEdges"
|
||||||
|
value="collapseEdges"
|
||||||
|
type="checkbox"
|
||||||
|
className="h-4 w-4 rounded border-gray-300"
|
||||||
|
onChange={(event) => collapseEdgesChanged(event.target.checked)}
|
||||||
|
checked={collapseEdges}
|
||||||
|
></input>
|
||||||
|
</div>
|
||||||
|
<div className="ml-3 text-sm">
|
||||||
|
<label
|
||||||
|
htmlFor="collapseEdges"
|
||||||
|
className="cursor-pointer font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Collapse edges
|
||||||
|
</label>
|
||||||
|
<p className="text-gray-500">
|
||||||
|
Display edges between groups rather than libraries
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default CollapseEdgesPanel;
|
||||||
@ -1,7 +1,9 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
import ExperimentalFeature from '../experimental-feature';
|
||||||
import { useDepGraphService } from '../hooks/use-dep-graph';
|
import { useDepGraphService } from '../hooks/use-dep-graph';
|
||||||
import { useDepGraphSelector } from '../hooks/use-dep-graph-selector';
|
import { useDepGraphSelector } from '../hooks/use-dep-graph-selector';
|
||||||
import {
|
import {
|
||||||
|
collapseEdgesSelector,
|
||||||
focusedProjectNameSelector,
|
focusedProjectNameSelector,
|
||||||
groupByFolderSelector,
|
groupByFolderSelector,
|
||||||
hasAffectedProjectsSelector,
|
hasAffectedProjectsSelector,
|
||||||
@ -9,6 +11,7 @@ import {
|
|||||||
searchDepthSelector,
|
searchDepthSelector,
|
||||||
textFilterSelector,
|
textFilterSelector,
|
||||||
} from '../machines/selectors';
|
} from '../machines/selectors';
|
||||||
|
import CollapseEdgesPanel from './collapse-edges-panel';
|
||||||
import FocusedProjectPanel from './focused-project-panel';
|
import FocusedProjectPanel from './focused-project-panel';
|
||||||
import GroupByFolderPanel from './group-by-folder-panel';
|
import GroupByFolderPanel from './group-by-folder-panel';
|
||||||
import ProjectList from './project-list';
|
import ProjectList from './project-list';
|
||||||
@ -24,6 +27,7 @@ export function Sidebar() {
|
|||||||
const textFilter = useDepGraphSelector(textFilterSelector);
|
const textFilter = useDepGraphSelector(textFilterSelector);
|
||||||
const hasAffectedProjects = useDepGraphSelector(hasAffectedProjectsSelector);
|
const hasAffectedProjects = useDepGraphSelector(hasAffectedProjectsSelector);
|
||||||
const groupByFolder = useDepGraphSelector(groupByFolderSelector);
|
const groupByFolder = useDepGraphSelector(groupByFolderSelector);
|
||||||
|
const collapseEdges = useDepGraphSelector(collapseEdgesSelector);
|
||||||
|
|
||||||
function resetFocus() {
|
function resetFocus() {
|
||||||
depGraphService.send({ type: 'unfocusProject' });
|
depGraphService.send({ type: 'unfocusProject' });
|
||||||
@ -52,6 +56,10 @@ export function Sidebar() {
|
|||||||
depGraphService.send({ type: 'setGroupByFolder', groupByFolder: checked });
|
depGraphService.send({ type: 'setGroupByFolder', groupByFolder: checked });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function collapseEdgesChanged(checked: boolean) {
|
||||||
|
depGraphService.send({ type: 'setCollapseEdges', collapseEdges: checked });
|
||||||
|
}
|
||||||
|
|
||||||
function incrementDepthFilter() {
|
function incrementDepthFilter() {
|
||||||
depGraphService.send({ type: 'incrementSearchDepth' });
|
depGraphService.send({ type: 'incrementSearchDepth' });
|
||||||
}
|
}
|
||||||
@ -174,6 +182,13 @@ export function Sidebar() {
|
|||||||
incrementDepthFilter={incrementDepthFilter}
|
incrementDepthFilter={incrementDepthFilter}
|
||||||
decrementDepthFilter={decrementDepthFilter}
|
decrementDepthFilter={decrementDepthFilter}
|
||||||
></SearchDepth>
|
></SearchDepth>
|
||||||
|
|
||||||
|
<ExperimentalFeature>
|
||||||
|
<CollapseEdgesPanel
|
||||||
|
collapseEdges={collapseEdges}
|
||||||
|
collapseEdgesChanged={collapseEdgesChanged}
|
||||||
|
></CollapseEdgesPanel>
|
||||||
|
</ExperimentalFeature>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ProjectList></ProjectList>
|
<ProjectList></ProjectList>
|
||||||
|
|||||||
@ -5,6 +5,7 @@ window.useXstateInspect = false;
|
|||||||
|
|
||||||
window.appConfig = {
|
window.appConfig = {
|
||||||
showDebugger: true,
|
showDebugger: true,
|
||||||
|
showExperimentalFeatures: true,
|
||||||
projectGraphs: [
|
projectGraphs: [
|
||||||
{
|
{
|
||||||
id: 'nx',
|
id: 'nx',
|
||||||
@ -41,6 +42,11 @@ window.appConfig = {
|
|||||||
label: 'Affected',
|
label: 'Affected',
|
||||||
url: 'assets/graphs/affected.json',
|
url: 'assets/graphs/affected.json',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'collapsing-edges-testing',
|
||||||
|
label: 'Collapsing Edges',
|
||||||
|
url: 'assets/graphs/collapsing-edges-testing.json',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
defaultProjectGraph: 'nx',
|
defaultProjectGraph: 'nx',
|
||||||
};
|
};
|
||||||
|
|||||||
17
dep-graph/client/src/assets/environment.release.js
Normal file
17
dep-graph/client/src/assets/environment.release.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
window.exclude = [];
|
||||||
|
window.watch = false;
|
||||||
|
window.environment = 'release';
|
||||||
|
window.useXstateInspect = false;
|
||||||
|
|
||||||
|
window.appConfig = {
|
||||||
|
showDebugger: false,
|
||||||
|
showExperimentalFeatures: false,
|
||||||
|
projectGraphs: [
|
||||||
|
{
|
||||||
|
id: 'local',
|
||||||
|
label: 'local',
|
||||||
|
url: 'assets/graphs/nx-examples.json',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultProjectGraph: 'local',
|
||||||
|
};
|
||||||
@ -5,6 +5,7 @@ window.useXstateInspect = false;
|
|||||||
|
|
||||||
window.appConfig = {
|
window.appConfig = {
|
||||||
showDebugger: false,
|
showDebugger: false,
|
||||||
|
showExperimentalFeatures: true,
|
||||||
projectGraphs: [
|
projectGraphs: [
|
||||||
{
|
{
|
||||||
id: 'local',
|
id: 'local',
|
||||||
|
|||||||
222
dep-graph/client/src/assets/graphs/collapsing-edges-testing.json
Normal file
222
dep-graph/client/src/assets/graphs/collapsing-edges-testing.json
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
{
|
||||||
|
"hash": "1c2b69586aa096dc5e42eb252d0b5bfb94f20dc969a1e7b6f381a3b13add6928",
|
||||||
|
"layout": {
|
||||||
|
"appsDir": "apps",
|
||||||
|
"libsDir": "libs"
|
||||||
|
},
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"name": "web",
|
||||||
|
"type": "app",
|
||||||
|
"data": {
|
||||||
|
"tags": [],
|
||||||
|
"root": "apps/app1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "admin",
|
||||||
|
"type": "app",
|
||||||
|
"data": {
|
||||||
|
"tags": [],
|
||||||
|
"root": "apps/app2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "core-util-auth",
|
||||||
|
"type": "lib",
|
||||||
|
"data": {
|
||||||
|
"tags": [],
|
||||||
|
"root": "core/util-auth"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "web-feature-home-page",
|
||||||
|
"type": "lib",
|
||||||
|
"data": {
|
||||||
|
"tags": [],
|
||||||
|
"root": "web/feature-homepage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "web-feature-search",
|
||||||
|
"type": "lib",
|
||||||
|
"data": {
|
||||||
|
"tags": [],
|
||||||
|
"root": "web/feature-search"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "web-data-access",
|
||||||
|
"type": "lib",
|
||||||
|
"data": {
|
||||||
|
"tags": [],
|
||||||
|
"root": "web/feature-search"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "admin-feature-users",
|
||||||
|
"type": "lib",
|
||||||
|
"data": {
|
||||||
|
"tags": [],
|
||||||
|
"root": "admin/feature-users"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "admin-feature-billing",
|
||||||
|
"type": "lib",
|
||||||
|
"data": {
|
||||||
|
"tags": [],
|
||||||
|
"root": "admin/feature-billing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "admin-data-access",
|
||||||
|
"type": "lib",
|
||||||
|
"data": {
|
||||||
|
"tags": [],
|
||||||
|
"root": "admin/data-access"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "shared-components-ui-button",
|
||||||
|
"type": "lib",
|
||||||
|
"data": {
|
||||||
|
"tags": [],
|
||||||
|
"root": "shared/components/ui-button"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "shared-components-ui-form",
|
||||||
|
"type": "lib",
|
||||||
|
"data": {
|
||||||
|
"tags": [],
|
||||||
|
"root": "shared/components/ui-form"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "shared-util",
|
||||||
|
"type": "lib",
|
||||||
|
"data": {
|
||||||
|
"tags": [],
|
||||||
|
"root": "shared/util"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"web": [
|
||||||
|
{
|
||||||
|
"type": "dynamic",
|
||||||
|
"source": "web",
|
||||||
|
"target": "web-feature-home-page"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dynamic",
|
||||||
|
"source": "web",
|
||||||
|
"target": "web-feature-search"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"source": "web",
|
||||||
|
"target": "core-util-auth"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"admin": [
|
||||||
|
{
|
||||||
|
"type": "dynamic",
|
||||||
|
"source": "admin",
|
||||||
|
"target": "admin-feature-users"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dynamic",
|
||||||
|
"source": "admin",
|
||||||
|
"target": "admin-feature-billing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"source": "admin",
|
||||||
|
"target": "core-util-auth"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"web-feature-home-page": [
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"source": "web-feature-home-page",
|
||||||
|
"target": "web-data-access"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"source": "web-feature-home-page",
|
||||||
|
"target": "shared-components-ui-button"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"web-feature-search": [
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"source": "web-feature-search",
|
||||||
|
"target": "web-data-access"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"source": "web-feature-search",
|
||||||
|
"target": "shared-components-ui-button"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"source": "web-feature-search",
|
||||||
|
"target": "shared-components-ui-form"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"web-data-access": [
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"source": "web-data-access",
|
||||||
|
"target": "core-util-auth"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"admin-feature-users": [
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"source": "admin-feature-users",
|
||||||
|
"target": "admin-data-access"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"source": "admin-feature-users",
|
||||||
|
"target": "shared-components-ui-button"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"admin-feature-billing": [
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"source": "admin-feature-billing",
|
||||||
|
"target": "admin-data-access"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"source": "admin-feature-billing",
|
||||||
|
"target": "shared-components-ui-button"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"admin-data-access": [
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"source": "admin-data-access",
|
||||||
|
"target": "core-util-auth"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"core-util-auth": [],
|
||||||
|
"shared-components-ui-button": [],
|
||||||
|
"shared-components-ui-form": [
|
||||||
|
{
|
||||||
|
"type": "static",
|
||||||
|
"source": "shared-components-ui-form",
|
||||||
|
"target": "shared-util"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"shared-util": []
|
||||||
|
},
|
||||||
|
"affected": [],
|
||||||
|
"changes": {
|
||||||
|
"added": []
|
||||||
|
}
|
||||||
|
}
|
||||||
2
dep-graph/client/src/globals.d.ts
vendored
2
dep-graph/client/src/globals.d.ts
vendored
@ -1,6 +1,6 @@
|
|||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
import type { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
|
import type { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
|
||||||
import { AppConfig } from './app/models';
|
import { AppConfig } from './app/interfaces';
|
||||||
|
|
||||||
export declare global {
|
export declare global {
|
||||||
export interface Window {
|
export interface Window {
|
||||||
|
|||||||
@ -132,7 +132,7 @@
|
|||||||
"css-minimizer-webpack-plugin": "^3.1.1",
|
"css-minimizer-webpack-plugin": "^3.1.1",
|
||||||
"cypress": "^9.1.0",
|
"cypress": "^9.1.0",
|
||||||
"cytoscape": "^3.18.2",
|
"cytoscape": "^3.18.2",
|
||||||
"cytoscape-dagre": "^2.3.2",
|
"cytoscape-dagre": "^2.4.0",
|
||||||
"cytoscape-popper": "^2.0.0",
|
"cytoscape-popper": "^2.0.0",
|
||||||
"cz-conventional-changelog": "^3.0.2",
|
"cz-conventional-changelog": "^3.0.2",
|
||||||
"cz-customizable": "^6.2.0",
|
"cz-customizable": "^6.2.0",
|
||||||
|
|||||||
@ -64,6 +64,7 @@ function buildEnvironmentJs(
|
|||||||
|
|
||||||
window.appConfig = {
|
window.appConfig = {
|
||||||
showDebugger: false,
|
showDebugger: false,
|
||||||
|
showExperimentalFeatures: false,
|
||||||
projectGraphs: [
|
projectGraphs: [
|
||||||
{
|
{
|
||||||
id: 'local',
|
id: 'local',
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { copyFileSync } from 'fs';
|
import { copyFileSync } from 'fs';
|
||||||
import { argv } from 'yargs';
|
import { argv } from 'yargs';
|
||||||
|
|
||||||
type Mode = 'dev' | 'watch';
|
type Mode = 'dev' | 'watch' | 'release';
|
||||||
const mode = argv._[0];
|
const mode = argv._[0];
|
||||||
|
|
||||||
console.log(`Setting up graph for ${mode}`);
|
console.log(`Setting up graph for ${mode}`);
|
||||||
|
|||||||
@ -10342,10 +10342,10 @@ cypress@^9.1.0:
|
|||||||
url "^0.11.0"
|
url "^0.11.0"
|
||||||
yauzl "^2.10.0"
|
yauzl "^2.10.0"
|
||||||
|
|
||||||
cytoscape-dagre@^2.3.2:
|
cytoscape-dagre@^2.4.0:
|
||||||
version "2.3.2"
|
version "2.4.0"
|
||||||
resolved "https://registry.npmjs.org/cytoscape-dagre/-/cytoscape-dagre-2.3.2.tgz"
|
resolved "https://registry.yarnpkg.com/cytoscape-dagre/-/cytoscape-dagre-2.4.0.tgz#abf145b1c675afe3b7d531166e6727dc39dc350d"
|
||||||
integrity sha512-dL9+RvGkatSlIdOKXiFwHpnpTo8ydFMqIYzZFkImJXNbDci3feyYxR46wFoaG9GFiWimc6XD9Lm0x29b1wvWpw==
|
integrity sha512-jfOtKzKduCnruBs3YMHS9kqWjZKqvp6loSJwlotPO5pcU4wLUhkx7ZBIyW3VWZXa8wfkGxv/zhWoBxWtYrUxKQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
dagre "^0.8.5"
|
dagre "^0.8.5"
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user