diff --git a/.gitignore b/.gitignore
index 9cee22106f..5cbf0b14d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,4 @@ tmp
jest.debug.config.js
.tool-versions
/.verdaccio/build/local-registry
-dep-graph/dep-graph/src/assets/environment.js
\ No newline at end of file
+dep-graph/client/src/assets/environment.js
\ No newline at end of file
diff --git a/dep-graph/dep-graph-e2e/.eslintrc b/dep-graph/client-e2e/.eslintrc
similarity index 100%
rename from dep-graph/dep-graph-e2e/.eslintrc
rename to dep-graph/client-e2e/.eslintrc
diff --git a/dep-graph/dep-graph-e2e/cypress-watch-mode.json b/dep-graph/client-e2e/cypress-watch-mode.json
similarity index 65%
rename from dep-graph/dep-graph-e2e/cypress-watch-mode.json
rename to dep-graph/client-e2e/cypress-watch-mode.json
index 6c3f664e0d..787a6239e1 100644
--- a/dep-graph/dep-graph-e2e/cypress-watch-mode.json
+++ b/dep-graph/client-e2e/cypress-watch-mode.json
@@ -6,7 +6,7 @@
"pluginsFile": "./src/plugins/index",
"supportFile": "./src/support/index.ts",
"video": true,
- "videosFolder": "../../dist/cypress/dep-graph/dep-graph-e2e/videos",
- "screenshotsFolder": "../../dist/cypress/dep-graph/dep-graph-e2e/screenshots",
+ "videosFolder": "../../dist/cypress/dep-graph/client-e2e/videos",
+ "screenshotsFolder": "../../dist/cypress/dep-graph/client-e2e/screenshots",
"chromeWebSecurity": false
}
diff --git a/dep-graph/dep-graph-e2e/cypress.json b/dep-graph/client-e2e/cypress.json
similarity index 64%
rename from dep-graph/dep-graph-e2e/cypress.json
rename to dep-graph/client-e2e/cypress.json
index 9d3462ef17..198b5740e5 100644
--- a/dep-graph/dep-graph-e2e/cypress.json
+++ b/dep-graph/client-e2e/cypress.json
@@ -6,7 +6,7 @@
"pluginsFile": "./src/plugins/index",
"supportFile": "./src/support/index.ts",
"video": true,
- "videosFolder": "../../dist/cypress/dep-graph/dep-graph-e2e/videos",
- "screenshotsFolder": "../../dist/cypress/dep-graph/dep-graph-e2e/screenshots",
+ "videosFolder": "../../dist/cypress/dep-graph/client-e2e/videos",
+ "screenshotsFolder": "../../dist/cypress/dep-graph/client-e2e/screenshots",
"chromeWebSecurity": false
}
diff --git a/dep-graph/client-e2e/project.json b/dep-graph/client-e2e/project.json
new file mode 100644
index 0000000000..a5952dc686
--- /dev/null
+++ b/dep-graph/client-e2e/project.json
@@ -0,0 +1,33 @@
+{
+ "root": "dep-graph/client-e2e",
+ "sourceRoot": "dep-graph/client-e2e/src",
+ "projectType": "application",
+ "targets": {
+ "e2e-disabled": {
+ "executor": "@nrwl/cypress:cypress",
+ "options": {
+ "cypressConfig": "dep-graph/client-e2e/cypress.json",
+ "tsConfig": "dep-graph/client-e2e/tsconfig.e2e.json",
+ "devServerTarget": "dep-graph-client:serve-for-e2e",
+ "baseUrl": "http://localhost:4200"
+ }
+ },
+ "e2e-watch-disabled": {
+ "executor": "@nrwl/cypress:cypress",
+ "options": {
+ "cypressConfig": "dep-graph/client-e2e/cypress-watch-mode.json",
+ "tsConfig": "dep-graph/client-e2e/tsconfig.e2e.json",
+ "devServerTarget": "dep-graph-client:serve-for-e2e:watch",
+ "baseUrl": "http://localhost:4200"
+ }
+ },
+ "lint": {
+ "executor": "@nrwl/linter:eslint",
+ "outputs": ["{options.outputFile}"],
+ "options": {
+ "lintFilePatterns": ["dep-graph/client-e2e/**/*.ts"]
+ }
+ }
+ },
+ "implicitDependencies": ["dep-graph-client"]
+}
diff --git a/dep-graph/dep-graph-e2e/src/fixtures/example.json b/dep-graph/client-e2e/src/fixtures/example.json
similarity index 100%
rename from dep-graph/dep-graph-e2e/src/fixtures/example.json
rename to dep-graph/client-e2e/src/fixtures/example.json
diff --git a/dep-graph/dep-graph-e2e/src/integration/app.spec.ts b/dep-graph/client-e2e/src/integration/app.spec.ts
similarity index 58%
rename from dep-graph/dep-graph-e2e/src/integration/app.spec.ts
rename to dep-graph/client-e2e/src/integration/app.spec.ts
index 75a8a7d685..03ea025c03 100644
--- a/dep-graph/dep-graph-e2e/src/integration/app.spec.ts
+++ b/dep-graph/client-e2e/src/integration/app.spec.ts
@@ -1,11 +1,14 @@
import {
getCheckedProjectItems,
getDeselectAllButton,
+ getImageDownloadButton,
getIncludeProjectsInPathButton,
getProjectItems,
+ getSelectAffectedButton,
getSelectAllButton,
getSelectProjectsMessage,
getTextFilterInput,
+ getTextFilterReset,
getUncheckedProjectItems,
getUnfocusProjectButton,
} from '../support/app.po';
@@ -20,15 +23,45 @@ describe('dep-graph-client', () => {
cy.wait('@getGraph');
});
- it('should display message to select projects', () => {
- getSelectProjectsMessage().should('be.visible');
+ describe('select projects message', () => {
+ it('should display on load', () => {
+ getSelectProjectsMessage().should('be.visible');
+ });
+
+ it('should hide when a project is selected', () => {
+ cy.contains('nx-dev').scrollIntoView().should('be.visible');
+ cy.get('[data-project="nx-dev"]').should('be.visible');
+ cy.get('[data-project="nx-dev"]').click({ force: true });
+ getSelectProjectsMessage().should('not.exist');
+ });
});
- it('should hide select projects message when a project is selected', () => {
- cy.contains('nx-dev').scrollIntoView().should('be.visible');
- cy.get('[data-project="nx-dev"]').should('be.visible');
- cy.get('[data-project="nx-dev"]').click({ force: true });
- getSelectProjectsMessage().should('not.be.visible');
+ describe('text filter', () => {
+ it('should hide clear button initially', () => {
+ getTextFilterReset().should('not.exist');
+ });
+
+ it('should show clear button after typing', () => {
+ getTextFilterInput().type('nx-dev');
+ getTextFilterReset().should('exist');
+ });
+
+ it('should hide clear button after clicking', () => {
+ getTextFilterInput().type('nx-dev');
+ getTextFilterReset().click().should('not.exist');
+ });
+
+ it('should filter projects', () => {
+ getTextFilterInput().type('nx-dev');
+ getCheckedProjectItems().should('have.length', 9);
+ });
+
+ it('should clear selection on reset', () => {
+ getTextFilterInput().type('nx-dev');
+ getCheckedProjectItems().should('have.length', 9);
+ getTextFilterReset().click();
+ getCheckedProjectItems().should('have.length', 0);
+ });
});
describe('selecting a different project', () => {
@@ -54,6 +87,20 @@ describe('dep-graph-client', () => {
});
});
+ describe('show affected button', () => {
+ it('should be hidden initially', () => {
+ getSelectAffectedButton().should('not.exist');
+ });
+
+ it('should check all affected project items', () => {
+ cy.get('[data-cy=project-select]').select('Affected', { force: true });
+ cy.wait('@getGraph');
+ getSelectAffectedButton().click();
+
+ getCheckedProjectItems().should('have.length', 5);
+ });
+ });
+
describe('selecting projects', () => {
it('should select a project by clicking on the project name', () => {
cy.get('[data-project="nx-dev"]').should('have.data', 'active', false);
@@ -61,7 +108,7 @@ describe('dep-graph-client', () => {
force: true,
});
- cy.get('[data-project="nx-dev"]').should('have.data', 'active', true);
+ cy.get('[data-project="nx-dev"][data-active="true"]').should('exist');
});
it('should deselect a project by clicking on the project name again', () => {
@@ -125,4 +172,21 @@ describe('dep-graph-client', () => {
getCheckedProjectItems().should('have.length', 17);
});
});
+
+ describe('image download button', () => {
+ it('should be hidden initally', () => {
+ getImageDownloadButton().should('have.class', 'opacity-0');
+ });
+
+ it('should be shown when a project is selected', () => {
+ cy.get('[data-project="nx-dev"]').prev('button').click({ force: true });
+ getImageDownloadButton().should('not.have.class', 'opacity-0');
+ });
+
+ it('should be hidden when no more projects are selected', () => {
+ cy.get('[data-project="nx-dev"]').prev('button').click({ force: true });
+ getDeselectAllButton().click();
+ getImageDownloadButton().should('have.class', 'opacity-0');
+ });
+ });
});
diff --git a/dep-graph/dep-graph-e2e/src/plugins/index.js b/dep-graph/client-e2e/src/plugins/index.js
similarity index 100%
rename from dep-graph/dep-graph-e2e/src/plugins/index.js
rename to dep-graph/client-e2e/src/plugins/index.js
diff --git a/dep-graph/dep-graph-e2e/src/support/app.po.ts b/dep-graph/client-e2e/src/support/app.po.ts
similarity index 77%
rename from dep-graph/dep-graph-e2e/src/support/app.po.ts
rename to dep-graph/client-e2e/src/support/app.po.ts
index 3c523f451a..ef184837e7 100644
--- a/dep-graph/dep-graph-e2e/src/support/app.po.ts
+++ b/dep-graph/client-e2e/src/support/app.po.ts
@@ -2,6 +2,8 @@ export const getSelectProjectsMessage = () => cy.get('#no-projects-chosen');
export const getGraph = () => cy.get('#graph-container');
export const getSelectAllButton = () => cy.get('[data-cy=selectAllButton]');
export const getDeselectAllButton = () => cy.get('[data-cy=deselectAllButton]');
+export const getSelectAffectedButton = () => cy.get('[data-cy=affectedButton]');
+
export const getUnfocusProjectButton = () => cy.get('[data-cy=unfocusButton]');
export const getProjectItems = () => cy.get('[data-project]');
@@ -13,5 +15,10 @@ export const getGroupByfolderItems = () =>
cy.get('input[name=displayOptions][value=groupByFolder]');
export const getTextFilterInput = () => cy.get('[data-cy=textFilterInput]');
+export const getTextFilterReset = () => cy.get('[data-cy=textFilterReset]');
+
export const getIncludeProjectsInPathButton = () =>
cy.get('input[name=textFilterCheckbox]');
+
+export const getImageDownloadButton = () =>
+ cy.get('[data-cy=downloadImageButton]');
diff --git a/dep-graph/dep-graph-e2e/src/support/commands.ts b/dep-graph/client-e2e/src/support/commands.ts
similarity index 100%
rename from dep-graph/dep-graph-e2e/src/support/commands.ts
rename to dep-graph/client-e2e/src/support/commands.ts
diff --git a/dep-graph/dep-graph-e2e/src/support/index.ts b/dep-graph/client-e2e/src/support/index.ts
similarity index 100%
rename from dep-graph/dep-graph-e2e/src/support/index.ts
rename to dep-graph/client-e2e/src/support/index.ts
diff --git a/dep-graph/dep-graph-e2e/src/watch-mode-integration/watch-mode.spec.ts b/dep-graph/client-e2e/src/watch-mode-integration/watch-mode.spec.ts
similarity index 98%
rename from dep-graph/dep-graph-e2e/src/watch-mode-integration/watch-mode.spec.ts
rename to dep-graph/client-e2e/src/watch-mode-integration/watch-mode.spec.ts
index f9d56444ec..d7a27c3559 100644
--- a/dep-graph/dep-graph-e2e/src/watch-mode-integration/watch-mode.spec.ts
+++ b/dep-graph/client-e2e/src/watch-mode-integration/watch-mode.spec.ts
@@ -4,7 +4,7 @@ describe('dep-graph-client in watch mode', () => {
beforeEach(() => {
cy.clock();
cy.visit('/');
- cy.tick(1000);
+ cy.tick(2000);
});
it('should auto-select new libs as they are created', () => {
diff --git a/dep-graph/dep-graph-e2e/tsconfig.e2e.json b/dep-graph/client-e2e/tsconfig.e2e.json
similarity index 100%
rename from dep-graph/dep-graph-e2e/tsconfig.e2e.json
rename to dep-graph/client-e2e/tsconfig.e2e.json
diff --git a/dep-graph/dep-graph-e2e/tsconfig.json b/dep-graph/client-e2e/tsconfig.json
similarity index 100%
rename from dep-graph/dep-graph-e2e/tsconfig.json
rename to dep-graph/client-e2e/tsconfig.json
diff --git a/dep-graph/client/.babelrc b/dep-graph/client/.babelrc
new file mode 100644
index 0000000000..61641ec8ac
--- /dev/null
+++ b/dep-graph/client/.babelrc
@@ -0,0 +1,11 @@
+{
+ "presets": [
+ [
+ "@nrwl/react/babel",
+ {
+ "runtime": "automatic"
+ }
+ ]
+ ],
+ "plugins": []
+}
diff --git a/dep-graph/dep-graph/browserslist b/dep-graph/client/.browserslistrc
similarity index 73%
rename from dep-graph/dep-graph/browserslist
rename to dep-graph/client/.browserslistrc
index 8d6179367e..f1d12df4fa 100644
--- a/dep-graph/dep-graph/browserslist
+++ b/dep-graph/client/.browserslistrc
@@ -1,4 +1,7 @@
-# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
+# This file is used by:
+# 1. autoprefixer to adjust CSS to support the below specified browsers
+# 2. babel preset-env to adjust included polyfills
+#
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
#
diff --git a/dep-graph/client/.eslintrc.json b/dep-graph/client/.eslintrc.json
new file mode 100644
index 0000000000..927a2d63f0
--- /dev/null
+++ b/dep-graph/client/.eslintrc.json
@@ -0,0 +1,21 @@
+{
+ "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
+ "ignorePatterns": ["!**/*"],
+ "rules": {
+ "@typescript-eslint/no-implicit-any": "off"
+ },
+ "overrides": [
+ {
+ "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
+ "rules": {}
+ },
+ {
+ "files": ["*.ts", "*.tsx"],
+ "rules": {}
+ },
+ {
+ "files": ["*.js", "*.jsx"],
+ "rules": {}
+ }
+ ]
+}
diff --git a/dep-graph/client/jest.config.js b/dep-graph/client/jest.config.js
new file mode 100644
index 0000000000..313faa823d
--- /dev/null
+++ b/dep-graph/client/jest.config.js
@@ -0,0 +1,13 @@
+// nx-ignore-next-line
+const nxPreset = require('@nrwl/jest/preset');
+
+module.exports = {
+ ...nxPreset,
+ displayName: 'dep-graph-client',
+ transform: {
+ '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest',
+ '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/next/babel'] }],
+ },
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
+ coverageDirectory: '../../coverage/nx-dev/nx-dev',
+};
diff --git a/dep-graph/dep-graph/postcss.config.js b/dep-graph/client/postcss.config.js
similarity index 60%
rename from dep-graph/dep-graph/postcss.config.js
rename to dep-graph/client/postcss.config.js
index 421dcdbd76..30c0cf3777 100644
--- a/dep-graph/dep-graph/postcss.config.js
+++ b/dep-graph/client/postcss.config.js
@@ -1,7 +1,7 @@
module.exports = {
plugins: {
tailwindcss: {
- config: './dep-graph/dep-graph/tailwind.config.js',
+ config: './dep-graph/client/tailwind.config.js',
},
autoprefixer: {},
},
diff --git a/dep-graph/dep-graph/project.json b/dep-graph/client/project.json
similarity index 73%
rename from dep-graph/dep-graph/project.json
rename to dep-graph/client/project.json
index 012374c0ec..10fd5cea66 100644
--- a/dep-graph/dep-graph/project.json
+++ b/dep-graph/client/project.json
@@ -1,6 +1,6 @@
{
- "root": "dep-graph/dep-graph",
- "sourceRoot": "dep-graph/dep-graph/src",
+ "root": "dep-graph/client",
+ "sourceRoot": "dep-graph/client/src",
"projectType": "application",
"targets": {
"build-base": {
@@ -8,11 +8,11 @@
"options": {
"maxWorkers": 8,
"outputPath": "build/apps/dep-graph",
- "index": "dep-graph/dep-graph/src/index.html",
- "main": "dep-graph/dep-graph/src/main.ts",
- "polyfills": "dep-graph/dep-graph/src/polyfills.ts",
- "tsConfig": "dep-graph/dep-graph/tsconfig.app.json",
- "styles": ["dep-graph/dep-graph/src/styles.scss"],
+ "index": "dep-graph/client/src/index.html",
+ "main": "dep-graph/client/src/main.tsx",
+ "polyfills": "dep-graph/client/src/polyfills.ts",
+ "tsConfig": "dep-graph/client/tsconfig.app.json",
+ "styles": ["dep-graph/client/src/styles.scss"],
"scripts": [],
"assets": [],
"optimization": true,
@@ -34,10 +34,10 @@
"dev": {
"fileReplacements": [],
"assets": [
- "dep-graph/dep-graph/src/favicon.ico",
- "dep-graph/dep-graph/src/assets/graphs/",
+ "dep-graph/client/src/favicon.ico",
+ "dep-graph/client/src/assets/graphs/",
{
- "input": "dep-graph/dep-graph/src/assets",
+ "input": "dep-graph/client/src/assets",
"output": "/",
"glob": "environment.js"
}
@@ -63,21 +63,21 @@
"serve-base": {
"executor": "@nrwl/web:dev-server",
"options": {
- "buildTarget": "dep-graph-dep-graph:build-base:dev"
+ "buildTarget": "dep-graph-client:build-base:dev"
}
},
"lint": {
"executor": "@nrwl/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
- "lintFilePatterns": ["dep-graph/dep-graph/**/*.ts"]
+ "lintFilePatterns": ["dep-graph/client/**/*.{ts,tsx,js,jsx}"]
}
},
"test": {
"executor": "@nrwl/jest:jest",
- "outputs": ["coverage/dep-graph/dep-graph"],
+ "outputs": ["coverage/dep-graph/client"],
"options": {
- "jestConfig": "dep-graph/dep-graph/jest.config.js",
+ "jestConfig": "dep-graph/client/jest.config.js",
"passWithNoTests": true
}
},
@@ -87,14 +87,14 @@
"options": {
"commands": [
"npx ts-node -P ./scripts/tsconfig.scripts.json ./scripts/copy-dep-graph-environment.ts dev",
- "nx serve-base dep-graph-dep-graph"
+ "nx serve-base dep-graph-client"
]
},
"configurations": {
"watch": {
"commands": [
"npx ts-node -P ./scripts/tsconfig.scripts.json ./scripts/copy-dep-graph-environment.ts watch",
- "nx serve-base dep-graph-dep-graph"
+ "nx serve-base dep-graph-client"
]
}
}
@@ -105,7 +105,7 @@
"options": {
"commands": [
"npx ts-node -P ./scripts/tsconfig.scripts.json ./scripts/copy-dep-graph-environment.ts dev",
- "nx serve-base dep-graph-dep-graph"
+ "nx serve-base dep-graph-client"
],
"readyWhen": "No issues found."
},
@@ -113,12 +113,12 @@
"watch": {
"commands": [
"npx ts-node -P ./scripts/tsconfig.scripts.json ./scripts/copy-dep-graph-environment.ts watch",
- "nx serve-base dep-graph-dep-graph"
+ "nx serve-base dep-graph-client"
],
"readyWhen": "No issues found."
}
}
}
},
- "tags": ["core"]
+ "tags": []
}
diff --git a/dep-graph/client/src/app/app.tsx b/dep-graph/client/src/app/app.tsx
new file mode 100644
index 0000000000..6ed3388031
--- /dev/null
+++ b/dep-graph/client/src/app/app.tsx
@@ -0,0 +1,12 @@
+import { Shell } from './shell';
+import { GlobalStateProvider } from './state.provider';
+
+export function App() {
+ return (
+
+
+
+ );
+}
+
+export default App;
diff --git a/dep-graph/client/src/app/debugger-panel.tsx b/dep-graph/client/src/app/debugger-panel.tsx
new file mode 100644
index 0000000000..056509815d
--- /dev/null
+++ b/dep-graph/client/src/app/debugger-panel.tsx
@@ -0,0 +1,59 @@
+import { ProjectGraphList } from './interfaces';
+import { GraphPerfReport } from './machines/interfaces';
+import { memo } from 'react';
+
+export interface DebuggerPanelProps {
+ projectGraphs: ProjectGraphList[];
+ selectedProjectGraph: string;
+ projectGraphChange: (projectName: string) => void;
+ lastPerfReport: GraphPerfReport;
+}
+
+export const DebuggerPanel = memo(function ({
+ projectGraphs,
+ selectedProjectGraph,
+ projectGraphChange,
+ lastPerfReport,
+}: DebuggerPanelProps) {
+ return (
+
+
Debugger
+
+
+ Last render took {lastPerfReport.renderTime}ms:{' '}
+ {lastPerfReport.numNodes} nodes{' '}
+ |{' '}
+ {lastPerfReport.numEdges} edges
+ .
+
+
+ );
+});
+
+export default DebuggerPanel;
diff --git a/dep-graph/dep-graph/src/app/fetch-project-graph-service.ts b/dep-graph/client/src/app/fetch-project-graph-service.ts
similarity index 77%
rename from dep-graph/dep-graph/src/app/fetch-project-graph-service.ts
rename to dep-graph/client/src/app/fetch-project-graph-service.ts
index dca3f79a40..75afe769b3 100644
--- a/dep-graph/dep-graph/src/app/fetch-project-graph-service.ts
+++ b/dep-graph/client/src/app/fetch-project-graph-service.ts
@@ -1,6 +1,6 @@
// nx-ignore-next-line
-import { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
-import { ProjectGraphService } from './models';
+import type { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
+import { ProjectGraphService } from './interfaces';
export class FetchProjectGraphService implements ProjectGraphService {
async getHash(): Promise {
diff --git a/dep-graph/client/src/app/hooks/use-debounce.ts b/dep-graph/client/src/app/hooks/use-debounce.ts
new file mode 100644
index 0000000000..2f62b1d205
--- /dev/null
+++ b/dep-graph/client/src/app/hooks/use-debounce.ts
@@ -0,0 +1,22 @@
+import { useEffect, useState } from 'react';
+
+export function useDebounce(value: string, delay: number) {
+ // State and setters for debounced value
+ const [debouncedValue, setDebouncedValue] = useState(value);
+ useEffect(
+ () => {
+ // Update debounced value after delay
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+ // Cancel the timeout if value changes (also on delay change or unmount)
+ // This is how we prevent debounced value from updating if value is changed ...
+ // .. within the delay period. Timeout gets cleared and restarted.
+ return () => {
+ clearTimeout(handler);
+ };
+ },
+ [value, delay] // Only re-call effect if value or delay changes
+ );
+ return debouncedValue;
+}
diff --git a/dep-graph/client/src/app/hooks/use-dep-graph-selector.ts b/dep-graph/client/src/app/hooks/use-dep-graph-selector.ts
new file mode 100644
index 0000000000..f28a114b94
--- /dev/null
+++ b/dep-graph/client/src/app/hooks/use-dep-graph-selector.ts
@@ -0,0 +1,11 @@
+import { useSelector } from '@xstate/react';
+import { DepGraphState } from '../machines/interfaces';
+import { useDepGraphService } from './use-dep-graph';
+
+export type DepGraphSelector = (depGraphState: DepGraphState) => T;
+
+export function useDepGraphSelector(selectorFunc: DepGraphSelector): T {
+ const depGraphService = useDepGraphService();
+
+ return useSelector(depGraphService, selectorFunc);
+}
diff --git a/dep-graph/client/src/app/hooks/use-dep-graph.ts b/dep-graph/client/src/app/hooks/use-dep-graph.ts
new file mode 100644
index 0000000000..01bfb77bae
--- /dev/null
+++ b/dep-graph/client/src/app/hooks/use-dep-graph.ts
@@ -0,0 +1,8 @@
+import { useContext } from 'react';
+import { GlobalStateContext } from '../state.provider';
+
+export function useDepGraphService() {
+ const globalState = useContext(GlobalStateContext);
+
+ return globalState;
+}
diff --git a/dep-graph/client/src/app/hooks/use-environment-config.ts b/dep-graph/client/src/app/hooks/use-environment-config.ts
new file mode 100644
index 0000000000..d398a2740e
--- /dev/null
+++ b/dep-graph/client/src/app/hooks/use-environment-config.ts
@@ -0,0 +1,30 @@
+// nx-ignore-next-line
+import type { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
+import { useRef } from 'react';
+import { AppConfig } from '../interfaces';
+
+export function useEnvironmentConfig(): {
+ exclude: string[];
+ focusedProject: string;
+ groupByFolder: boolean;
+ watch: boolean;
+ localMode: 'serve' | 'build';
+ projectGraphResponse?: DepGraphClientResponse;
+ environment: 'dev' | 'watch' | 'release';
+ appConfig: AppConfig;
+ useXstateInspect: boolean;
+} {
+ const environmentConfig = useRef({
+ exclude: window.exclude,
+ focusedProject: window.focusedProject,
+ groupByFolder: window.groupByFolder,
+ watch: window.watch,
+ localMode: window.localMode,
+ projectGraphResponse: window.projectGraphResponse,
+ environment: window.environment,
+ appConfig: window.appConfig,
+ useXstateInspect: window.useXstateInspect,
+ });
+
+ return environmentConfig.current;
+}
diff --git a/dep-graph/client/src/app/hooks/use-interval-when.ts b/dep-graph/client/src/app/hooks/use-interval-when.ts
new file mode 100644
index 0000000000..2195b84023
--- /dev/null
+++ b/dep-graph/client/src/app/hooks/use-interval-when.ts
@@ -0,0 +1,28 @@
+import { useRef, useEffect } from 'react';
+
+export const useIntervalWhen = (
+ callback: () => void,
+ delay: number,
+ condition: boolean
+) => {
+ const savedCallback = useRef(() => {});
+
+ useEffect(() => {
+ if (condition) {
+ savedCallback.current = callback;
+ }
+ }, [callback, condition]);
+
+ useEffect(() => {
+ if (condition) {
+ const tick = () => {
+ savedCallback.current();
+ };
+
+ if (delay !== null) {
+ let id = setInterval(tick, delay);
+ return () => clearInterval(id);
+ }
+ }
+ }, [delay, condition]);
+};
diff --git a/dep-graph/client/src/app/hooks/use-project-graph-data-service.ts b/dep-graph/client/src/app/hooks/use-project-graph-data-service.ts
new file mode 100644
index 0000000000..7848357dd1
--- /dev/null
+++ b/dep-graph/client/src/app/hooks/use-project-graph-data-service.ts
@@ -0,0 +1,24 @@
+import { FetchProjectGraphService } from '../fetch-project-graph-service';
+import { ProjectGraphService } from '../interfaces';
+import { LocalProjectGraphService } from '../local-project-graph-service';
+import { MockProjectGraphService } from '../mock-project-graph-service';
+
+let projectGraphService: ProjectGraphService;
+
+export function useProjectGraphDataService() {
+ if (projectGraphService === undefined) {
+ if (window.environment === 'dev') {
+ projectGraphService = new FetchProjectGraphService();
+ } else if (window.environment === 'watch') {
+ projectGraphService = new MockProjectGraphService();
+ } else if (window.environment === 'release') {
+ if (window.localMode === 'build') {
+ projectGraphService = new LocalProjectGraphService();
+ } else {
+ projectGraphService = new FetchProjectGraphService();
+ }
+ }
+ }
+
+ return projectGraphService;
+}
diff --git a/dep-graph/dep-graph/src/app/models.ts b/dep-graph/client/src/app/interfaces.ts
similarity index 72%
rename from dep-graph/dep-graph/src/app/models.ts
rename to dep-graph/client/src/app/interfaces.ts
index 15e9fe832d..c7215259d0 100644
--- a/dep-graph/dep-graph/src/app/models.ts
+++ b/dep-graph/client/src/app/interfaces.ts
@@ -1,5 +1,5 @@
// nx-ignore-next-line
-import { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
+import type { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
export interface ProjectGraphList {
id: string;
@@ -25,9 +25,3 @@ export interface AppConfig {
projectGraphs: ProjectGraphList[];
defaultProjectGraph: string;
}
-
-export const DEFAULT_CONFIG: AppConfig = {
- showDebugger: false,
- projectGraphs: [],
- defaultProjectGraph: null,
-};
diff --git a/dep-graph/dep-graph/src/app/local-project-graph-service.ts b/dep-graph/client/src/app/local-project-graph-service.ts
similarity index 71%
rename from dep-graph/dep-graph/src/app/local-project-graph-service.ts
rename to dep-graph/client/src/app/local-project-graph-service.ts
index 291af27cd5..b899844b9b 100644
--- a/dep-graph/dep-graph/src/app/local-project-graph-service.ts
+++ b/dep-graph/client/src/app/local-project-graph-service.ts
@@ -1,6 +1,6 @@
// nx-ignore-next-line
-import { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
-import { ProjectGraphService } from './models';
+import type { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
+import { ProjectGraphService } from './interfaces';
export class LocalProjectGraphService implements ProjectGraphService {
async getHash(): Promise {
diff --git a/dep-graph/dep-graph/src/app/machines/custom-selected.state.ts b/dep-graph/client/src/app/machines/custom-selected.state.ts
similarity index 100%
rename from dep-graph/dep-graph/src/app/machines/custom-selected.state.ts
rename to dep-graph/client/src/app/machines/custom-selected.state.ts
diff --git a/dep-graph/dep-graph/src/app/machines/dep-graph.machine.ts b/dep-graph/client/src/app/machines/dep-graph.machine.ts
similarity index 94%
rename from dep-graph/dep-graph/src/app/machines/dep-graph.machine.ts
rename to dep-graph/client/src/app/machines/dep-graph.machine.ts
index 885c92cbf0..0be090e0cc 100644
--- a/dep-graph/dep-graph/src/app/machines/dep-graph.machine.ts
+++ b/dep-graph/client/src/app/machines/dep-graph.machine.ts
@@ -1,6 +1,6 @@
import { assign } from '@xstate/immer';
import { Machine, send, spawn } from 'xstate';
-import { useGraphService } from '../graph.service';
+import { getGraphService } from './graph.service';
import { customSelectedStateConfig } from './custom-selected.state';
import { focusedStateConfig } from './focused.state';
import {
@@ -27,16 +27,22 @@ export const initialContext: DepGraphContext = {
appsDir: '',
},
graph: null,
+ lastPerfReport: {
+ numEdges: 0,
+ numNodes: 0,
+ renderTime: 0,
+ },
};
const graphActor = (callback, receive) => {
- const graphService = useGraphService();
+ const graphService = getGraphService();
receive((e) => {
- const selectedProjectNames = graphService.handleEvent(e);
+ const { selectedProjectNames, perfReport } = graphService.handleEvent(e);
callback({
type: 'setSelectedProjectsFromGraph',
selectedProjectNames,
+ perfReport,
});
});
};
@@ -80,6 +86,7 @@ export const depGraphMachine = Machine<
setSelectedProjectsFromGraph: {
actions: assign((ctx, event) => {
ctx.selectedProjects = event.selectedProjectNames;
+ ctx.lastPerfReport = event.perfReport;
}),
},
selectProject: {
@@ -163,9 +170,11 @@ export const depGraphMachine = Machine<
ctx.groupByFolder = event.groupByFolder;
}),
incrementSearchDepth: assign((ctx) => {
+ ctx.searchDepthEnabled = true;
ctx.searchDepth = ctx.searchDepth + 1;
}),
decrementSearchDepth: assign((ctx) => {
+ ctx.searchDepthEnabled = true;
ctx.searchDepth = ctx.searchDepth > 1 ? ctx.searchDepth - 1 : 1;
}),
setSearchDepthEnabled: assign((ctx, event) => {
diff --git a/dep-graph/dep-graph/src/app/machines/dep-graph.service.ts b/dep-graph/client/src/app/machines/dep-graph.service.ts
similarity index 51%
rename from dep-graph/dep-graph/src/app/machines/dep-graph.service.ts
rename to dep-graph/client/src/app/machines/dep-graph.service.ts
index 1c242f6906..d91fc00280 100644
--- a/dep-graph/dep-graph/src/app/machines/dep-graph.service.ts
+++ b/dep-graph/client/src/app/machines/dep-graph.service.ts
@@ -1,13 +1,9 @@
-import { from } from 'rxjs';
-import { map, shareReplay } from 'rxjs/operators';
import { interpret, Interpreter, Typestate } from 'xstate';
import { depGraphMachine } from './dep-graph.machine';
import {
DepGraphContext,
- DepGraphUIEvents,
- DepGraphSend,
- DepGraphStateObservable,
DepGraphSchema,
+ DepGraphUIEvents,
} from './interfaces';
let depGraphService: Interpreter<
@@ -17,23 +13,13 @@ let depGraphService: Interpreter<
Typestate
>;
-let depGraphState$: DepGraphStateObservable;
-
-export function useDepGraphService(): [DepGraphStateObservable, DepGraphSend] {
+export function getDepGraphService() {
if (!depGraphService) {
depGraphService = interpret(depGraphMachine, {
devTools: !!window.useXstateInspect,
});
depGraphService.start();
-
- depGraphState$ = from(depGraphService).pipe(
- map((state) => ({
- value: state.value,
- context: state.context,
- })),
- shareReplay(1)
- );
}
- return [depGraphState$, depGraphService.send];
+ return depGraphService;
}
diff --git a/dep-graph/dep-graph/src/app/machines/dep-graph.spec.ts b/dep-graph/client/src/app/machines/dep-graph.spec.ts
similarity index 88%
rename from dep-graph/dep-graph/src/app/machines/dep-graph.spec.ts
rename to dep-graph/client/src/app/machines/dep-graph.spec.ts
index 5f92c5c973..ae639c4760 100644
--- a/dep-graph/dep-graph/src/app/machines/dep-graph.spec.ts
+++ b/dep-graph/client/src/app/machines/dep-graph.spec.ts
@@ -1,4 +1,5 @@
-import { ProjectGraphDependency, ProjectGraphNode } from '@nrwl/devkit';
+// nx-ignore-next-line
+import type { ProjectGraphDependency, ProjectGraphNode } from '@nrwl/devkit';
import { depGraphMachine } from './dep-graph.machine';
import { interpret } from 'xstate';
@@ -6,32 +7,44 @@ export const mockProjects: ProjectGraphNode[] = [
{
name: 'app1',
type: 'app',
- data: {},
+ data: {
+ root: 'apps/app1',
+ },
},
{
name: 'app2',
type: 'app',
- data: {},
+ data: {
+ root: 'apps/app2',
+ },
},
{
name: 'ui-lib',
type: 'lib',
- data: {},
+ data: {
+ root: 'libs/ui-lib',
+ },
},
{
name: 'feature-lib1',
type: 'lib',
- data: {},
+ data: {
+ root: 'libs/feature/lib1',
+ },
},
{
name: 'feature-lib2',
type: 'lib',
- data: {},
+ data: {
+ root: 'libs/feature/lib2',
+ },
},
{
name: 'auth-lib',
type: 'lib',
- data: {},
+ data: {
+ root: 'libs/auth-lib',
+ },
},
];
@@ -282,70 +295,9 @@ describe('dep-graph machine', () => {
expect(result.value).toEqual('unselected');
expect(result.context.selectedProjects).toEqual([]);
});
-
- it('should not decrement search depth below 1', () => {
- let result = depGraphMachine.transition(depGraphMachine.initialState, {
- type: 'initGraph',
- projects: mockProjects,
- dependencies: mockDependencies,
- affectedProjects: [],
- workspaceLayout: { appsDir: 'apps', libsDir: 'libs' },
- });
-
- result = depGraphMachine.transition(result, {
- type: 'focusProject',
- projectName: 'app1',
- });
-
- expect(result.context.searchDepth).toEqual(1);
-
- result = depGraphMachine.transition(result, {
- type: 'incrementSearchDepth',
- });
-
- expect(result.context.searchDepth).toEqual(2);
-
- result = depGraphMachine.transition(result, {
- type: 'incrementSearchDepth',
- });
-
- result = depGraphMachine.transition(result, {
- type: 'incrementSearchDepth',
- });
-
- expect(result.context.searchDepth).toEqual(4);
-
- result = depGraphMachine.transition(result, {
- type: 'decrementSearchDepth',
- });
-
- result = depGraphMachine.transition(result, {
- type: 'decrementSearchDepth',
- });
-
- expect(result.context.searchDepth).toEqual(2);
-
- result = depGraphMachine.transition(result, {
- type: 'decrementSearchDepth',
- });
-
- expect(result.context.searchDepth).toEqual(1);
-
- result = depGraphMachine.transition(result, {
- type: 'decrementSearchDepth',
- });
-
- expect(result.context.searchDepth).toEqual(1);
-
- result = depGraphMachine.transition(result, {
- type: 'decrementSearchDepth',
- });
-
- expect(result.context.searchDepth).toEqual(1);
- });
});
- describe('filtering projects by text', () => {
+ describe('search depth', () => {
it('should not decrement search depth below 1', () => {
let result = depGraphMachine.transition(depGraphMachine.initialState, {
type: 'initGraph',
@@ -406,5 +358,41 @@ describe('dep-graph machine', () => {
expect(result.context.searchDepth).toEqual(1);
});
+
+ it('should activate search depth if incremented or decremented', () => {
+ let result = depGraphMachine.transition(depGraphMachine.initialState, {
+ type: 'initGraph',
+ projects: mockProjects,
+ dependencies: mockDependencies,
+ affectedProjects: [],
+ workspaceLayout: { appsDir: 'apps', libsDir: 'libs' },
+ });
+
+ result = depGraphMachine.transition(result, {
+ type: 'setSearchDepthEnabled',
+ searchDepthEnabled: false,
+ });
+
+ expect(result.context.searchDepthEnabled).toBe(false);
+
+ result = depGraphMachine.transition(result, {
+ type: 'incrementSearchDepth',
+ });
+
+ expect(result.context.searchDepthEnabled).toBe(true);
+
+ result = depGraphMachine.transition(result, {
+ type: 'setSearchDepthEnabled',
+ searchDepthEnabled: false,
+ });
+
+ expect(result.context.searchDepthEnabled).toBe(false);
+
+ result = depGraphMachine.transition(result, {
+ type: 'decrementSearchDepth',
+ });
+
+ expect(result.context.searchDepthEnabled).toBe(true);
+ });
});
});
diff --git a/dep-graph/dep-graph/src/app/machines/focused.state.ts b/dep-graph/client/src/app/machines/focused.state.ts
similarity index 95%
rename from dep-graph/dep-graph/src/app/machines/focused.state.ts
rename to dep-graph/client/src/app/machines/focused.state.ts
index b4ebb518c2..0b5d9a3917 100644
--- a/dep-graph/dep-graph/src/app/machines/focused.state.ts
+++ b/dep-graph/client/src/app/machines/focused.state.ts
@@ -1,6 +1,5 @@
import { assign } from '@xstate/immer';
import { send } from 'xstate';
-import { selectProjectsForFocusedProject } from '../util';
import { DepGraphStateNodeConfig } from './interfaces';
export const focusedStateConfig: DepGraphStateNodeConfig = {
diff --git a/dep-graph/dep-graph/src/app/graph.service.ts b/dep-graph/client/src/app/machines/graph.service.ts
similarity index 67%
rename from dep-graph/dep-graph/src/app/graph.service.ts
rename to dep-graph/client/src/app/machines/graph.service.ts
index 8a0334580c..457192c2f3 100644
--- a/dep-graph/dep-graph/src/app/graph.service.ts
+++ b/dep-graph/client/src/app/machines/graph.service.ts
@@ -1,9 +1,9 @@
import { GraphService } from './graph';
-import { GraphTooltipService } from './tooltip-service';
+import { GraphTooltipService } from '../tooltip-service';
let graphService: GraphService;
-export function useGraphService(): GraphService {
+export function getGraphService(): GraphService {
if (!graphService) {
graphService = new GraphService(
new GraphTooltipService(),
diff --git a/dep-graph/dep-graph/src/app/graph.ts b/dep-graph/client/src/app/machines/graph.ts
similarity index 85%
rename from dep-graph/dep-graph/src/app/graph.ts
rename to dep-graph/client/src/app/machines/graph.ts
index c594795575..aa4ea610db 100644
--- a/dep-graph/dep-graph/src/app/graph.ts
+++ b/dep-graph/client/src/app/machines/graph.ts
@@ -1,35 +1,27 @@
+// nx-ignore-next-line
import type { ProjectGraphDependency, ProjectGraphNode } from '@nrwl/devkit';
import type { VirtualElement } from '@popperjs/core';
-import * as cy from 'cytoscape';
-import * as cytoscapeDagre from 'cytoscape-dagre';
-import * as popper from 'cytoscape-popper';
-import { Subject } from 'rxjs';
+import { default as cy } from 'cytoscape';
+import { default as cytoscapeDagre } from 'cytoscape-dagre';
+import { default as popper } from 'cytoscape-popper';
import type { Instance } from 'tippy.js';
-import { GraphRenderEvents } from './machines/interfaces';
-import { ProjectNodeToolTip } from './project-node-tooltip';
-import { edgeStyles, nodeStyles } from './styles-graph';
-import { GraphTooltipService } from './tooltip-service';
+import { ProjectNodeToolTip } from '../project-node-tooltip';
+import { edgeStyles, nodeStyles } from '../styles-graph';
+import { GraphTooltipService } from '../tooltip-service';
import {
CytoscapeDagreConfig,
ParentNode,
ProjectEdge,
ProjectNode,
-} from './util-cytoscape';
+} from '../util-cytoscape';
+import { GraphRenderEvents, GraphPerfReport } from './interfaces';
-export interface GraphPerfReport {
- renderTime: number;
- numNodes: number;
- numEdges: number;
-}
export class GraphService {
private traversalGraph: cy.Core;
private renderGraph: cy.Core;
private openTooltip: Instance = null;
- private renderTimesSubject = new Subject();
- renderTimes$ = this.renderTimesSubject.asObservable();
-
constructor(
private tooltipService: GraphTooltipService,
private containerId: string
@@ -38,17 +30,18 @@ export class GraphService {
cy.use(popper);
}
- handleEvent(event: GraphRenderEvents): string[] {
+ handleEvent(event: GraphRenderEvents): {
+ selectedProjectNames: string[];
+ perfReport: GraphPerfReport;
+ } {
const time = Date.now();
- if (
- this.renderGraph &&
- event.type !== 'notifyGraphFocusProject' &&
- event.type !== 'notifyGraphUpdateGraph'
- ) {
+ if (this.renderGraph && event.type !== 'notifyGraphUpdateGraph') {
this.renderGraph.nodes('.focused').removeClass('focused');
}
+ this.tooltipService.hideAll();
+
switch (event.type) {
case 'notifyGraphInitGraph':
this.initGraph(
@@ -104,40 +97,43 @@ export class GraphService {
break;
}
- let visibleProjects: string[] = [];
+ let selectedProjectNames: string[] = [];
+ let perfReport: GraphPerfReport = {
+ numEdges: 0,
+ numNodes: 0,
+ renderTime: 0,
+ };
if (this.renderGraph) {
this.renderGraph
.elements()
.sort((a, b) => a.id().localeCompare(b.id()))
- .layout({
+ .layout({
name: 'dagre',
nodeDimensionsIncludeLabels: true,
rankSep: 75,
rankDir: 'TB',
edgeSep: 50,
ranker: 'network-simplex',
- })
+ } as CytoscapeDagreConfig)
.run();
this.renderGraph.fit().center().resize();
- visibleProjects = this.renderGraph
+ selectedProjectNames = this.renderGraph
.nodes('[type!="dir"]')
.map((node) => node.id());
const renderTime = Date.now() - time;
- const report: GraphPerfReport = {
+ perfReport = {
renderTime,
numNodes: this.renderGraph.nodes().length,
numEdges: this.renderGraph.edges().length,
};
-
- this.renderTimesSubject.next(report);
}
- return visibleProjects;
+ return { selectedProjectNames, perfReport };
}
setShownProjects(selectedProjectNames: string[]) {
@@ -178,9 +174,13 @@ export class GraphService {
this.renderGraph?.nodes() ?? this.traversalGraph.collection();
const nodeToHide = this.renderGraph.$id(projectName);
- const nodesToAdd = currentNodes.difference(nodeToHide);
+ const nodesToAdd = currentNodes
+ .difference(nodeToHide)
+ .difference(nodeToHide.ancestors());
const ancestorsToAdd = nodesToAdd.ancestors();
- const nodesToRender = nodesToAdd.union(ancestorsToAdd);
+
+ let nodesToRender = nodesToAdd.union(ancestorsToAdd);
+
const edgesToRender = nodesToRender.edgesTo(nodesToRender);
this.transferToRenderGraph(nodesToRender.union(edgesToRender));
@@ -229,22 +229,28 @@ export class GraphService {
includePath: boolean,
searchDepth: number = -1
) {
- const split = search.split(',');
+ if (search === '') {
+ this.transferToRenderGraph(this.traversalGraph.collection());
+ } else {
+ const split = search.split(',');
- let filteredProjects = this.traversalGraph.nodes().filter((node) => {
- return split.findIndex((splitItem) => node.id().includes(splitItem)) > -1;
- });
+ let filteredProjects = this.traversalGraph.nodes().filter((node) => {
+ return (
+ split.findIndex((splitItem) => node.id().includes(splitItem)) > -1
+ );
+ });
- if (includePath) {
- filteredProjects = filteredProjects.union(
- this.includeProjectsByDepth(filteredProjects, searchDepth)
- );
+ if (includePath) {
+ filteredProjects = filteredProjects.union(
+ this.includeProjectsByDepth(filteredProjects, searchDepth)
+ );
+ }
+
+ filteredProjects = filteredProjects.union(filteredProjects.ancestors());
+ const edgesToRender = filteredProjects.edgesTo(filteredProjects);
+
+ this.transferToRenderGraph(filteredProjects.union(edgesToRender));
}
-
- filteredProjects = filteredProjects.union(filteredProjects.ancestors());
- const edgesToRender = filteredProjects.edgesTo(filteredProjects);
-
- this.transferToRenderGraph(filteredProjects.union(edgesToRender));
}
private transferToRenderGraph(elements: cy.Collection) {
@@ -281,10 +287,6 @@ export class GraphService {
this.listenForProjectNodeHovers();
}
- getImage() {
- return this.renderGraph.png({ bg: '#fff', full: true });
- }
-
private includeProjectsByDepth(
projects: cy.NodeCollection | cy.NodeSingular,
depth: number = -1
@@ -463,4 +465,8 @@ export class GraphService {
.removeClass('highlight');
});
}
+
+ getImage() {
+ return this.renderGraph.png({ bg: '#fff', full: true });
+ }
}
diff --git a/dep-graph/dep-graph/src/app/machines/interfaces.ts b/dep-graph/client/src/app/machines/interfaces.ts
similarity index 85%
rename from dep-graph/dep-graph/src/app/machines/interfaces.ts
rename to dep-graph/client/src/app/machines/interfaces.ts
index a9ffa5c35f..39ddccfecc 100644
--- a/dep-graph/dep-graph/src/app/machines/interfaces.ts
+++ b/dep-graph/client/src/app/machines/interfaces.ts
@@ -1,7 +1,13 @@
-import { ProjectGraphDependency, ProjectGraphNode } from '@nrwl/devkit';
+// nx-ignore-next-line
+import type { ProjectGraphDependency, ProjectGraphNode } from '@nrwl/devkit';
import { Observable } from 'rxjs';
-import { ActionObject, ActorRef, StateNodeConfig, StateValue } from 'xstate';
-import { GraphService } from '../graph';
+import {
+ ActionObject,
+ ActorRef,
+ State,
+ StateNodeConfig,
+ StateValue,
+} from 'xstate';
// The hierarchical (recursive) schema for the states
export interface DepGraphSchema {
@@ -14,10 +20,19 @@ export interface DepGraphSchema {
};
}
+export interface GraphPerfReport {
+ renderTime: number;
+ numNodes: number;
+ numEdges: number;
+}
// The events that the machine handles
export type DepGraphUIEvents =
- | { type: 'setSelectedProjectsFromGraph'; selectedProjectNames: string[] }
+ | {
+ type: 'setSelectedProjectsFromGraph';
+ selectedProjectNames: string[];
+ perfReport: GraphPerfReport;
+ }
| { type: 'selectProject'; projectName: string }
| { type: 'deselectProject'; projectName: string }
| { type: 'selectAll' }
@@ -122,6 +137,7 @@ export interface DepGraphContext {
appsDir: string;
};
graph: ActorRef;
+ lastPerfReport: GraphPerfReport;
}
export type DepGraphStateNodeConfig = StateNodeConfig<
@@ -138,3 +154,13 @@ export type DepGraphStateObservable = Observable<{
value: StateValue;
context: DepGraphContext;
}>;
+
+export type DepGraphState = State<
+ DepGraphContext,
+ DepGraphUIEvents,
+ any,
+ {
+ value: any;
+ context: DepGraphContext;
+ }
+>;
diff --git a/dep-graph/client/src/app/machines/selectors.ts b/dep-graph/client/src/app/machines/selectors.ts
new file mode 100644
index 0000000000..14f2473611
--- /dev/null
+++ b/dep-graph/client/src/app/machines/selectors.ts
@@ -0,0 +1,43 @@
+import type { ProjectGraphNode } from '@nrwl/devkit';
+import { DepGraphSelector } from '../hooks/use-dep-graph-selector';
+import { WorkspaceLayout } from '../interfaces';
+import { GraphPerfReport } from './interfaces';
+
+export const allProjectsSelector: DepGraphSelector = (
+ state
+) => state.context.projects;
+
+export const workspaceLayoutSelector: DepGraphSelector = (
+ state
+) => state.context.workspaceLayout;
+
+export const selectedProjectNamesSelector: DepGraphSelector = (
+ state
+) => state.context.selectedProjects;
+
+export const projectIsSelectedSelector: DepGraphSelector = (state) =>
+ state.context.selectedProjects.length > 0;
+
+export const lastPerfReportSelector: DepGraphSelector = (
+ state
+) => state.context.lastPerfReport;
+
+export const focusedProjectNameSelector: DepGraphSelector = (state) =>
+ state.context.focusedProject;
+
+export const searchDepthSelector: DepGraphSelector<{
+ searchDepth: number;
+ searchDepthEnabled: boolean;
+}> = (state) => ({
+ searchDepth: state.context.searchDepth,
+ searchDepthEnabled: state.context.searchDepthEnabled,
+});
+
+export const includePathSelector: DepGraphSelector = (state) =>
+ state.context.includePath;
+
+export const textFilterSelector: DepGraphSelector = (state) =>
+ state.context.textFilter;
+
+export const hasAffectedProjectsSelector: DepGraphSelector = (state) =>
+ state.context.affectedProjects.length > 0;
diff --git a/dep-graph/dep-graph/src/app/machines/text-filtered.state.ts b/dep-graph/client/src/app/machines/text-filtered.state.ts
similarity index 100%
rename from dep-graph/dep-graph/src/app/machines/text-filtered.state.ts
rename to dep-graph/client/src/app/machines/text-filtered.state.ts
diff --git a/dep-graph/dep-graph/src/app/machines/unselected.state.ts b/dep-graph/client/src/app/machines/unselected.state.ts
similarity index 96%
rename from dep-graph/dep-graph/src/app/machines/unselected.state.ts
rename to dep-graph/client/src/app/machines/unselected.state.ts
index 2df2f04c02..8191e03e83 100644
--- a/dep-graph/dep-graph/src/app/machines/unselected.state.ts
+++ b/dep-graph/client/src/app/machines/unselected.state.ts
@@ -1,6 +1,5 @@
import { assign } from '@xstate/immer';
import { send } from 'xstate';
-import { useGraphService } from '../graph.service';
import { DepGraphStateNodeConfig } from './interfaces';
export const unselectedStateConfig: DepGraphStateNodeConfig = {
diff --git a/dep-graph/dep-graph/src/app/mock-project-graph-service.ts b/dep-graph/client/src/app/mock-project-graph-service.ts
similarity index 90%
rename from dep-graph/dep-graph/src/app/mock-project-graph-service.ts
rename to dep-graph/client/src/app/mock-project-graph-service.ts
index df9951f6ca..7911fd6574 100644
--- a/dep-graph/dep-graph/src/app/mock-project-graph-service.ts
+++ b/dep-graph/client/src/app/mock-project-graph-service.ts
@@ -1,7 +1,7 @@
-import { ProjectGraphDependency, ProjectGraphNode } from '@nrwl/devkit';
+import type { ProjectGraphDependency, ProjectGraphNode } from '@nrwl/devkit';
// nx-ignore-next-line
-import { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
-import { ProjectGraphService } from '../app/models';
+import type { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
+import { ProjectGraphService } from '../app/interfaces';
export class MockProjectGraphService implements ProjectGraphService {
private response: DepGraphClientResponse = {
diff --git a/dep-graph/dep-graph/src/app/project-node-tooltip.ts b/dep-graph/client/src/app/project-node-tooltip.ts
similarity index 83%
rename from dep-graph/dep-graph/src/app/project-node-tooltip.ts
rename to dep-graph/client/src/app/project-node-tooltip.ts
index 9c990e8b8b..cde4eea2e6 100644
--- a/dep-graph/dep-graph/src/app/project-node-tooltip.ts
+++ b/dep-graph/client/src/app/project-node-tooltip.ts
@@ -1,5 +1,5 @@
import * as cy from 'cytoscape';
-import { useDepGraphService } from './machines/dep-graph.service';
+import { getDepGraphService } from './machines/dep-graph.service';
export class ProjectNodeToolTip {
constructor(private node: cy.NodeSingular) {}
@@ -54,15 +54,21 @@ export class ProjectNodeToolTip {
wrapper.classList.add('flex');
- const [_, send] = useDepGraphService();
+ const depGraphService = getDepGraphService();
focusButton.addEventListener('click', () =>
- send({ type: 'focusProject', projectName: this.node.attr('id') })
+ depGraphService.send({
+ type: 'focusProject',
+ projectName: this.node.attr('id'),
+ })
);
focusButton.innerText = 'Focus';
excludeButton.addEventListener('click', () => {
- send({ type: 'deselectProject', projectName: this.node.attr('id') });
+ depGraphService.send({
+ type: 'deselectProject',
+ projectName: this.node.attr('id'),
+ });
});
excludeButton.innerText = 'Exclude';
diff --git a/dep-graph/client/src/app/shell.tsx b/dep-graph/client/src/app/shell.tsx
new file mode 100644
index 0000000000..3663cd4251
--- /dev/null
+++ b/dep-graph/client/src/app/shell.tsx
@@ -0,0 +1,190 @@
+// nx-ignore-next-line
+import type { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
+import Tippy from '@tippyjs/react';
+import { useEffect, useState } from 'react';
+import DebuggerPanel from './debugger-panel';
+import { useDepGraphService } from './hooks/use-dep-graph';
+import { useDepGraphSelector } from './hooks/use-dep-graph-selector';
+import { useEnvironmentConfig } from './hooks/use-environment-config';
+import { useIntervalWhen } from './hooks/use-interval-when';
+import { useProjectGraphDataService } from './hooks/use-project-graph-data-service';
+import { getGraphService } from './machines/graph.service';
+import {
+ lastPerfReportSelector,
+ projectIsSelectedSelector,
+} from './machines/selectors';
+import Sidebar from './sidebar/sidebar';
+
+export function Shell() {
+ const depGraphService = useDepGraphService();
+
+ const projectGraphService = useProjectGraphDataService();
+ const environment = useEnvironmentConfig();
+ const lastPerfReport = useDepGraphSelector(lastPerfReportSelector);
+ const projectIsSelected = useDepGraphSelector(projectIsSelectedSelector);
+
+ const [selectedProjectId, setSelectedProjectId] = useState(
+ environment.appConfig.defaultProjectGraph
+ );
+
+ function projectChange(projectGraphId: string) {
+ setSelectedProjectId(projectGraphId);
+ }
+
+ useEffect(() => {
+ const { appConfig } = environment;
+
+ const projectInfo = appConfig.projectGraphs.find(
+ (graph) => graph.id === selectedProjectId
+ );
+
+ const fetchProjectGraph = async () => {
+ const project: DepGraphClientResponse =
+ await projectGraphService.getProjectGraph(projectInfo.url);
+
+ const workspaceLayout = project?.layout;
+ depGraphService.send({
+ type: 'initGraph',
+ projects: project.projects,
+ dependencies: project.dependencies,
+ affectedProjects: project.affected,
+ workspaceLayout: workspaceLayout,
+ });
+
+ if (environment.focusedProject) {
+ depGraphService.send({
+ type: 'focusProject',
+ projectName: environment.focusedProject,
+ });
+ }
+
+ if (environment.groupByFolder) {
+ depGraphService.send({
+ type: 'setGroupByFolder',
+ groupByFolder: true,
+ });
+ }
+ };
+ fetchProjectGraph();
+ }, [selectedProjectId, environment, depGraphService, projectGraphService]);
+
+ useIntervalWhen(
+ () => {
+ const projectInfo = environment.appConfig.projectGraphs.find(
+ (graph) => graph.id === selectedProjectId
+ );
+
+ const fetchProjectGraph = async () => {
+ const project: DepGraphClientResponse =
+ await projectGraphService.getProjectGraph(projectInfo.url);
+
+ depGraphService.send({
+ type: 'updateGraph',
+ projects: project.projects,
+ dependencies: project.dependencies,
+ });
+ };
+
+ fetchProjectGraph();
+ },
+ 5000,
+ environment.watch
+ );
+
+ function downloadImage() {
+ const graph = getGraphService();
+ const data = graph.getImage();
+
+ let downloadLink = document.createElement('a');
+ downloadLink.href = data;
+ downloadLink.download = 'graph.png';
+ // this is necessary as link.click() does not work on the latest firefox
+ downloadLink.dispatchEvent(
+ new MouseEvent('click', {
+ bubbles: true,
+ cancelable: true,
+ view: window,
+ })
+ );
+ }
+
+ return (
+ <>
+
+
+ {environment.appConfig.showDebugger ? (
+
+ ) : null}
+
+ {!projectIsSelected ? (
+
+
+
Please select projects in the sidebar.
+
+ ) : null}
+
+
+ >
+ );
+}
diff --git a/dep-graph/client/src/app/sidebar/focused-project-panel.tsx b/dep-graph/client/src/app/sidebar/focused-project-panel.tsx
new file mode 100644
index 0000000000..0803c59b77
--- /dev/null
+++ b/dep-graph/client/src/app/sidebar/focused-project-panel.tsx
@@ -0,0 +1,59 @@
+import { memo } from 'react';
+
+export interface FocusedProjectPanelProps {
+ focusedProject: string;
+ resetFocus: () => void;
+}
+
+export const FocusedProjectPanel = memo(
+ ({ focusedProject, resetFocus }: FocusedProjectPanelProps) => {
+ return (
+
+
resetFocus()}
+ >
+
+
+ Focused on {focusedProject}
+
+
+
+
+ );
+ }
+);
+
+export default FocusedProjectPanel;
diff --git a/dep-graph/client/src/app/sidebar/group-by-folder-panel.tsx b/dep-graph/client/src/app/sidebar/group-by-folder-panel.tsx
new file mode 100644
index 0000000000..96914a873f
--- /dev/null
+++ b/dep-graph/client/src/app/sidebar/group-by-folder-panel.tsx
@@ -0,0 +1,39 @@
+import { memo } from 'react';
+
+export interface DisplayOptionsPanelProps {
+ groupByFolderChanged: (checked: boolean) => void;
+}
+
+export const GroupByFolderPanel = memo(
+ ({ groupByFolderChanged }: DisplayOptionsPanelProps) => {
+ return (
+
+
+
+ groupByFolderChanged(event.target.checked)}
+ >
+
+
+
+
+ Visually arrange libraries by folders with different colors.
+
+
+
+
+ );
+ }
+);
+
+export default GroupByFolderPanel;
diff --git a/dep-graph/client/src/app/sidebar/project-list.tsx b/dep-graph/client/src/app/sidebar/project-list.tsx
new file mode 100644
index 0000000000..803074ec93
--- /dev/null
+++ b/dep-graph/client/src/app/sidebar/project-list.tsx
@@ -0,0 +1,280 @@
+import type { ProjectGraphNode } from '@nrwl/devkit';
+import { useDepGraphService } from '../hooks/use-dep-graph';
+import { useDepGraphSelector } from '../hooks/use-dep-graph-selector';
+import {
+ allProjectsSelector,
+ selectedProjectNamesSelector,
+ workspaceLayoutSelector,
+} from '../machines/selectors';
+import { parseParentDirectoriesFromPilePath } from '../util';
+
+function getProjectsByType(type: string, projects: ProjectGraphNode[]) {
+ return projects
+ .filter((project) => project.type === type)
+ .sort((a, b) => a.name.localeCompare(b.name));
+}
+
+interface SidebarProject {
+ projectGraphNode: ProjectGraphNode;
+ isSelected: boolean;
+}
+
+type DirectoryProjectRecord = Record;
+
+function groupProjectsByDirectory(
+ projects: ProjectGraphNode[],
+ selectedProjects: string[],
+ workspaceLayout: { appsDir: string; libsDir: string }
+): DirectoryProjectRecord {
+ let groups = {};
+
+ projects.forEach((project) => {
+ const workspaceRoot =
+ project.type === 'app' || project.type === 'e2e'
+ ? workspaceLayout.appsDir
+ : workspaceLayout.libsDir;
+ const directories = parseParentDirectoriesFromPilePath(
+ project.data.root,
+ workspaceRoot
+ );
+ const directory = directories.join('/');
+
+ if (!groups.hasOwnProperty(directory)) {
+ groups[directory] = [];
+ }
+ groups[directory].push({
+ projectGraphNode: project,
+ isSelected: selectedProjects.includes(project.name),
+ });
+ });
+
+ return groups;
+}
+
+function ProjectListItem({
+ project,
+ toggleProject,
+ focusProject,
+}: {
+ project: SidebarProject;
+ toggleProject: (projectId: string, currentlySelected: boolean) => void;
+ focusProject: (projectId: string) => void;
+}) {
+ return (
+
+
+
+
+
+
+ {project.isSelected ? (
+
+ toggleProject(project.projectGraphNode.name, project.isSelected)
+ }
+ >
+
+
+ ) : null}
+
+ );
+}
+
+function SubProjectList({
+ headerText,
+ projects,
+ selectProject,
+ deselectProject,
+ focusProject,
+}: {
+ headerText: string;
+ projects: SidebarProject[];
+ selectProject: (projectName: string) => void;
+ deselectProject: (projectName: string) => void;
+ focusProject: (projectName: string) => void;
+}) {
+ let sortedProjects = [...projects];
+ sortedProjects.sort((a, b) => {
+ return a.projectGraphNode.name.localeCompare(b.projectGraphNode.name);
+ });
+
+ function toggleProject(projectName: string, currentlySelected: boolean) {
+ if (currentlySelected) {
+ deselectProject(projectName);
+ } else {
+ selectProject(projectName);
+ }
+ }
+
+ return (
+ <>
+
+ {headerText}
+
+
+ {sortedProjects.map((project) => {
+ return (
+
+ );
+ })}
+
+ >
+ );
+}
+
+export function ProjectList() {
+ const depGraphService = useDepGraphService();
+
+ function deselectProject(projectName: string) {
+ depGraphService.send({ type: 'deselectProject', projectName });
+ }
+
+ function selectProject(projectName: string) {
+ depGraphService.send({ type: 'selectProject', projectName });
+ }
+
+ function focusProject(projectName: string) {
+ depGraphService.send({ type: 'focusProject', projectName });
+ }
+
+ const projects = useDepGraphSelector(allProjectsSelector);
+ const workspaceLayout = useDepGraphSelector(workspaceLayoutSelector);
+ const selectedProjects = useDepGraphSelector(selectedProjectNamesSelector);
+
+ const appProjects = getProjectsByType('app', projects);
+ const libProjects = getProjectsByType('lib', projects);
+ const e2eProjects = getProjectsByType('e2e', projects);
+
+ const appDirectoryGroups = groupProjectsByDirectory(
+ appProjects,
+ selectedProjects,
+ workspaceLayout
+ );
+ const libDirectoryGroups = groupProjectsByDirectory(
+ libProjects,
+ selectedProjects,
+ workspaceLayout
+ );
+ const e2eDirectoryGroups = groupProjectsByDirectory(
+ e2eProjects,
+ selectedProjects,
+ workspaceLayout
+ );
+
+ const sortedAppDirectories = Object.keys(appDirectoryGroups).sort();
+ const sortedLibDirectories = Object.keys(libDirectoryGroups).sort();
+ const sortedE2EDirectories = Object.keys(e2eDirectoryGroups).sort();
+
+ return (
+
+
+ app projects
+
+
+ {sortedAppDirectories.map((directoryName) => {
+ return (
+
+ );
+ })}
+
+
+ e2e projects
+
+
+ {sortedE2EDirectories.map((directoryName) => {
+ return (
+
+ );
+ })}
+
+
+ lib projects
+
+
+ {sortedLibDirectories.map((directoryName) => {
+ return (
+
+ );
+ })}
+
+ );
+}
+
+export default ProjectList;
diff --git a/dep-graph/client/src/app/sidebar/search-depth.tsx b/dep-graph/client/src/app/sidebar/search-depth.tsx
new file mode 100644
index 0000000000..e46a3b9fc8
--- /dev/null
+++ b/dep-graph/client/src/app/sidebar/search-depth.tsx
@@ -0,0 +1,102 @@
+import { memo } from 'react';
+
+export interface SearchDepthProps {
+ searchDepth: number;
+ searchDepthEnabled: boolean;
+ searchDepthFilterEnabledChange: (checked: boolean) => void;
+ decrementDepthFilter: () => void;
+ incrementDepthFilter: () => void;
+}
+
+export const SearchDepth = memo(
+ ({
+ searchDepth,
+ searchDepthEnabled,
+ searchDepthFilterEnabledChange,
+ decrementDepthFilter,
+ incrementDepthFilter,
+ }: SearchDepthProps) => {
+ return (
+
+
+
+
+ searchDepthFilterEnabledChange(event.target.checked)
+ }
+ >
+
+
+
+
+ Explore connected libraries step by step.
+
+
+
+
+
+
+
+ {searchDepth}
+
+
+
+
+
+ );
+ }
+);
+
+export default SearchDepth;
diff --git a/dep-graph/client/src/app/sidebar/show-hide-projects.tsx b/dep-graph/client/src/app/sidebar/show-hide-projects.tsx
new file mode 100644
index 0000000000..b2a27e3b1f
--- /dev/null
+++ b/dep-graph/client/src/app/sidebar/show-hide-projects.tsx
@@ -0,0 +1,100 @@
+import { memo } from 'react';
+
+export interface ShowHideAllProjectsProps {
+ showAllProjects: () => void;
+ hideAllProjects: () => void;
+ showAffectedProjects: () => void;
+ hasAffectedProjects: boolean;
+}
+
+export const ShowHideAllProjects = memo(
+ ({
+ showAllProjects,
+ hideAllProjects,
+ showAffectedProjects,
+ hasAffectedProjects: affectedProjects,
+ }: ShowHideAllProjectsProps) => {
+ return (
+
+
+
+ {affectedProjects ? (
+
+ ) : null}
+
+
+
+ );
+ }
+);
+
+export default ShowHideAllProjects;
diff --git a/dep-graph/client/src/app/sidebar/sidebar.tsx b/dep-graph/client/src/app/sidebar/sidebar.tsx
new file mode 100644
index 0000000000..c94bf0a907
--- /dev/null
+++ b/dep-graph/client/src/app/sidebar/sidebar.tsx
@@ -0,0 +1,181 @@
+import { useCallback } from 'react';
+import { useDepGraphService } from '../hooks/use-dep-graph';
+import { useDepGraphSelector } from '../hooks/use-dep-graph-selector';
+import {
+ focusedProjectNameSelector,
+ hasAffectedProjectsSelector,
+ includePathSelector,
+ searchDepthSelector,
+ textFilterSelector,
+} from '../machines/selectors';
+import FocusedProjectPanel from './focused-project-panel';
+import GroupByFolderPanel from './group-by-folder-panel';
+import ProjectList from './project-list';
+import SearchDepth from './search-depth';
+import ShowHideProjects from './show-hide-projects';
+import TextFilterPanel from './text-filter-panel';
+
+export function Sidebar() {
+ const depGraphService = useDepGraphService();
+ const focusedProject = useDepGraphSelector(focusedProjectNameSelector);
+ const searchDepthInfo = useDepGraphSelector(searchDepthSelector);
+ const includePath = useDepGraphSelector(includePathSelector);
+ const textFilter = useDepGraphSelector(textFilterSelector);
+ const hasAffectedProjects = useDepGraphSelector(hasAffectedProjectsSelector);
+
+ function resetFocus() {
+ depGraphService.send({ type: 'unfocusProject' });
+ }
+
+ function showAllProjects() {
+ depGraphService.send({ type: 'selectAll' });
+ }
+
+ function hideAllProjects() {
+ depGraphService.send({ type: 'deselectAll' });
+ }
+
+ function showAffectedProjects() {
+ depGraphService.send({ type: 'selectAffected' });
+ }
+
+ function searchDepthFilterEnabledChange(checked: boolean) {
+ depGraphService.send({
+ type: 'setSearchDepthEnabled',
+ searchDepthEnabled: checked,
+ });
+ }
+
+ function groupByFolderChanged(checked: boolean) {
+ depGraphService.send({ type: 'setGroupByFolder', groupByFolder: checked });
+ }
+
+ function incrementDepthFilter() {
+ depGraphService.send({ type: 'incrementSearchDepth' });
+ }
+
+ function decrementDepthFilter() {
+ depGraphService.send({ type: 'decrementSearchDepth' });
+ }
+
+ function resetTextFilter() {
+ depGraphService.send({ type: 'clearTextFilter' });
+ }
+
+ function includeLibsInPathChange() {
+ depGraphService.send({
+ type: 'setIncludeProjectsByPath',
+ includeProjectsByPath: !includePath,
+ });
+ }
+
+ const updateTextFilter = useCallback(
+ (textFilter: string) => {
+ depGraphService.send({ type: 'filterByText', search: textFilter });
+ },
+ [depGraphService]
+ );
+
+ return (
+
+ );
+}
+
+export default Sidebar;
diff --git a/dep-graph/client/src/app/sidebar/text-filter-panel.tsx b/dep-graph/client/src/app/sidebar/text-filter-panel.tsx
new file mode 100644
index 0000000000..e008d9557b
--- /dev/null
+++ b/dep-graph/client/src/app/sidebar/text-filter-panel.tsx
@@ -0,0 +1,139 @@
+import { useEffect, useState } from 'react';
+import { useDebounce } from '../hooks/use-debounce';
+
+export interface TextFilterPanelProps {
+ textFilter: string;
+ resetTextFilter: () => void;
+ updateTextFilter: (textFilter: string) => void;
+ toggleIncludeLibsInPathChange: () => void;
+ includePath: boolean;
+}
+
+export function TextFilterPanel({
+ textFilter,
+ resetTextFilter,
+ updateTextFilter,
+ toggleIncludeLibsInPathChange,
+ includePath,
+}: TextFilterPanelProps) {
+ const [currentTextFilter, setCurrentTextFilter] = useState('');
+
+ const debouncedTextFilter = useDebounce(currentTextFilter, 500);
+
+ function onTextFilterKeyUp(event: React.KeyboardEvent) {
+ if (event.key === 'Enter') {
+ updateTextFilter(event.currentTarget.value);
+ }
+ }
+
+ function onTextInputChange(change: string) {
+ if (change === '') {
+ setCurrentTextFilter('');
+ resetTextFilter();
+ } else {
+ setCurrentTextFilter(change);
+ }
+ }
+
+ function resetClicked() {
+ setCurrentTextFilter('');
+ resetTextFilter();
+ }
+
+ useEffect(() => {
+ if (debouncedTextFilter !== '') {
+ updateTextFilter(debouncedTextFilter);
+ }
+ }, [debouncedTextFilter, updateTextFilter]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ Show libraries that are related to your search.
+
+
+
+
+
+ );
+}
+
+export default TextFilterPanel;
diff --git a/dep-graph/client/src/app/state.provider.tsx b/dep-graph/client/src/app/state.provider.tsx
new file mode 100644
index 0000000000..d4f7a5773f
--- /dev/null
+++ b/dep-graph/client/src/app/state.provider.tsx
@@ -0,0 +1,18 @@
+import { createContext } from 'react';
+import { InterpreterFrom } from 'xstate';
+import { depGraphMachine } from './machines/dep-graph.machine';
+import { getDepGraphService } from './machines/dep-graph.service';
+
+export const GlobalStateContext = createContext<
+ InterpreterFrom
+>({} as InterpreterFrom);
+
+export const GlobalStateProvider = (props) => {
+ const depGraphService = getDepGraphService();
+
+ return (
+
+ {props.children}
+
+ );
+};
diff --git a/dep-graph/dep-graph/src/app/styles-graph/edges.ts b/dep-graph/client/src/app/styles-graph/edges.ts
similarity index 100%
rename from dep-graph/dep-graph/src/app/styles-graph/edges.ts
rename to dep-graph/client/src/app/styles-graph/edges.ts
diff --git a/dep-graph/dep-graph/src/app/styles-graph/fonts.ts b/dep-graph/client/src/app/styles-graph/fonts.ts
similarity index 100%
rename from dep-graph/dep-graph/src/app/styles-graph/fonts.ts
rename to dep-graph/client/src/app/styles-graph/fonts.ts
diff --git a/dep-graph/dep-graph/src/app/styles-graph/index.ts b/dep-graph/client/src/app/styles-graph/index.ts
similarity index 100%
rename from dep-graph/dep-graph/src/app/styles-graph/index.ts
rename to dep-graph/client/src/app/styles-graph/index.ts
diff --git a/dep-graph/dep-graph/src/app/styles-graph/nodes.ts b/dep-graph/client/src/app/styles-graph/nodes.ts
similarity index 100%
rename from dep-graph/dep-graph/src/app/styles-graph/nodes.ts
rename to dep-graph/client/src/app/styles-graph/nodes.ts
diff --git a/dep-graph/dep-graph/src/app/styles-graph/palette.ts b/dep-graph/client/src/app/styles-graph/palette.ts
similarity index 100%
rename from dep-graph/dep-graph/src/app/styles-graph/palette.ts
rename to dep-graph/client/src/app/styles-graph/palette.ts
diff --git a/dep-graph/dep-graph/src/app/tooltip-service.ts b/dep-graph/client/src/app/tooltip-service.ts
similarity index 100%
rename from dep-graph/dep-graph/src/app/tooltip-service.ts
rename to dep-graph/client/src/app/tooltip-service.ts
diff --git a/dep-graph/dep-graph/src/app/util-cytoscape/cytoscape.models.ts b/dep-graph/client/src/app/util-cytoscape/cytoscape.models.ts
similarity index 100%
rename from dep-graph/dep-graph/src/app/util-cytoscape/cytoscape.models.ts
rename to dep-graph/client/src/app/util-cytoscape/cytoscape.models.ts
diff --git a/dep-graph/dep-graph/src/app/util-cytoscape/edge.ts b/dep-graph/client/src/app/util-cytoscape/edge.ts
similarity index 100%
rename from dep-graph/dep-graph/src/app/util-cytoscape/edge.ts
rename to dep-graph/client/src/app/util-cytoscape/edge.ts
diff --git a/dep-graph/dep-graph/src/app/util-cytoscape/index.ts b/dep-graph/client/src/app/util-cytoscape/index.ts
similarity index 100%
rename from dep-graph/dep-graph/src/app/util-cytoscape/index.ts
rename to dep-graph/client/src/app/util-cytoscape/index.ts
diff --git a/dep-graph/dep-graph/src/app/util-cytoscape/parent-node.ts b/dep-graph/client/src/app/util-cytoscape/parent-node.ts
similarity index 100%
rename from dep-graph/dep-graph/src/app/util-cytoscape/parent-node.ts
rename to dep-graph/client/src/app/util-cytoscape/parent-node.ts
diff --git a/dep-graph/dep-graph/src/app/util-cytoscape/project-node.spec.ts b/dep-graph/client/src/app/util-cytoscape/project-node.spec.ts
similarity index 100%
rename from dep-graph/dep-graph/src/app/util-cytoscape/project-node.spec.ts
rename to dep-graph/client/src/app/util-cytoscape/project-node.spec.ts
diff --git a/dep-graph/dep-graph/src/app/util-cytoscape/project-node.ts b/dep-graph/client/src/app/util-cytoscape/project-node.ts
similarity index 100%
rename from dep-graph/dep-graph/src/app/util-cytoscape/project-node.ts
rename to dep-graph/client/src/app/util-cytoscape/project-node.ts
diff --git a/dep-graph/dep-graph/src/app/util.spec.ts b/dep-graph/client/src/app/util.spec.ts
similarity index 94%
rename from dep-graph/dep-graph/src/app/util.spec.ts
rename to dep-graph/client/src/app/util.spec.ts
index d4794b1963..cf142f5830 100644
--- a/dep-graph/dep-graph/src/app/util.spec.ts
+++ b/dep-graph/client/src/app/util.spec.ts
@@ -1,6 +1,6 @@
import { parseParentDirectoriesFromPilePath } from './util';
-describe('parseParentDirectoriesFromPilePath', () => {
+describe('parseParentDirectoriesFromFilePath', () => {
// path, workspaceRoot, output
const cases: [string, string, string[]][] = [
['apps/app1', 'apps', []],
diff --git a/dep-graph/client/src/app/util.ts b/dep-graph/client/src/app/util.ts
new file mode 100644
index 0000000000..fcbd84e7b8
--- /dev/null
+++ b/dep-graph/client/src/app/util.ts
@@ -0,0 +1,56 @@
+import { ProjectGraphDependency } from '@nrwl/devkit';
+
+export function trimBackSlash(value: string): string {
+ return value.replace(/\/$/, '');
+}
+
+export function parseParentDirectoriesFromPilePath(
+ path: string,
+ workspaceRoot: string
+) {
+ const root = trimBackSlash(path);
+
+ // split the source root on directory separator
+ const split: string[] = root.split('/');
+
+ // check the first part for libs or apps, depending on workspaceLayout
+ if (split[0] === trimBackSlash(workspaceRoot)) {
+ split.shift();
+ }
+
+ // pop off the last element, which should be the lib name
+ split.pop();
+
+ return split;
+}
+
+export function hasPath(
+ dependencies: Record,
+ target: string,
+ node: string,
+ visited: string[],
+ currentSearchDepth: number,
+ maxSearchDepth: number = -1 // -1 indicates unlimited search depth
+) {
+ if (target === node) return true;
+
+ if (maxSearchDepth === -1 || currentSearchDepth <= maxSearchDepth) {
+ for (let d of dependencies[node] || []) {
+ if (visited.indexOf(d.target) > -1) continue;
+ visited.push(d.target);
+ if (
+ hasPath(
+ dependencies,
+ target,
+ d.target,
+ visited,
+ currentSearchDepth + 1,
+ maxSearchDepth
+ )
+ )
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/dep-graph/dep-graph/src/assets/.gitkeep b/dep-graph/client/src/assets/.gitkeep
similarity index 100%
rename from dep-graph/dep-graph/src/assets/.gitkeep
rename to dep-graph/client/src/assets/.gitkeep
diff --git a/dep-graph/dep-graph/src/assets/environment.dev.js b/dep-graph/client/src/assets/environment.dev.js
similarity index 100%
rename from dep-graph/dep-graph/src/assets/environment.dev.js
rename to dep-graph/client/src/assets/environment.dev.js
diff --git a/dep-graph/dep-graph/src/assets/environment.watch.js b/dep-graph/client/src/assets/environment.watch.js
similarity index 100%
rename from dep-graph/dep-graph/src/assets/environment.watch.js
rename to dep-graph/client/src/assets/environment.watch.js
diff --git a/dep-graph/dep-graph/src/assets/graphs/affected.json b/dep-graph/client/src/assets/graphs/affected.json
similarity index 100%
rename from dep-graph/dep-graph/src/assets/graphs/affected.json
rename to dep-graph/client/src/assets/graphs/affected.json
diff --git a/dep-graph/dep-graph/src/assets/graphs/focus-testing.json b/dep-graph/client/src/assets/graphs/focus-testing.json
similarity index 100%
rename from dep-graph/dep-graph/src/assets/graphs/focus-testing.json
rename to dep-graph/client/src/assets/graphs/focus-testing.json
diff --git a/dep-graph/dep-graph/src/assets/graphs/nx-examples.json b/dep-graph/client/src/assets/graphs/nx-examples.json
similarity index 100%
rename from dep-graph/dep-graph/src/assets/graphs/nx-examples.json
rename to dep-graph/client/src/assets/graphs/nx-examples.json
diff --git a/dep-graph/dep-graph/src/assets/graphs/nx.json b/dep-graph/client/src/assets/graphs/nx.json
similarity index 100%
rename from dep-graph/dep-graph/src/assets/graphs/nx.json
rename to dep-graph/client/src/assets/graphs/nx.json
diff --git a/dep-graph/dep-graph/src/assets/graphs/ocean.json b/dep-graph/client/src/assets/graphs/ocean.json
similarity index 100%
rename from dep-graph/dep-graph/src/assets/graphs/ocean.json
rename to dep-graph/client/src/assets/graphs/ocean.json
diff --git a/dep-graph/dep-graph/src/assets/graphs/storybook.json b/dep-graph/client/src/assets/graphs/storybook.json
similarity index 100%
rename from dep-graph/dep-graph/src/assets/graphs/storybook.json
rename to dep-graph/client/src/assets/graphs/storybook.json
diff --git a/dep-graph/dep-graph/src/assets/graphs/sub-apps.json b/dep-graph/client/src/assets/graphs/sub-apps.json
similarity index 100%
rename from dep-graph/dep-graph/src/assets/graphs/sub-apps.json
rename to dep-graph/client/src/assets/graphs/sub-apps.json
diff --git a/dep-graph/client/src/environments/environment.prod.ts b/dep-graph/client/src/environments/environment.prod.ts
new file mode 100644
index 0000000000..c9669790be
--- /dev/null
+++ b/dep-graph/client/src/environments/environment.prod.ts
@@ -0,0 +1,3 @@
+export const environment = {
+ production: true,
+};
diff --git a/dep-graph/client/src/environments/environment.ts b/dep-graph/client/src/environments/environment.ts
new file mode 100644
index 0000000000..7ed83767ff
--- /dev/null
+++ b/dep-graph/client/src/environments/environment.ts
@@ -0,0 +1,6 @@
+// This file can be replaced during build by using the `fileReplacements` array.
+// When building for production, this file is replaced with `environment.prod.ts`.
+
+export const environment = {
+ production: false,
+};
diff --git a/dep-graph/dep-graph/src/favicon.ico b/dep-graph/client/src/favicon.ico
similarity index 100%
rename from dep-graph/dep-graph/src/favicon.ico
rename to dep-graph/client/src/favicon.ico
diff --git a/dep-graph/dep-graph/src/globals.d.ts b/dep-graph/client/src/globals.d.ts
similarity index 79%
rename from dep-graph/dep-graph/src/globals.d.ts
rename to dep-graph/client/src/globals.d.ts
index d91999b287..2f74708f14 100644
--- a/dep-graph/dep-graph/src/globals.d.ts
+++ b/dep-graph/client/src/globals.d.ts
@@ -1,7 +1,5 @@
// nx-ignore-next-line
-import { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
-import { ProjectGraph, ProjectGraphNode } from '@nrwl/devkit';
-import { ProjectGraphList } from './graphs';
+import type { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
import { AppConfig } from './app/models';
export declare global {
diff --git a/dep-graph/client/src/index.html b/dep-graph/client/src/index.html
new file mode 100644
index 0000000000..571eaa5db8
--- /dev/null
+++ b/dep-graph/client/src/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+ Nx Workspace Dependency Graph
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dep-graph/client/src/main.tsx b/dep-graph/client/src/main.tsx
new file mode 100644
index 0000000000..829b92689d
--- /dev/null
+++ b/dep-graph/client/src/main.tsx
@@ -0,0 +1,18 @@
+import { StrictMode } from 'react';
+import * as ReactDOM from 'react-dom';
+import { inspect } from '@xstate/inspect';
+import App from './app/app';
+
+if (window.useXstateInspect === true) {
+ inspect({
+ url: 'https://stately.ai/viz?inspect',
+ iframe: false, // open in new window
+ });
+}
+
+ReactDOM.render(
+
+
+ ,
+ document.getElementById('app')
+);
diff --git a/dep-graph/dep-graph/src/polyfills.ts b/dep-graph/client/src/polyfills.ts
similarity index 100%
rename from dep-graph/dep-graph/src/polyfills.ts
rename to dep-graph/client/src/polyfills.ts
diff --git a/dep-graph/dep-graph/src/styles.scss b/dep-graph/client/src/styles.scss
similarity index 100%
rename from dep-graph/dep-graph/src/styles.scss
rename to dep-graph/client/src/styles.scss
index 0a0d006916..22dea58976 100644
--- a/dep-graph/dep-graph/src/styles.scss
+++ b/dep-graph/client/src/styles.scss
@@ -20,6 +20,24 @@ html {
display: flex;
}
+#no-projects-chosen {
+ display: flex;
+ width: 100%;
+ height: 100%;
+ align-items: center;
+ justify-content: center;
+}
+
+#graph-container,
+#cytoscape-graph {
+ width: 100%;
+ height: 100%;
+}
+
+canvas {
+ cursor: pointer;
+}
+
.tippy-box[data-theme~='nx'] {
box-sizing: border-box;
border-style: solid;
@@ -90,21 +108,3 @@ html {
background-color: rgba(243, 244, 246, 1);
}
}
-
-#no-projects-chosen {
- display: flex;
- width: 100%;
- height: 100%;
- align-items: center;
- justify-content: center;
-}
-
-#graph-container,
-#cytoscape-graph {
- width: 100%;
- height: 100%;
-}
-
-canvas {
- cursor: pointer;
-}
diff --git a/dep-graph/dep-graph/tailwind.config.js b/dep-graph/client/tailwind.config.js
similarity index 100%
rename from dep-graph/dep-graph/tailwind.config.js
rename to dep-graph/client/tailwind.config.js
diff --git a/dep-graph/client/tsconfig.app.json b/dep-graph/client/tsconfig.app.json
new file mode 100644
index 0000000000..7a1a35f04e
--- /dev/null
+++ b/dep-graph/client/tsconfig.app.json
@@ -0,0 +1,23 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../dist/out-tsc",
+ "types": ["node"],
+ "lib": ["DOM", "es2019"]
+ },
+ "files": [
+ "../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
+ "../../node_modules/@nrwl/react/typings/image.d.ts"
+ ],
+ "exclude": [
+ "**/*.spec.ts",
+ "**/*.test.ts",
+ "**/*.spec.tsx",
+ "**/*.test.tsx",
+ "**/*.spec.js",
+ "**/*.test.js",
+ "**/*.spec.jsx",
+ "**/*.test.jsx"
+ ],
+ "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
+}
diff --git a/dep-graph/dep-graph/tsconfig.json b/dep-graph/client/tsconfig.json
similarity index 67%
rename from dep-graph/dep-graph/tsconfig.json
rename to dep-graph/client/tsconfig.json
index 63dbe35fb2..ad56986cc1 100644
--- a/dep-graph/dep-graph/tsconfig.json
+++ b/dep-graph/client/tsconfig.json
@@ -1,5 +1,9 @@
{
"extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "allowSyntheticDefaultImports": true
+ },
"files": [],
"include": [],
"references": [
diff --git a/dep-graph/client/tsconfig.spec.json b/dep-graph/client/tsconfig.spec.json
new file mode 100644
index 0000000000..65f3147400
--- /dev/null
+++ b/dep-graph/client/tsconfig.spec.json
@@ -0,0 +1,24 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../../dist/out-tsc",
+ "module": "commonjs",
+ "types": ["jest", "node"],
+ "lib": ["DOM"]
+ },
+ "include": [
+ "**/*.test.ts",
+ "**/*.spec.ts",
+ "**/*.test.tsx",
+ "**/*.spec.tsx",
+ "**/*.test.js",
+ "**/*.spec.js",
+ "**/*.test.jsx",
+ "**/*.spec.jsx",
+ "**/*.d.ts"
+ ],
+ "files": [
+ "../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
+ "../../node_modules/@nrwl/react/typings/image.d.ts"
+ ]
+}
diff --git a/dep-graph/dep-graph-e2e/project.json b/dep-graph/dep-graph-e2e/project.json
deleted file mode 100644
index 46b324374f..0000000000
--- a/dep-graph/dep-graph-e2e/project.json
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- "root": "dep-graph/dep-graph-e2e",
- "sourceRoot": "dep-graph/dep-graph-e2e/src",
- "projectType": "application",
- "targets": {
- "e2e-disabled": {
- "executor": "@nrwl/cypress:cypress",
- "options": {
- "cypressConfig": "dep-graph/dep-graph-e2e/cypress.json",
- "tsConfig": "dep-graph/dep-graph-e2e/tsconfig.e2e.json",
- "devServerTarget": "dep-graph-dep-graph:serve-for-e2e",
- "baseUrl": "http://localhost:4200"
- }
- },
- "e2e-watch-disabled": {
- "executor": "@nrwl/cypress:cypress",
- "options": {
- "cypressConfig": "dep-graph/dep-graph-e2e/cypress-watch-mode.json",
- "tsConfig": "dep-graph/dep-graph-e2e/tsconfig.e2e.json",
- "devServerTarget": "dep-graph-dep-graph:serve-for-e2e:watch",
- "baseUrl": "http://localhost:4200"
- }
- },
- "lint": {
- "executor": "@nrwl/linter:eslint",
- "outputs": ["{options.outputFile}"],
- "options": {
- "lintFilePatterns": ["dep-graph/dep-graph-e2e/**/*.ts"]
- }
- }
- },
- "implicitDependencies": ["dep-graph-dep-graph"]
-}
diff --git a/dep-graph/dep-graph/.babelrc b/dep-graph/dep-graph/.babelrc
deleted file mode 100644
index 0cae4a9a81..0000000000
--- a/dep-graph/dep-graph/.babelrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "presets": ["@nrwl/web/babel"]
-}
diff --git a/dep-graph/dep-graph/.eslintrc b/dep-graph/dep-graph/.eslintrc
deleted file mode 100644
index ab8f38339c..0000000000
--- a/dep-graph/dep-graph/.eslintrc
+++ /dev/null
@@ -1 +0,0 @@
-{ "extends": "../../.eslintrc", "rules": {}, "ignorePatterns": ["!**/*"] }
diff --git a/dep-graph/dep-graph/jest.config.js b/dep-graph/dep-graph/jest.config.js
deleted file mode 100644
index 35d028a001..0000000000
--- a/dep-graph/dep-graph/jest.config.js
+++ /dev/null
@@ -1,15 +0,0 @@
-module.exports = {
- name: 'dep-graph-dep-graph',
- preset: '../../jest.preset.js',
- setupFilesAfterEnv: ['/src/test-setup.ts'],
- globals: {
- 'ts-jest': {
- tsconfig: '/tsconfig.spec.json',
- },
- },
- transform: {
- '^.+\\.[tj]s$': 'ts-jest',
- },
- moduleFileExtensions: ['ts', 'js', 'html'],
- coverageDirectory: '../../coverage/dep-graph/dep-graph',
-};
diff --git a/dep-graph/dep-graph/src/app/app.ts b/dep-graph/dep-graph/src/app/app.ts
deleted file mode 100644
index efa5380e9f..0000000000
--- a/dep-graph/dep-graph/src/app/app.ts
+++ /dev/null
@@ -1,153 +0,0 @@
-// nx-ignore-next-line
-import type { DepGraphClientResponse } from '@nrwl/workspace/src/command-line/dep-graph';
-import { fromEvent } from 'rxjs';
-import { startWith } from 'rxjs/operators';
-import tippy from 'tippy.js';
-import { DebuggerPanel } from './debugger-panel';
-import { useGraphService } from './graph.service';
-import { useDepGraphService } from './machines/dep-graph.service';
-import { DepGraphSend } from './machines/interfaces';
-import { AppConfig, DEFAULT_CONFIG, ProjectGraphService } from './models';
-import { SidebarComponent } from './ui-sidebar/sidebar';
-
-export class AppComponent {
- private sidebar = new SidebarComponent();
- private graph = useGraphService();
- private debuggerPanel: DebuggerPanel;
-
- private windowResize$ = fromEvent(window, 'resize').pipe(startWith({}));
-
- private send: DepGraphSend;
-
- private downloadImageButton: HTMLButtonElement;
-
- constructor(
- private config: AppConfig = DEFAULT_CONFIG,
- private projectGraphService: ProjectGraphService
- ) {
- const [state$, send] = useDepGraphService();
-
- state$.subscribe((state) => {
- if (state.context.selectedProjects.length !== 0) {
- document.getElementById('no-projects-chosen').style.display = 'none';
- if (this.downloadImageButton) {
- this.downloadImageButton.classList.remove('opacity-0');
- }
- } else {
- document.getElementById('no-projects-chosen').style.display = 'flex';
- if (this.downloadImageButton) {
- this.downloadImageButton.classList.add('opacity-0');
- }
- }
- });
-
- this.send = send;
-
- this.loadProjectGraph(config.defaultProjectGraph);
- this.render();
-
- if (window.watch === true) {
- setInterval(
- () => this.updateProjectGraph(config.defaultProjectGraph),
- 5000
- );
- }
-
- this.downloadImageButton = document.querySelector(
- '[data-cy="downloadImageButton"]'
- );
-
- this.downloadImageButton.addEventListener('click', () => {
- const graph = useGraphService();
- const data = graph.getImage();
-
- var downloadLink = document.createElement('a');
- downloadLink.href = data;
- downloadLink.download = 'graph.png';
- // this is necessary as link.click() does not work on the latest firefox
- downloadLink.dispatchEvent(
- new MouseEvent('click', {
- bubbles: true,
- cancelable: true,
- view: window,
- })
- );
- });
-
- tippy(this.downloadImageButton, {
- content: 'Download Graph as PNG',
- placement: 'right',
- theme: 'nx',
- });
- }
-
- private async loadProjectGraph(projectGraphId: string) {
- const projectInfo = this.config.projectGraphs.find(
- (graph) => graph.id === projectGraphId
- );
-
- const project: DepGraphClientResponse =
- await this.projectGraphService.getProjectGraph(projectInfo.url);
-
- const workspaceLayout = project?.layout;
- this.send({
- type: 'initGraph',
- projects: project.projects,
- dependencies: project.dependencies,
- affectedProjects: project.affected,
- workspaceLayout: workspaceLayout,
- });
-
- if (!!window.focusedProject) {
- this.send({
- type: 'focusProject',
- projectName: window.focusedProject,
- });
- }
-
- if (window.groupByFolder) {
- this.send({
- type: 'setGroupByFolder',
- groupByFolder: window.groupByFolder,
- });
- }
- }
-
- private async updateProjectGraph(projectGraphId: string) {
- const projectInfo = this.config.projectGraphs.find(
- (graph) => graph.id === projectGraphId
- );
-
- const project: DepGraphClientResponse =
- await this.projectGraphService.getProjectGraph(projectInfo.url);
-
- this.send({
- type: 'updateGraph',
- projects: project.projects,
- dependencies: project.dependencies,
- });
- }
-
- private render() {
- const debuggerPanelContainer = document.getElementById('debugger-panel');
-
- if (this.config.showDebugger) {
- debuggerPanelContainer.hidden = false;
- debuggerPanelContainer.style.display = 'flex';
-
- this.debuggerPanel = new DebuggerPanel(
- debuggerPanelContainer,
- this.config.projectGraphs,
- this.config.defaultProjectGraph
- );
-
- this.debuggerPanel.selectProject$.subscribe((id) => {
- this.loadProjectGraph(id);
- });
-
- this.graph.renderTimes$.subscribe(
- (renderTime) => (this.debuggerPanel.renderTime = renderTime)
- );
- }
- }
-}
diff --git a/dep-graph/dep-graph/src/app/debugger-panel.ts b/dep-graph/dep-graph/src/app/debugger-panel.ts
deleted file mode 100644
index f44b5f1480..0000000000
--- a/dep-graph/dep-graph/src/app/debugger-panel.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { Subject } from 'rxjs';
-import { GraphPerfReport } from './graph';
-import { ProjectGraphList } from './models';
-import { removeChildrenFromContainer } from './util';
-
-export class DebuggerPanel {
- set renderTime(renderTime: GraphPerfReport) {
- this.renderReportElement.innerHTML = `Last render took ${renderTime.renderTime}ms: ${renderTime.numNodes} nodes | ${renderTime.numEdges} edges.`;
- }
-
- private selectProjectSubject = new Subject();
-
- selectProject$ = this.selectProjectSubject.asObservable();
-
- private renderReportElement: HTMLElement;
-
- constructor(
- private container: HTMLElement,
- private projectGraphs: ProjectGraphList[],
- private initialSelectedGraph: string
- ) {
- this.render();
- }
-
- private render() {
- removeChildrenFromContainer(this.container);
-
- const header = document.createElement('h4');
- header.className = 'text-lg font-bold mr-4';
- header.innerText = `Debugger`;
-
- const select = document.createElement('select');
- select.className =
- 'w-auto flex items-center px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white';
-
- this.projectGraphs.forEach((projectGraph) => {
- const option = document.createElement('option');
- option.value = projectGraph.id;
- option.innerText = projectGraph.label;
-
- select.appendChild(option);
- });
-
- select.value = this.initialSelectedGraph;
- select.dataset['cy'] = 'project-select';
-
- select.onchange = (event) =>
- this.selectProjectSubject.next(
- (event.currentTarget as HTMLSelectElement).value
- );
-
- this.renderReportElement = document.createElement('p');
- this.renderReportElement.className = 'text-sm';
-
- this.container.appendChild(header);
- this.container.appendChild(select);
- this.container.appendChild(this.renderReportElement);
- }
-}
diff --git a/dep-graph/dep-graph/src/app/ui-sidebar/display-options-panel.ts b/dep-graph/dep-graph/src/app/ui-sidebar/display-options-panel.ts
deleted file mode 100644
index 3db236220d..0000000000
--- a/dep-graph/dep-graph/src/app/ui-sidebar/display-options-panel.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-import { useGraphService } from '../graph.service';
-import { useDepGraphService } from '../machines/dep-graph.service';
-import { DepGraphSend } from '../machines/interfaces';
-import { removeChildrenFromContainer } from '../util';
-
-export class DisplayOptionsPanel {
- searchDepthDisplay: HTMLSpanElement;
- affectedButtonElement: HTMLElement;
- groupByFolderCheckboxElement: HTMLInputElement;
-
- send: DepGraphSend;
-
- constructor(private container: HTMLElement) {
- const [state$, send] = useDepGraphService();
- this.send = send;
- this.render();
-
- state$.subscribe((state) => {
- if (
- state.context.affectedProjects.length > 0 &&
- this.affectedButtonElement.classList.contains('hidden')
- ) {
- this.affectedButtonElement.classList.remove('hidden');
- } else if (
- state.context.affectedProjects.length === 0 &&
- !this.affectedButtonElement.classList.contains('hidden')
- ) {
- this.affectedButtonElement.classList.add('hidden');
- }
-
- this.searchDepthDisplay.innerText = state.context.searchDepth.toString();
-
- if (
- this.groupByFolderCheckboxElement.checked !==
- state.context.groupByFolder
- ) {
- this.groupByFolderCheckboxElement.checked = state.context.groupByFolder;
- }
- });
- }
-
- private static renderHtmlTemplate(): HTMLElement {
- const render = document.createElement('template');
- render.innerHTML = `
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Visually arrange libraries by folders with different colors.
-
-
-
-
-
-
-
-
-
-
-
-
Explore connected libraries step by step.
-
-
-
-
-
-
1
-
-
-
-
-
- `.trim();
- return render.content.firstChild as HTMLElement;
- }
-
- private render() {
- removeChildrenFromContainer(this.container);
-
- const element = DisplayOptionsPanel.renderHtmlTemplate();
-
- this.affectedButtonElement = element.querySelector(
- '[data-cy="affectedButton"]'
- );
-
- this.affectedButtonElement.addEventListener('click', () =>
- this.send({ type: 'selectAffected' })
- );
-
- const selectAllButtonElement: HTMLElement = element.querySelector(
- '[data-cy="selectAllButton"]'
- );
- selectAllButtonElement.addEventListener('click', () => {
- this.send({ type: 'selectAll' });
- });
-
- const deselectAllButtonElement: HTMLElement = element.querySelector(
- '[data-cy="deselectAllButton"]'
- );
- deselectAllButtonElement.addEventListener('click', () => {
- this.send({ type: 'deselectAll' });
- });
-
- this.groupByFolderCheckboxElement =
- element.querySelector('#displayOptions');
-
- this.groupByFolderCheckboxElement.addEventListener(
- 'change',
- (event: InputEvent) =>
- this.send({
- type: 'setGroupByFolder',
- groupByFolder: (event.target as HTMLInputElement).checked,
- })
- );
-
- this.searchDepthDisplay = element.querySelector('#depthFilterValue');
- const incrementButtonElement: HTMLInputElement = element.querySelector(
- '#depthFilterIncrement'
- );
- const decrementButtonElement: HTMLInputElement = element.querySelector(
- '#depthFilterDecrement'
- );
- const searchDepthEnabledElement: HTMLInputElement =
- element.querySelector('#depthFilter');
-
- incrementButtonElement.addEventListener('click', () => {
- this.send({ type: 'incrementSearchDepth' });
- });
- decrementButtonElement.addEventListener('click', () => {
- this.send({ type: 'decrementSearchDepth' });
- });
-
- searchDepthEnabledElement.addEventListener('change', (event: InputEvent) =>
- this.send({
- type: 'setSearchDepthEnabled',
- searchDepthEnabled: (event.target).checked,
- })
- );
-
- this.container.appendChild(element);
- }
-}
diff --git a/dep-graph/dep-graph/src/app/ui-sidebar/focused-project-panel.ts b/dep-graph/dep-graph/src/app/ui-sidebar/focused-project-panel.ts
deleted file mode 100644
index 93e6b8d689..0000000000
--- a/dep-graph/dep-graph/src/app/ui-sidebar/focused-project-panel.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { map } from 'rxjs/operators';
-import { useDepGraphService } from '../machines/dep-graph.service';
-import { DepGraphSend } from '../machines/interfaces';
-import { removeChildrenFromContainer } from '../util';
-
-export class FocusedProjectPanel {
- private send: DepGraphSend;
-
- constructor(private container: HTMLElement) {
- const [state$, send] = useDepGraphService();
- this.send = send;
-
- state$
- .pipe(map(({ context }) => context.focusedProject))
- .subscribe((focusedProject) => this.render(focusedProject));
- }
-
- private static renderHtmlTemplate(): HTMLElement {
- const render = document.createElement('template');
- render.innerHTML = `
-
-
-
-
- e2e-some-other-very-long-project-name
-
-
-
-
- `.trim();
- return render.content.firstChild as HTMLElement;
- }
-
- private render(projectName?: string) {
- removeChildrenFromContainer(this.container);
-
- const element = FocusedProjectPanel.renderHtmlTemplate();
- const projectNameElement: HTMLElement = element.querySelector(
- '#focused-project-name'
- );
- const unfocusButtonElement = element.querySelector(
- '[data-cy="unfocusButton"]'
- );
-
- if (projectName && projectName !== '') {
- projectNameElement.innerText = `Focused on ${projectName}`;
- this.container.hidden = false;
- } else {
- this.container.hidden = true;
- }
-
- unfocusButtonElement.addEventListener('click', () =>
- this.send({ type: 'unfocusProject' })
- );
-
- this.container.appendChild(element);
- }
-}
diff --git a/dep-graph/dep-graph/src/app/ui-sidebar/project-list.ts b/dep-graph/dep-graph/src/app/ui-sidebar/project-list.ts
deleted file mode 100644
index 3da59c0762..0000000000
--- a/dep-graph/dep-graph/src/app/ui-sidebar/project-list.ts
+++ /dev/null
@@ -1,227 +0,0 @@
-import type { ProjectGraphNode } from '@nrwl/devkit';
-import { useDepGraphService } from '../machines/dep-graph.service';
-import { DepGraphSend } from '../machines/interfaces';
-import {
- parseParentDirectoriesFromPilePath,
- removeChildrenFromContainer,
-} from '../util';
-
-export class ProjectList {
- private projectItems: Record = {};
-
- private send: DepGraphSend;
-
- constructor(private container: HTMLElement) {
- const [state$, send] = useDepGraphService();
- this.send = send;
-
- state$.subscribe((state) => {
- this.render(state.context.projects, state.context.workspaceLayout);
- this.setSelectedProjects(state.context.selectedProjects);
- });
- }
-
- private static renderHtmlItemTemplate(): HTMLElement {
- const render = document.createElement('template');
- render.innerHTML = `
-
-
-
-
-
-
-
-
-
- `.trim();
- return render.content.firstChild as HTMLElement;
- }
-
- setSelectedProjects(selectedProjects: string[]) {
- Object.keys(this.projectItems).forEach((projectName) => {
- this.projectItems[projectName].dataset['active'] = selectedProjects
- .includes(projectName)
- .toString();
- this.projectItems[projectName].dispatchEvent(new CustomEvent('change'));
- });
- }
-
- checkAllProjects() {
- this.send({ type: 'selectAll' });
- }
-
- uncheckAllProjects() {
- this.send({ type: 'deselectAll' });
- }
-
- uncheckProject(projectName: string) {
- this.send({ type: 'deselectProject', projectName });
- }
-
- private render(
- projects: ProjectGraphNode[],
- workspaceLayout: { appsDir: string; libsDir: string }
- ) {
- removeChildrenFromContainer(this.container);
-
- const appProjects = this.getProjectsByType('app', projects);
- const libProjects = this.getProjectsByType('lib', projects);
- const e2eProjects = this.getProjectsByType('e2e', projects);
-
- const appDirectoryGroups = this.groupProjectsByDirectory(
- appProjects,
- workspaceLayout
- );
- const libDirectoryGroups = this.groupProjectsByDirectory(
- libProjects,
- workspaceLayout
- );
- const e2eDirectoryGroups = this.groupProjectsByDirectory(
- e2eProjects,
- workspaceLayout
- );
-
- const sortedAppDirectories = Object.keys(appDirectoryGroups).sort();
- const sortedLibDirectories = Object.keys(libDirectoryGroups).sort();
- const sortedE2EDirectories = Object.keys(e2eDirectoryGroups).sort();
-
- const appsHeader = document.createElement('h2');
- appsHeader.className =
- 'mt-8 text-lg font-bold border-b border-gray-50 border-solid';
- appsHeader.textContent = 'App projects';
- this.container.append(appsHeader);
-
- sortedAppDirectories.forEach((directoryName) => {
- this.createProjectList(directoryName, appDirectoryGroups[directoryName]);
- });
-
- const e2eHeader = document.createElement('h2');
- e2eHeader.className =
- 'mt-8 text-lg font-bold border-b border-gray-50 border-solid';
- e2eHeader.textContent = 'E2E projects';
- this.container.append(e2eHeader);
-
- sortedE2EDirectories.forEach((directoryName) => {
- this.createProjectList(directoryName, e2eDirectoryGroups[directoryName]);
- });
-
- const libHeader = document.createElement('h2');
- libHeader.className =
- 'mt-8 text-lg font-bold border-b border-gray-50 border-solid';
- libHeader.textContent = 'Lib projects';
- this.container.append(libHeader);
-
- sortedLibDirectories.forEach((directoryName) => {
- this.createProjectList(directoryName, libDirectoryGroups[directoryName]);
- });
- }
-
- private getProjectsByType(type: string, projects: ProjectGraphNode[]) {
- return projects
- .filter((project) => project.type === type)
- .sort((a, b) => a.name.localeCompare(b.name));
- }
-
- private groupProjectsByDirectory(
- projects: ProjectGraphNode[],
- workspaceLayout: { appsDir: string; libsDir: string }
- ) {
- let groups = {};
-
- projects.forEach((project) => {
- const workspaceRoot =
- project.type === 'app' || project.type === 'e2e'
- ? workspaceLayout.appsDir
- : workspaceLayout.libsDir;
- const directories = parseParentDirectoriesFromPilePath(
- project.data.root,
- workspaceRoot
- );
- const directory = directories.join('/');
-
- if (!groups.hasOwnProperty(directory)) {
- groups[directory] = [];
- }
- groups[directory].push(project);
- });
-
- return groups;
- }
-
- private createProjectList(headerText, projects) {
- const header = document.createElement('h3');
- header.className =
- 'mt-4 py-2 uppercase tracking-wide font-semibold text-sm lg:text-xs text-gray-900 cursor-text';
- header.textContent = headerText;
-
- const formGroup = document.createElement('ul');
- formGroup.className = 'mt-2 -ml-3';
-
- let sortedProjects = [...projects];
- sortedProjects.sort((a, b) => {
- return a.name.localeCompare(b.name);
- });
-
- projects.forEach((project) => {
- const element = ProjectList.renderHtmlItemTemplate();
- const selectedIconElement: HTMLElement = element.querySelector(
- 'span[role="selection-icon"]'
- );
- const focusButtonElement: HTMLElement = element.querySelector('button');
- focusButtonElement.addEventListener('click', () =>
- this.send({ type: 'focusProject', projectName: project.name })
- );
-
- const projectNameElement: HTMLElement = element.querySelector('label');
- projectNameElement.innerText = project.name;
- projectNameElement.dataset['project'] = project.name;
- projectNameElement.dataset['active'] = 'false';
- selectedIconElement.classList.add('hidden');
-
- projectNameElement.addEventListener('click', (event) => {
- const el = event.target as HTMLElement;
- if (el.dataset['active'] === 'true') {
- this.send({
- type: 'deselectProject',
- projectName: el.dataset['project'],
- });
- } else {
- this.send({
- type: 'selectProject',
- projectName: el.dataset['project'],
- });
- }
- });
-
- projectNameElement.addEventListener('change', (event) => {
- const el = event.target as HTMLElement;
- if (el.dataset['active'] === 'false') {
- selectedIconElement.classList.add('hidden');
- } else selectedIconElement.classList.remove('hidden');
- });
-
- selectedIconElement.addEventListener('click', () => {
- projectNameElement.dispatchEvent(new Event('click'));
- });
-
- this.projectItems[project.name] = projectNameElement;
-
- formGroup.append(element);
- });
-
- this.container.append(header);
- this.container.append(formGroup);
- }
-}
diff --git a/dep-graph/dep-graph/src/app/ui-sidebar/sidebar.ts b/dep-graph/dep-graph/src/app/ui-sidebar/sidebar.ts
deleted file mode 100644
index c320dc4466..0000000000
--- a/dep-graph/dep-graph/src/app/ui-sidebar/sidebar.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { DisplayOptionsPanel } from './display-options-panel';
-import { FocusedProjectPanel } from './focused-project-panel';
-import { ProjectList } from './project-list';
-import { TextFilterPanel } from './text-filter-panel';
-
-declare var ResizeObserver;
-
-export class SidebarComponent {
- private displayOptionsPanel: DisplayOptionsPanel;
- private focusedProjectPanel: FocusedProjectPanel;
- private textFilterPanel: TextFilterPanel;
- private projectList: ProjectList;
-
- constructor() {
- const displayOptionsPanelContainer = document.getElementById(
- 'display-options-panel'
- );
-
- this.displayOptionsPanel = new DisplayOptionsPanel(
- displayOptionsPanelContainer
- );
-
- const focusedProjectPanelContainer =
- document.getElementById('focused-project');
-
- this.focusedProjectPanel = new FocusedProjectPanel(
- focusedProjectPanelContainer
- );
-
- const textFilterPanelContainer =
- document.getElementById('text-filter-panel');
- this.textFilterPanel = new TextFilterPanel(textFilterPanelContainer);
-
- const projectListContainer = document.getElementById('project-lists');
- this.projectList = new ProjectList(projectListContainer);
- }
-}
diff --git a/dep-graph/dep-graph/src/app/ui-sidebar/text-filter-panel.ts b/dep-graph/dep-graph/src/app/ui-sidebar/text-filter-panel.ts
deleted file mode 100644
index a9b026ecd9..0000000000
--- a/dep-graph/dep-graph/src/app/ui-sidebar/text-filter-panel.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-import { fromEvent, Subscription } from 'rxjs';
-import { debounceTime, filter, map } from 'rxjs/operators';
-import { useDepGraphService } from '../machines/dep-graph.service';
-import { DepGraphSend } from '../machines/interfaces';
-import { removeChildrenFromContainer } from '../util';
-
-export interface TextFilterChangeEvent {
- text: string;
- includeInPath: boolean;
-}
-
-export class TextFilterPanel {
- private textInput: HTMLInputElement;
- private includeInPathCheckbox: HTMLInputElement;
- private send: DepGraphSend;
-
- constructor(private container: HTMLElement) {
- const [_, send] = useDepGraphService();
- this.send = send;
- this.render();
- }
-
- private static renderHtmlTemplate(): HTMLElement {
- const render = document.createElement('template');
- render.innerHTML = `
-
-
-
-
-
-
-
-
-
-
Show libraries that are related to your search.
-
-
-
-
- `.trim();
- return render.content.firstChild as HTMLElement;
- }
-
- private render() {
- removeChildrenFromContainer(this.container);
-
- const element = TextFilterPanel.renderHtmlTemplate();
- const resetInputElement: HTMLElement =
- element.querySelector('#textFilterReset');
- resetInputElement.classList.add('hidden');
-
- this.textInput = element.querySelector('input[type="text"]');
- this.textInput.addEventListener('keyup', (event) => {
- if (event.key === 'Enter') {
- this.send({ type: 'filterByText', search: this.textInput.value });
- }
-
- if (!!this.textInput.value.length) {
- resetInputElement.classList.remove('hidden');
- this.includeInPathCheckbox.disabled = false;
- } else {
- resetInputElement.classList.add('hidden');
- this.includeInPathCheckbox.disabled = true;
- }
- });
-
- fromEvent(this.textInput, 'keyup')
- .pipe(
- filter((event: KeyboardEvent) => event.key !== 'Enter'),
- debounceTime(500),
- map(() =>
- this.send({ type: 'filterByText', search: this.textInput.value })
- )
- )
- .subscribe();
-
- this.includeInPathCheckbox = element.querySelector('#includeInPath');
- this.includeInPathCheckbox.addEventListener('change', () =>
- this.send({
- type: 'setIncludeProjectsByPath',
- includeProjectsByPath: this.includeInPathCheckbox.checked,
- })
- );
-
- resetInputElement.addEventListener('click', () => {
- this.textInput.value = '';
- this.includeInPathCheckbox.checked = false;
- this.includeInPathCheckbox.disabled = true;
- resetInputElement.classList.add('hidden');
- this.send([{ type: 'clearTextFilter' }]);
- });
-
- this.container.appendChild(element);
- }
-}
diff --git a/dep-graph/dep-graph/src/app/util.ts b/dep-graph/dep-graph/src/app/util.ts
deleted file mode 100644
index 0c0e44f750..0000000000
--- a/dep-graph/dep-graph/src/app/util.ts
+++ /dev/null
@@ -1,143 +0,0 @@
-import { ProjectGraphDependency, ProjectGraphNode } from '@nrwl/devkit';
-
-export function removeChildrenFromContainer(container: HTMLElement) {
- Array.from(container.children).forEach((child) =>
- container.removeChild(child)
- );
-}
-
-export function trimBackSlash(value: string): string {
- return value.replace(/\/$/, '');
-}
-
-export function parseParentDirectoriesFromPilePath(
- path: string,
- workspaceRoot: string
-) {
- const root = trimBackSlash(path);
-
- // split the source root on directory separator
- const split: string[] = root.split('/');
-
- // check the first part for libs or apps, depending on workspaceLayout
- if (split[0] === trimBackSlash(workspaceRoot)) {
- split.shift();
- }
-
- // pop off the last element, which should be the lib name
- split.pop();
-
- return split;
-}
-
-export function hasPath(
- dependencies: Record,
- target: string,
- node: string,
- visited: string[],
- currentSearchDepth: number,
- maxSearchDepth: number = -1 // -1 indicates unlimited search depth
-) {
- if (target === node) return true;
-
- if (maxSearchDepth === -1 || currentSearchDepth <= maxSearchDepth) {
- for (let d of dependencies[node] || []) {
- if (visited.indexOf(d.target) > -1) continue;
- visited.push(d.target);
- if (
- hasPath(
- dependencies,
- target,
- d.target,
- visited,
- currentSearchDepth + 1,
- maxSearchDepth
- )
- )
- return true;
- }
- }
-
- return false;
-}
-
-export function selectProjectsForFocusedProject(
- projects: ProjectGraphNode[],
- dependencies: Record,
- focusedProjectName: string,
- searchDepth: number
-) {
- return projects
- .map((project) => project.name)
- .filter(
- (projectName) =>
- hasPath(
- dependencies,
- focusedProjectName,
- projectName,
- [],
- 1,
- searchDepth
- ) ||
- hasPath(
- dependencies,
- projectName,
- focusedProjectName,
- [],
- 1,
- searchDepth
- )
- );
-}
-
-export function filterProjectsByText(
- text: string,
- includeInPath: boolean,
- searchDepth: number,
- projects: ProjectGraphNode[],
- dependencies: Record
-) {
- const split = text.split(',').map((splitItem) => splitItem.trim());
-
- const selectedProjects = new Set();
-
- projects
- .map((project) => project.name)
- .forEach((project) => {
- const projectMatch =
- split.findIndex((splitItem) => project.includes(splitItem)) > -1;
-
- if (projectMatch) {
- selectedProjects.add(project);
-
- if (includeInPath) {
- projects
- .map((project) => project.name)
- .forEach((projectInPath) => {
- if (
- hasPath(
- dependencies,
- project,
- projectInPath,
- [],
- 1,
- searchDepth
- ) ||
- hasPath(
- dependencies,
- projectInPath,
- project,
- [],
- 1,
- searchDepth
- )
- ) {
- selectedProjects.add(projectInPath);
- }
- });
- }
- }
- });
-
- return Array.from(selectedProjects);
-}
diff --git a/dep-graph/dep-graph/src/index.html b/dep-graph/dep-graph/src/index.html
deleted file mode 100644
index a53bfeb3af..0000000000
--- a/dep-graph/dep-graph/src/index.html
+++ /dev/null
@@ -1,188 +0,0 @@
-
-
-
-
- Nx Workspace Dependency Graph
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Please select projects in the sidebar.
-
-
-
-
-
diff --git a/dep-graph/dep-graph/src/main.ts b/dep-graph/dep-graph/src/main.ts
deleted file mode 100644
index 5f66ff45a9..0000000000
--- a/dep-graph/dep-graph/src/main.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { AppComponent } from './app/app';
-import { LocalProjectGraphService } from './app/local-project-graph-service';
-import { inspect } from '@xstate/inspect';
-import { ProjectGraphService } from './app/models';
-import { MockProjectGraphService } from './app/mock-project-graph-service';
-import { FetchProjectGraphService } from './app/fetch-project-graph-service';
-
-if (window.useXstateInspect === true) {
- inspect({
- url: 'https://stately.ai/viz?inspect',
- iframe: false, // open in new window
- });
-}
-
-let projectGraphService: ProjectGraphService;
-
-if (window.environment === 'dev') {
- projectGraphService = new FetchProjectGraphService();
-} else if (window.environment === 'watch') {
- projectGraphService = new MockProjectGraphService();
-} else if (window.environment === 'release') {
- if (window.localMode === 'build') {
- projectGraphService = new LocalProjectGraphService();
- } else {
- projectGraphService = new FetchProjectGraphService();
- }
-}
-
-setTimeout(() => new AppComponent(window.appConfig, projectGraphService));
diff --git a/dep-graph/dep-graph/src/test-setup.ts b/dep-graph/dep-graph/src/test-setup.ts
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/dep-graph/dep-graph/tsconfig.app.json b/dep-graph/dep-graph/tsconfig.app.json
deleted file mode 100644
index 4206675676..0000000000
--- a/dep-graph/dep-graph/tsconfig.app.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../dist/out-tsc",
- "types": ["node"],
- "lib": ["DOM", "es2019"]
- },
- "exclude": ["**/*.spec.ts", "**/*.test.ts"],
- "include": ["**/*.ts"]
-}
diff --git a/dep-graph/dep-graph/tsconfig.spec.json b/dep-graph/dep-graph/tsconfig.spec.json
deleted file mode 100644
index 1b59ddf639..0000000000
--- a/dep-graph/dep-graph/tsconfig.spec.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "../../dist/out-tsc",
- "module": "commonjs",
- "types": ["jest", "node"],
- "lib": ["DOM"]
- },
- "files": ["src/test-setup.ts"],
- "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts"]
-}
diff --git a/nx.json b/nx.json
index 52ab9aed76..de7dcd1156 100644
--- a/nx.json
+++ b/nx.json
@@ -56,8 +56,18 @@
"generators": {
"@nrwl/react": {
"application": {
+ "style": "css",
+ "linter": "eslint",
"babel": true
+ },
+ "component": {
+ "style": "css"
+ },
+ "library": {
+ "style": "css",
+ "linter": "eslint"
}
}
- }
+ },
+ "defaultProject": "dep-graph-client"
}
diff --git a/package.json b/package.json
index 055897ee44..ef4fb03dfd 100644
--- a/package.json
+++ b/package.json
@@ -94,6 +94,7 @@
"@tailwindcss/typography": "^0.4.1",
"@testing-library/react": "11.2.6",
"@testing-library/react-hooks": "7.0.1",
+ "@tippyjs/react": "^4.2.6",
"@types/css-minimizer-webpack-plugin": "^3.0.2",
"@types/cytoscape": "^3.18.2",
"@types/eslint": "^8.2.0",
@@ -119,6 +120,7 @@
"@typescript-eslint/parser": "~5.3.0",
"@xstate/immer": "^0.2.0",
"@xstate/inspect": "^0.5.1",
+ "@xstate/react": "^1.6.3",
"angular": "1.8.0",
"autoprefixer": "^10.2.5",
"babel-jest": "27.2.3",
@@ -147,7 +149,7 @@
"eslint-plugin-import": "2.25.2",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.23.1",
- "eslint-plugin-react-hooks": "4.2.0",
+ "eslint-plugin-react-hooks": "4.3.0",
"express": "4.17.1",
"file-loader": "^6.2.0",
"file-type": "^16.2.0",
@@ -209,6 +211,7 @@
"react-redux": "7.2.3",
"react-refresh": "^0.10.0",
"react-router-dom": "5.1.2",
+ "react-test-renderer": "17.0.2",
"regenerator-runtime": "0.13.7",
"release-it": "^14.11.3",
"rollup": "^2.56.2",
@@ -233,7 +236,6 @@
"tcp-port-used": "^1.0.2",
"terser": "4.3.8",
"terser-webpack-plugin": "^5.1.1",
- "tippy.js": "^6.3.1",
"tmp": "~0.2.1",
"tree-kill": "1.2.2",
"ts-jest": "27.0.5",
diff --git a/packages/workspace/project.json b/packages/workspace/project.json
index 99ef71332a..fd01730956 100644
--- a/packages/workspace/project.json
+++ b/packages/workspace/project.json
@@ -2,7 +2,7 @@
"root": "packages/workspace",
"sourceRoot": "packages/workspace/src",
"projectType": "library",
- "implicitDependencies": ["dep-graph-dep-graph"],
+ "implicitDependencies": ["dep-graph-client"],
"targets": {
"test": {
"executor": "@nrwl/jest:jest",
diff --git a/scripts/copy-dep-graph-environment.ts b/scripts/copy-dep-graph-environment.ts
index b7722e208c..c457038f31 100644
--- a/scripts/copy-dep-graph-environment.ts
+++ b/scripts/copy-dep-graph-environment.ts
@@ -7,6 +7,6 @@ const mode = argv._[0];
console.log(`Setting up dep-graph for ${mode}`);
copyFileSync(
- `dep-graph/dep-graph/src/assets/environment.${mode}.js`,
- `dep-graph/dep-graph/src/assets/environment.js`
+ `dep-graph/client/src/assets/environment.${mode}.js`,
+ `dep-graph/client/src/assets/environment.js`
);
diff --git a/workspace.json b/workspace.json
index 8ddeed969e..4321172434 100644
--- a/workspace.json
+++ b/workspace.json
@@ -6,8 +6,8 @@
"create-nx-plugin": "packages/create-nx-plugin",
"create-nx-workspace": "packages/create-nx-workspace",
"cypress": "packages/cypress",
- "dep-graph-dep-graph": "dep-graph/dep-graph",
- "dep-graph-dep-graph-e2e": "dep-graph/dep-graph-e2e",
+ "dep-graph-client": "/dep-graph/client",
+ "dep-graph-client-e2e": "dep-graph/client-e2e",
"detox": "packages/detox",
"devkit": "packages/devkit",
"docs": "docs",
diff --git a/yarn.lock b/yarn.lock
index bde7c80643..14de427618 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3125,11 +3125,16 @@
schema-utils "^3.0.0"
source-map "^0.7.3"
-"@popperjs/core@^2.0.0", "@popperjs/core@^2.5.4", "@popperjs/core@^2.6.0", "@popperjs/core@^2.9.0", "@popperjs/core@^2.9.2":
+"@popperjs/core@^2.0.0", "@popperjs/core@^2.5.4", "@popperjs/core@^2.6.0", "@popperjs/core@^2.9.2":
version "2.10.2"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590"
integrity sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ==
+"@popperjs/core@^2.9.0":
+ version "2.11.0"
+ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.0.tgz#6734f8ebc106a0860dff7f92bf90df193f0935d7"
+ integrity sha512-zrsUxjLOKAzdewIDRWy9nsV1GQsKBCWaGwsZQlCgr6/q+vjyZhFgqedLfFBuI9anTPEUT4APq9Mu0SZBTzIcGQ==
+
"@reduxjs/toolkit@1.6.1":
version "1.6.1"
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-1.6.1.tgz#7bc83b47352a663bf28db01e79d17ba54b98ade9"
@@ -4298,6 +4303,13 @@
"@babel/runtime" "^7.12.5"
"@testing-library/dom" "^7.28.1"
+"@tippyjs/react@^4.2.6":
+ version "4.2.6"
+ resolved "https://registry.yarnpkg.com/@tippyjs/react/-/react-4.2.6.tgz#971677a599bf663f20bb1c60a62b9555b749cc71"
+ integrity sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==
+ dependencies:
+ tippy.js "^6.3.1"
+
"@tokenizer/token@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276"
@@ -4936,6 +4948,11 @@
dependencies:
"@types/node" "*"
+"@types/yoga-layout@1.9.2":
+ version "1.9.2"
+ resolved "https://registry.yarnpkg.com/@types/yoga-layout/-/yoga-layout-1.9.2.tgz#efaf9e991a7390dc081a0b679185979a83a9639a"
+ integrity sha512-S9q47ByT2pPvD65IvrWp7qppVMpk9WGMbVq9wbWZOHg6tnXSD4vyhao6nOSBwwfDdV2p3Kx9evA9vI+XWTfDvw==
+
"@typescript-eslint/eslint-plugin@~5.3.0":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.3.1.tgz#d8ff412f10f54f6364e7fd7c1e70eb6767f434c3"
@@ -5524,6 +5541,14 @@
dependencies:
fast-safe-stringify "^2.0.7"
+"@xstate/react@^1.6.3":
+ version "1.6.3"
+ resolved "https://registry.yarnpkg.com/@xstate/react/-/react-1.6.3.tgz#706f3beb7bc5879a78088985c8fd43b9dab7f725"
+ integrity sha512-NCUReRHPGvvCvj2yLZUTfR0qVp6+apc8G83oXSjN4rl89ZjyujiKrTff55bze/HrsvCsP/sUJASf2n0nzMF1KQ==
+ dependencies:
+ use-isomorphic-layout-effect "^1.0.0"
+ use-subscription "^1.3.0"
+
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@@ -6256,6 +6281,11 @@ atomic-sleep@^1.0.0:
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
+auto-bind@4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-4.0.0.tgz#e3589fc6c2da8f7ca43ba9f84fa52a744fc997fb"
+ integrity sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==
+
autolinker@~0.15.0:
version "0.15.3"
resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.15.3.tgz#342417d8f2f3461b14cf09088d5edf8791dc9832"
@@ -7493,7 +7523,7 @@ clean-stack@^2.0.0:
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
-cli-boxes@^2.2.1:
+cli-boxes@^2.2.0, cli-boxes@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f"
integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==
@@ -7517,7 +7547,7 @@ cli-spinners@^1.0.1:
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.3.1.tgz#002c1990912d0d59580c93bd36c056de99e4259a"
integrity sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==
-cli-spinners@^2.5.0:
+cli-spinners@^2.3.0, cli-spinners@^2.5.0:
version "2.6.1"
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d"
integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==
@@ -7620,6 +7650,13 @@ coa@^2.0.2:
chalk "^2.4.1"
q "^1.1.2"
+code-excerpt@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/code-excerpt/-/code-excerpt-3.0.0.tgz#fcfb6748c03dba8431c19f5474747fad3f250f10"
+ integrity sha512-VHNTVhd7KsLGOqfX3SyeO8RyYPMp1GJOg194VITk04WMYCv4plV68YWe6TJZxd9MhobjtpMRnVky01gqZsalaw==
+ dependencies:
+ convert-to-spaces "^1.0.1"
+
code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
@@ -8128,6 +8165,11 @@ convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1,
dependencies:
safe-buffer "~5.1.1"
+convert-to-spaces@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/convert-to-spaces/-/convert-to-spaces-1.0.2.tgz#7e3e48bbe6d997b1417ddca2868204b4d3d85715"
+ integrity sha1-fj5Iu+bZl7FBfdyihoIEtNPYVxU=
+
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
@@ -10425,12 +10467,7 @@ eslint-plugin-jsx-a11y@^6.4.1, eslint-plugin-jsx-a11y@^6.5.1:
language-tags "^1.0.5"
minimatch "^3.0.4"
-eslint-plugin-react-hooks@4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz#8c229c268d468956334c943bb45fc860280f5556"
- integrity sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==
-
-eslint-plugin-react-hooks@^4.2.0, eslint-plugin-react-hooks@^4.3.0:
+eslint-plugin-react-hooks@4.3.0, eslint-plugin-react-hooks@^4.2.0, eslint-plugin-react-hooks@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz#318dbf312e06fab1c835a4abef00121751ac1172"
integrity sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==
@@ -12852,6 +12889,42 @@ injection-js@^2.4.0:
dependencies:
tslib "^2.0.0"
+ink-spinner@^4.0.3:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/ink-spinner/-/ink-spinner-4.0.3.tgz#0d0f4a787ae1a4270928e063d9c52527cb264feb"
+ integrity sha512-uJ4nbH00MM9fjTJ5xdw0zzvtXMkeGb0WV6dzSWvFv2/+ks6FIhpkt+Ge/eLdh0Ah6Vjw5pLMyNfoHQpRDRVFbQ==
+ dependencies:
+ cli-spinners "^2.3.0"
+
+ink@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/ink/-/ink-3.2.0.tgz#434793630dc57d611c8fe8fffa1db6b56f1a16bb"
+ integrity sha512-firNp1q3xxTzoItj/eOOSZQnYSlyrWks5llCTVX37nJ59K3eXbQ8PtzCguqo8YI19EELo5QxaKnJd4VxzhU8tg==
+ dependencies:
+ ansi-escapes "^4.2.1"
+ auto-bind "4.0.0"
+ chalk "^4.1.0"
+ cli-boxes "^2.2.0"
+ cli-cursor "^3.1.0"
+ cli-truncate "^2.1.0"
+ code-excerpt "^3.0.0"
+ indent-string "^4.0.0"
+ is-ci "^2.0.0"
+ lodash "^4.17.20"
+ patch-console "^1.0.0"
+ react-devtools-core "^4.19.1"
+ react-reconciler "^0.26.2"
+ scheduler "^0.20.2"
+ signal-exit "^3.0.2"
+ slice-ansi "^3.0.0"
+ stack-utils "^2.0.2"
+ string-width "^4.2.2"
+ type-fest "^0.12.0"
+ widest-line "^3.1.0"
+ wrap-ansi "^6.2.0"
+ ws "^7.5.5"
+ yoga-layout-prebuilt "^1.9.6"
+
inline-style-parser@0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
@@ -17312,6 +17385,11 @@ pascalcase@^0.1.1:
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
+patch-console@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/patch-console/-/patch-console-1.0.0.tgz#19b9f028713feb8a3c023702a8cc8cb9f7466f9d"
+ integrity sha512-nxl9nrnLQmh64iTzMfyylSlRozL7kAXIaxw1fVcLYdyhNkJCRUzirRZTikXGJsg+hc4fqpneTK6iU2H1Q8THSA==
+
path-browserify@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
@@ -18820,6 +18898,14 @@ react-dev-utils@^11.0.4:
strip-ansi "6.0.0"
text-table "0.2.0"
+react-devtools-core@^4.19.1:
+ version "4.22.1"
+ resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-4.22.1.tgz#b276d42f860bedc373c9b3c0f5f96734318dd453"
+ integrity sha512-pvpNDHE7p0FtcCmIWGazoY8LLVfBI9sw0Kf10kdHhPI9Tzt3OG/qEt16GrAbE0keuna5WzX3r1qPKVjqOqsuUg==
+ dependencies:
+ shell-quote "^1.6.1"
+ ws "^7"
+
react-docgen-typescript@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-2.1.1.tgz#c9f9ccb1fa67e0f4caf3b12f2a07512a201c2dcf"
@@ -18926,7 +19012,7 @@ react-intersection-observer@^8.32.2:
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-8.32.2.tgz#527eeecf569309d64ed96330636d90aac336c957"
integrity sha512-QTcea+n28AvOHbTku+jErfQqknbc4Nuh7EUNik8p/JMN56W2Jhjs+qcYZzQhAoyLX8pZD0QXpYX0lW87faackQ==
-react-is@17.0.2, react-is@^17.0.0, react-is@^17.0.1, react-is@^17.0.2:
+react-is@17.0.2, "react-is@^16.12.0 || ^17.0.0", react-is@^17.0.0, react-is@^17.0.1, react-is@^17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
@@ -18977,6 +19063,15 @@ react-popper@^2.2.4:
react-fast-compare "^3.0.1"
warning "^4.0.2"
+react-reconciler@^0.26.2:
+ version "0.26.2"
+ resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.26.2.tgz#bbad0e2d1309423f76cf3c3309ac6c96e05e9d91"
+ integrity sha512-nK6kgY28HwrMNwDnMui3dvm3rCFjZrcGiuwLc5COUipBK5hWHLOxMJhSnSomirqWwjPBJKV1QcbkI0VJr7Gl1Q==
+ dependencies:
+ loose-envify "^1.1.0"
+ object-assign "^4.1.1"
+ scheduler "^0.20.2"
+
react-redux@7.2.3:
version "7.2.3"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.3.tgz#4c084618600bb199012687da9e42123cca3f0be9"
@@ -19057,6 +19152,14 @@ react-select@^3.2.0:
react-input-autosize "^3.0.0"
react-transition-group "^4.3.0"
+react-shallow-renderer@^16.13.1:
+ version "16.14.1"
+ resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz#bf0d02df8a519a558fd9b8215442efa5c840e124"
+ integrity sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==
+ dependencies:
+ object-assign "^4.1.1"
+ react-is "^16.12.0 || ^17.0.0"
+
react-sizeme@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-3.0.2.tgz#4a2f167905ba8f8b8d932a9e35164e459f9020e4"
@@ -19089,6 +19192,16 @@ react-syntax-highlighter@^15.4.3:
prismjs "^1.25.0"
refractor "^3.2.0"
+react-test-renderer@17.0.2:
+ version "17.0.2"
+ resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c"
+ integrity sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==
+ dependencies:
+ object-assign "^4.1.1"
+ react-is "^17.0.2"
+ react-shallow-renderer "^16.13.1"
+ scheduler "^0.20.2"
+
react-textarea-autosize@^8.3.0:
version "8.3.3"
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8"
@@ -20830,7 +20943,7 @@ stable@^0.1.8:
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
-stack-utils@^2.0.3:
+stack-utils@^2.0.2, stack-utils@^2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5"
integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==
@@ -22050,6 +22163,11 @@ type-detect@4.0.8:
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
+type-fest@^0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.12.0.tgz#f57a27ab81c68d136a51fd71467eff94157fa1ee"
+ integrity sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==
+
type-fest@^0.18.0:
version "0.18.1"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f"
@@ -22506,7 +22624,7 @@ use-latest@^1.0.0:
dependencies:
use-isomorphic-layout-effect "^1.0.0"
-use-subscription@1.5.1:
+use-subscription@1.5.1, use-subscription@^1.3.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1"
integrity sha512-Xv2a1P/yReAjAbhylMfFplFKj9GssgTwN7RlcTxBujFQcloStWNDQdc4g4NRWH9xS4i/FDk04vQBptAXoF3VcA==
@@ -23350,6 +23468,11 @@ write-file-atomic@^3.0.0:
signal-exit "^3.0.2"
typedarray-to-buffer "^3.1.5"
+ws@^7, ws@^7.5.5:
+ version "7.5.6"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
+ integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
+
ws@^7.0.0, ws@^7.4.6:
version "7.5.5"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881"
@@ -23533,6 +23656,13 @@ yocto-queue@^0.1.0:
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
+yoga-layout-prebuilt@^1.9.6:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/yoga-layout-prebuilt/-/yoga-layout-prebuilt-1.10.0.tgz#2936fbaf4b3628ee0b3e3b1df44936d6c146faa6"
+ integrity sha512-YnOmtSbv4MTf7RGJMK0FvZ+KD8OEe/J5BNnR0GHhD8J/XcG/Qvxgszm0Un6FTHWW4uHlTgP0IztiXQnGyIR45g==
+ dependencies:
+ "@types/yoga-layout" "1.9.2"
+
zone.js@~0.11.4:
version "0.11.4"
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.11.4.tgz#0f70dcf6aba80f698af5735cbb257969396e8025"