feat(graph): add target groups and technology icon (#22839)
This commit is contained in:
parent
addde70251
commit
aa82f031c3
@ -10,7 +10,7 @@ export default {
|
||||
'^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/nx-dev/nx-dev',
|
||||
coverageDirectory: '../../coverage/graph/client',
|
||||
// The mock for widnow.matchMedia has to be in a separete file and imported before the components to test
|
||||
// for more info check : // https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
|
||||
modulePathIgnorePatterns: [
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { themeInit } from '@nx/graph/ui-theme';
|
||||
import { rootStore } from '@nx/graph/state';
|
||||
import { Provider as StoreProvider } from 'react-redux';
|
||||
import { rankDirInit } from './rankdir-resolver';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import { getRouter } from './get-router';
|
||||
@ -7,5 +9,9 @@ themeInit();
|
||||
rankDirInit();
|
||||
|
||||
export function App() {
|
||||
return <RouterProvider router={getRouter()} />;
|
||||
return (
|
||||
<StoreProvider store={rootStore}>
|
||||
<RouterProvider router={getRouter()} />
|
||||
</StoreProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
} from '@heroicons/react/24/outline';
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import type { ProjectGraphNode } from '@nx/devkit';
|
||||
import type { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
/* eslint-enable @nx/enforce-module-boundaries */
|
||||
import { useProjectGraphSelector } from './hooks/use-project-graph-selector';
|
||||
import {
|
||||
@ -23,7 +23,7 @@ import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useRouteConstructor } from '@nx/graph/shared';
|
||||
|
||||
interface SidebarProject {
|
||||
projectGraphNode: ProjectGraphNode;
|
||||
projectGraphNode: ProjectGraphProjectNode;
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
||||
@ -36,7 +36,7 @@ interface TracingInfo {
|
||||
}
|
||||
|
||||
function groupProjectsByDirectory(
|
||||
projects: ProjectGraphNode[],
|
||||
projects: ProjectGraphProjectNode[],
|
||||
selectedProjects: string[],
|
||||
workspaceLayout: { appsDir: string; libsDir: string }
|
||||
): DirectoryProjectRecord {
|
||||
|
||||
@ -3,7 +3,7 @@ import { ProjectDetailsHeader } from 'graph/project-details/src/lib/project-deta
|
||||
import { useRouteError } from 'react-router-dom';
|
||||
|
||||
export function ErrorBoundary() {
|
||||
let error = useRouteError()?.toString();
|
||||
let error = useRouteError();
|
||||
console.error(error);
|
||||
const environment = useEnvironmentConfig()?.environment;
|
||||
|
||||
@ -20,7 +20,7 @@ export function ErrorBoundary() {
|
||||
<h1 className="mb-4 text-4xl dark:text-slate-100">Error</h1>
|
||||
<div>
|
||||
<p className="mb-4 text-lg dark:text-slate-200">{message}</p>
|
||||
<p className="text-sm">Error message: {error}</p>
|
||||
<p className="text-sm">Error message: {error?.toString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import { ProjectGraphDependency, ProjectGraphProjectNode } from '@nx/devkit';
|
||||
import { getEnvironmentConfig } from '@nx/graph/shared';
|
||||
import type {
|
||||
ProjectGraphDependency,
|
||||
ProjectGraphProjectNode,
|
||||
} from '@nx/devkit';
|
||||
/* eslint-enable @nx/enforce-module-boundaries */
|
||||
|
||||
export function parseParentDirectoriesFromFilePath(
|
||||
|
||||
@ -1189,27 +1189,111 @@
|
||||
}
|
||||
],
|
||||
"targets": {
|
||||
"e2e": {
|
||||
"executor": "@nrwl/cypress:cypress",
|
||||
"options": {
|
||||
"cypressConfig": "apps/cart-e2e/cypress.config.ts",
|
||||
"devServerTarget": "cart:serve",
|
||||
"testingType": "e2e"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "cart:serve:production"
|
||||
}
|
||||
},
|
||||
"inputs": ["default", "^production"]
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"cache": true,
|
||||
"options": {
|
||||
"lintFilePatterns": ["apps/cart-e2e/**/*.{ts,tsx,js,jsx}"]
|
||||
"cwd": "apps/cart-e2e",
|
||||
"command": "eslint ."
|
||||
},
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"]
|
||||
"inputs": [
|
||||
"default",
|
||||
"^default",
|
||||
"{workspaceRoot}/.eslintrc.json",
|
||||
"{projectRoot}/.eslintrc.json",
|
||||
"{workspaceRoot}/tools/eslint-rules/**/*",
|
||||
{
|
||||
"externalDependencies": ["eslint"]
|
||||
}
|
||||
],
|
||||
"executor": "nx:run-commands",
|
||||
"configurations": {}
|
||||
},
|
||||
"e2e": {
|
||||
"cache": true,
|
||||
"inputs": ["default", "^production"],
|
||||
"outputs": [
|
||||
"{workspaceRoot}/dist/cypress/apps/cart-e2e/videos",
|
||||
"{workspaceRoot}/dist/cypress/apps/cart-e2e/screenshots"
|
||||
],
|
||||
"metadata": {
|
||||
"technologies": ["cypress"],
|
||||
"description": "Runs Cypress Tests"
|
||||
},
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"cwd": "apps/cart-e2e",
|
||||
"command": "cypress run"
|
||||
},
|
||||
"configurations": {}
|
||||
},
|
||||
"e2e-ci--src/e2e/app.cy.ts": {
|
||||
"outputs": [
|
||||
"{workspaceRoot}/dist/cypress/apps/cart-e2e/videos",
|
||||
"{workspaceRoot}/dist/cypress/apps/cart-e2e/screenshots"
|
||||
],
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": ["cypress"]
|
||||
}
|
||||
],
|
||||
"cache": true,
|
||||
"options": {
|
||||
"cwd": "apps/cart-e2e",
|
||||
"command": "cypress run --env webServerCommand=\"nx run cart:serve\" --spec src/e2e/app.cy.ts"
|
||||
},
|
||||
"metadata": {
|
||||
"technologies": ["cypress"],
|
||||
"description": "Runs Cypress Tests in src/e2e/app.cy.ts in CI"
|
||||
},
|
||||
"executor": "nx:run-commands",
|
||||
"configurations": {}
|
||||
},
|
||||
"e2e-ci": {
|
||||
"executor": "nx:noop",
|
||||
"cache": true,
|
||||
"inputs": [
|
||||
"default",
|
||||
"^production",
|
||||
{
|
||||
"externalDependencies": ["cypress"]
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
"{workspaceRoot}/dist/cypress/apps/cart-e2e/videos",
|
||||
"{workspaceRoot}/dist/cypress/apps/cart-e2e/screenshots"
|
||||
],
|
||||
"dependsOn": [
|
||||
{
|
||||
"target": "e2e-ci--src/e2e/app.cy.ts",
|
||||
"projects": "self",
|
||||
"params": "forward"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"technologies": ["cypress"],
|
||||
"description": "Runs Cypress Tests in CI"
|
||||
},
|
||||
"options": {},
|
||||
"configurations": {}
|
||||
},
|
||||
"open-cypress": {
|
||||
"options": {
|
||||
"cwd": "apps/cart-e2e",
|
||||
"command": "cypress open"
|
||||
},
|
||||
"metadata": {
|
||||
"technologies": ["cypress"],
|
||||
"description": "Opens Cypress"
|
||||
},
|
||||
"executor": "nx:run-commands",
|
||||
"configurations": {}
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"targetGroups": {
|
||||
"E2E (CI)": ["e2e-ci--src/e2e/app.cy.ts", "e2e-ci"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,3 +44,13 @@
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Dark mode */
|
||||
html.dark .adaptive-icon {
|
||||
/* fill: white; */
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.adaptive-icon {
|
||||
fill: black;
|
||||
}
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
import type { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
import {
|
||||
ScrollRestoration,
|
||||
useParams,
|
||||
useRouteLoaderData,
|
||||
} from 'react-router-dom';
|
||||
import { ProjectDetailsWrapper } from './project-details-wrapper';
|
||||
import ProjectDetailsWrapper from './project-details-wrapper';
|
||||
import {
|
||||
fetchProjectGraph,
|
||||
getProjectGraphDataService,
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
import {
|
||||
AppDispatch,
|
||||
RootState,
|
||||
expandTargetActions,
|
||||
getExpandedTargets,
|
||||
} from '@nx/graph/state';
|
||||
|
||||
const mapStateToProps = (state: RootState) => {
|
||||
return {
|
||||
expandTargets: getExpandedTargets(state),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch: AppDispatch) => {
|
||||
return {
|
||||
setExpandTargets(targets: string[]) {
|
||||
dispatch(expandTargetActions.setExpandTargets(targets));
|
||||
},
|
||||
collapseAllTargets() {
|
||||
dispatch(expandTargetActions.collapseAllTargets());
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type mapStateToPropsType = ReturnType<typeof mapStateToProps>;
|
||||
type mapDispatchToPropsType = ReturnType<typeof mapDispatchToProps>;
|
||||
|
||||
export {
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
mapStateToPropsType,
|
||||
mapDispatchToPropsType,
|
||||
};
|
||||
@ -1,10 +1,8 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
import { useNavigate, useNavigation, useSearchParams } from 'react-router-dom';
|
||||
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
import type { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
import { useNavigate, useNavigation, useSearchParams } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
getExternalApiService,
|
||||
useEnvironmentConfig,
|
||||
@ -12,19 +10,28 @@ import {
|
||||
} from '@nx/graph/shared';
|
||||
import { Spinner } from '@nx/graph/ui-components';
|
||||
|
||||
import { ProjectDetails } from '@nx/graph/ui-project-details';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import {
|
||||
ProjectDetails,
|
||||
ProjectDetailsImperativeHandle,
|
||||
} from '@nx/graph/ui-project-details';
|
||||
import { useCallback, useLayoutEffect, useRef } from 'react';
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
mapStateToPropsType,
|
||||
mapDispatchToPropsType,
|
||||
} from './project-details-wrapper.state';
|
||||
|
||||
export interface ProjectDetailsProps {
|
||||
project: ProjectGraphProjectNode;
|
||||
sourceMap: Record<string, string[]>;
|
||||
}
|
||||
type ProjectDetailsProps = mapStateToPropsType &
|
||||
mapDispatchToPropsType & {
|
||||
project: ProjectGraphProjectNode;
|
||||
sourceMap: Record<string, string[]>;
|
||||
};
|
||||
|
||||
export function ProjectDetailsWrapper(props: ProjectDetailsProps) {
|
||||
const projectDetailsRef = useRef<ProjectDetailsImperativeHandle>(null);
|
||||
export function ProjectDetailsWrapperComponent({
|
||||
project,
|
||||
sourceMap,
|
||||
setExpandTargets,
|
||||
expandTargets,
|
||||
collapseAllTargets,
|
||||
}: ProjectDetailsProps) {
|
||||
const environment = useEnvironmentConfig()?.environment;
|
||||
const externalApiService = getExternalApiService();
|
||||
const navigate = useNavigate();
|
||||
@ -88,63 +95,56 @@ export function ProjectDetailsWrapper(props: ProjectDetailsProps) {
|
||||
[externalApiService]
|
||||
);
|
||||
|
||||
const updateSearchParams = (params: URLSearchParams, sections: string[]) => {
|
||||
if (sections.length === 0) {
|
||||
const updateSearchParams = (
|
||||
params: URLSearchParams,
|
||||
targetNames: string[]
|
||||
) => {
|
||||
if (targetNames.length === 0) {
|
||||
params.delete('expanded');
|
||||
} else {
|
||||
params.set('expanded', sections.join(','));
|
||||
params.set('expanded', targetNames.join(','));
|
||||
}
|
||||
};
|
||||
|
||||
const handleTargetCollapse = useCallback(
|
||||
(targetName: string) => {
|
||||
const expandedSections = searchParams.get('expanded')?.split(',') || [];
|
||||
if (!expandedSections.includes(targetName)) return;
|
||||
const newExpandedSections = expandedSections.filter(
|
||||
(section) => section !== targetName
|
||||
);
|
||||
setSearchParams(
|
||||
(currentSearchParams) => {
|
||||
updateSearchParams(currentSearchParams, newExpandedSections);
|
||||
return currentSearchParams;
|
||||
},
|
||||
{
|
||||
replace: true,
|
||||
preventScrollReset: true,
|
||||
}
|
||||
);
|
||||
},
|
||||
[setSearchParams, searchParams]
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!project.data.targets) return;
|
||||
|
||||
const handleTargetExpand = useCallback(
|
||||
(targetName: string) => {
|
||||
const expandedSections = searchParams.get('expanded')?.split(',') || [];
|
||||
if (expandedSections.includes(targetName)) return;
|
||||
expandedSections.push(targetName);
|
||||
setSearchParams(
|
||||
(currentSearchParams) => {
|
||||
updateSearchParams(currentSearchParams, expandedSections);
|
||||
return currentSearchParams;
|
||||
},
|
||||
{ replace: true, preventScrollReset: true }
|
||||
);
|
||||
},
|
||||
[setSearchParams, searchParams]
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!props.project.data.targets) return;
|
||||
|
||||
const expandedSections = searchParams.get('expanded')?.split(',') || [];
|
||||
for (const targetName of Object.keys(props.project.data.targets)) {
|
||||
if (expandedSections.includes(targetName)) {
|
||||
projectDetailsRef.current?.expandTarget(targetName);
|
||||
} else {
|
||||
projectDetailsRef.current?.collapseTarget(targetName);
|
||||
}
|
||||
const expandedTargetsParams = searchParams.get('expanded')?.split(',');
|
||||
if (expandedTargetsParams && expandedTargetsParams.length > 0) {
|
||||
setExpandTargets(expandedTargetsParams);
|
||||
}
|
||||
}, [searchParams, props.project.data.targets, projectDetailsRef]);
|
||||
|
||||
return () => {
|
||||
collapseAllTargets();
|
||||
searchParams.delete('expanded');
|
||||
setSearchParams(searchParams, { replace: true });
|
||||
};
|
||||
}, []); // only run on mount
|
||||
|
||||
useEffect(() => {
|
||||
if (!project.data.targets) return;
|
||||
|
||||
const expandedTargetsParams =
|
||||
searchParams.get('expanded')?.split(',') || [];
|
||||
|
||||
if (expandedTargetsParams.join(',') === expandTargets.join(',')) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSearchParams(
|
||||
(currentSearchParams) => {
|
||||
updateSearchParams(currentSearchParams, expandTargets);
|
||||
return currentSearchParams;
|
||||
},
|
||||
{ replace: true, preventScrollReset: true }
|
||||
);
|
||||
}, [
|
||||
expandTargets,
|
||||
project.data.targets,
|
||||
setExpandTargets,
|
||||
searchParams,
|
||||
setSearchParams,
|
||||
]);
|
||||
|
||||
if (
|
||||
navigationState === 'loading' &&
|
||||
@ -159,10 +159,8 @@ export function ProjectDetailsWrapper(props: ProjectDetailsProps) {
|
||||
|
||||
return (
|
||||
<ProjectDetails
|
||||
ref={projectDetailsRef}
|
||||
{...props}
|
||||
onTargetCollapse={handleTargetCollapse}
|
||||
onTargetExpand={handleTargetExpand}
|
||||
project={project}
|
||||
sourceMap={sourceMap}
|
||||
onViewInProjectGraph={handleViewInProjectGraph}
|
||||
onViewInTaskGraph={handleViewInTaskGraph}
|
||||
onRunTarget={environment === 'nx-console' ? handleRunTarget : undefined}
|
||||
@ -170,4 +168,8 @@ export function ProjectDetailsWrapper(props: ProjectDetailsProps) {
|
||||
);
|
||||
}
|
||||
|
||||
export const ProjectDetailsWrapper = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ProjectDetailsWrapperComponent);
|
||||
export default ProjectDetailsWrapper;
|
||||
|
||||
12
graph/state/.babelrc
Normal file
12
graph/state/.babelrc
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
18
graph/state/.eslintrc.json
Normal file
18
graph/state/.eslintrc.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
11
graph/state/project.json
Normal file
11
graph/state/project.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "graph-state",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "graph/state/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"// targets": "to see all targets run: nx show project ui-icons --web",
|
||||
"targets": {
|
||||
"lint": {}
|
||||
}
|
||||
}
|
||||
6
graph/state/src/index.ts
Normal file
6
graph/state/src/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export * from './lib/expand-targets/expand-targets.slice';
|
||||
export * from './lib/root/root-state.initial';
|
||||
export * from './lib/root/root-state.interface';
|
||||
export * from './lib/root/root.reducer';
|
||||
export * from './lib/root/root.store';
|
||||
export * from './lib/store.decorator';
|
||||
54
graph/state/src/lib/expand-targets/expand-targets.slice.ts
Normal file
54
graph/state/src/lib/expand-targets/expand-targets.slice.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
export const EXPAND_TARGETS_KEY = 'expandTargets';
|
||||
|
||||
export const initialExpandTargets: string[] = [];
|
||||
|
||||
export const expandTargetSlice = createSlice({
|
||||
name: EXPAND_TARGETS_KEY,
|
||||
initialState: initialExpandTargets,
|
||||
reducers: {
|
||||
expandTarget: (state: string[], action: PayloadAction<string>) => {
|
||||
if (state.includes(action.payload)) {
|
||||
return state;
|
||||
}
|
||||
state.push(action.payload);
|
||||
return state;
|
||||
},
|
||||
collapseTarget: (state: string[], action: PayloadAction<string>) => {
|
||||
if (state.includes(action.payload)) {
|
||||
state = state.filter((target) => target !== action.payload);
|
||||
}
|
||||
return state;
|
||||
},
|
||||
toggleExpandTarget: (state: string[], action: PayloadAction<string>) => {
|
||||
if (state.includes(action.payload)) {
|
||||
state = state.filter((target) => target !== action.payload);
|
||||
} else {
|
||||
state.push(action.payload);
|
||||
}
|
||||
return state;
|
||||
},
|
||||
setExpandTargets: (state: string[], action: PayloadAction<string[]>) => {
|
||||
state = action.payload;
|
||||
return state;
|
||||
},
|
||||
collapseAllTargets: (state: string[]) => {
|
||||
state = [];
|
||||
return state;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* Export reducer for store configuration.
|
||||
*/
|
||||
export const expandTargetReducer = expandTargetSlice.reducer;
|
||||
|
||||
export const expandTargetActions = expandTargetSlice.actions;
|
||||
|
||||
export const getExpandedTargets = <
|
||||
ROOT extends { [EXPAND_TARGETS_KEY]: string[] }
|
||||
>(
|
||||
rootState: ROOT
|
||||
): string[] => rootState[EXPAND_TARGETS_KEY];
|
||||
9
graph/state/src/lib/root/root-state.initial.ts
Normal file
9
graph/state/src/lib/root/root-state.initial.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import {
|
||||
EXPAND_TARGETS_KEY,
|
||||
initialExpandTargets,
|
||||
} from '../expand-targets/expand-targets.slice';
|
||||
import { RootState } from './root-state.interface';
|
||||
|
||||
export const initialRootState: RootState = {
|
||||
[EXPAND_TARGETS_KEY]: initialExpandTargets,
|
||||
};
|
||||
5
graph/state/src/lib/root/root-state.interface.ts
Normal file
5
graph/state/src/lib/root/root-state.interface.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { EXPAND_TARGETS_KEY } from '../expand-targets/expand-targets.slice';
|
||||
|
||||
export interface RootState {
|
||||
[EXPAND_TARGETS_KEY]: string[];
|
||||
}
|
||||
10
graph/state/src/lib/root/root.reducer.ts
Normal file
10
graph/state/src/lib/root/root.reducer.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { combineReducers } from '@reduxjs/toolkit';
|
||||
import {
|
||||
EXPAND_TARGETS_KEY,
|
||||
expandTargetReducer,
|
||||
} from '../expand-targets/expand-targets.slice';
|
||||
import { RootState } from './root-state.interface';
|
||||
|
||||
export const rootReducer = combineReducers<RootState>({
|
||||
[EXPAND_TARGETS_KEY]: expandTargetReducer,
|
||||
});
|
||||
19
graph/state/src/lib/root/root.store.ts
Normal file
19
graph/state/src/lib/root/root.store.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { initialRootState } from './root-state.initial';
|
||||
import { rootReducer } from './root.reducer';
|
||||
|
||||
declare const process: any;
|
||||
|
||||
export const rootStore = configureStore({
|
||||
reducer: rootReducer,
|
||||
middleware: (getDefaultMiddleware) => {
|
||||
const defaultMiddleware = getDefaultMiddleware({
|
||||
serializableCheck: false,
|
||||
});
|
||||
return defaultMiddleware;
|
||||
},
|
||||
devTools: process.env.NODE_ENV === 'development',
|
||||
preloadedState: initialRootState,
|
||||
});
|
||||
|
||||
export type AppDispatch = typeof rootStore.dispatch;
|
||||
7
graph/state/src/lib/store.decorator.tsx
Normal file
7
graph/state/src/lib/store.decorator.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { rootStore } from './root/root.store';
|
||||
|
||||
export const StoreDecorator = (story: any) => {
|
||||
return <Provider store={rootStore}>{story()}</Provider>;
|
||||
};
|
||||
17
graph/state/tsconfig.json
Normal file
17
graph/state/tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../tsconfig.base.json"
|
||||
}
|
||||
27
graph/state/tsconfig.lib.json
Normal file
27
graph/state/tsconfig.lib.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": [
|
||||
"node",
|
||||
"@nx/react/typings/cssmodule.d.ts",
|
||||
"@nx/react/typings/image.d.ts"
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.test.jsx",
|
||||
"**/*.stories.ts",
|
||||
"**/*.stories.js",
|
||||
"**/*.stories.jsx",
|
||||
"**/*.stories.tsx"
|
||||
],
|
||||
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
|
||||
}
|
||||
12
graph/ui-icons/.babelrc
Normal file
12
graph/ui-icons/.babelrc
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
18
graph/ui-icons/.eslintrc.json
Normal file
18
graph/ui-icons/.eslintrc.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*", "storybook-static"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
21
graph/ui-icons/.storybook/main.ts
Normal file
21
graph/ui-icons/.storybook/main.ts
Normal file
@ -0,0 +1,21 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
import { mergeConfig } from 'vite';
|
||||
// nx-ignore-next-line
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/lib/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||
addons: ['@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {},
|
||||
},
|
||||
|
||||
viteFinal: async (config) =>
|
||||
mergeConfig(config, {
|
||||
plugins: [nxViteTsPaths()],
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
1
graph/ui-icons/.storybook/preview.ts
Normal file
1
graph/ui-icons/.storybook/preview.ts
Normal file
@ -0,0 +1 @@
|
||||
import './tailwind.css';
|
||||
3
graph/ui-icons/.storybook/tailwind.css
Normal file
3
graph/ui-icons/.storybook/tailwind.css
Normal file
@ -0,0 +1,3 @@
|
||||
@tailwind components;
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
7
graph/ui-icons/README.md
Normal file
7
graph/ui-icons/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# ui-icons
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test ui-icons` to execute the unit tests via [Jest](https://jestjs.io).
|
||||
10
graph/ui-icons/postcss.config.js
Normal file
10
graph/ui-icons/postcss.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
config: path.join(__dirname, 'tailwind.config.js'),
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
54
graph/ui-icons/project.json
Normal file
54
graph/ui-icons/project.json
Normal file
@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "graph-ui-icons",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "graph/ui-icons/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"// targets": "to see all targets run: nx show project ui-icons --web",
|
||||
"targets": {
|
||||
"lint": {},
|
||||
"storybook": {
|
||||
"executor": "@nx/storybook:storybook",
|
||||
"options": {
|
||||
"port": 4400,
|
||||
"configDir": "graph/ui-icons/.storybook"
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"quiet": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"build-storybook": {
|
||||
"executor": "@nx/storybook:build",
|
||||
"outputs": ["{options.outputDir}"],
|
||||
"options": {
|
||||
"outputDir": "dist/storybook/graph-ui-icons",
|
||||
"configDir": "graph/ui-icons/.storybook"
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"quiet": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"test-storybook": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "test-storybook -c graph/ui-icons/.storybook --url=http://localhost:4400"
|
||||
}
|
||||
},
|
||||
"static-storybook": {
|
||||
"executor": "@nx/web:file-server",
|
||||
"options": {
|
||||
"buildTarget": "graph-ui-icons:build-storybook",
|
||||
"staticFilePath": "dist/storybook/graph-ui-icons"
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"buildTarget": "graph-ui-icons:build-storybook:ci"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
graph/ui-icons/src/index.ts
Normal file
2
graph/ui-icons/src/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './lib/technology-icon';
|
||||
export * from './lib/framework-icons';
|
||||
24
graph/ui-icons/src/lib/framework-icons.stories.tsx
Normal file
24
graph/ui-icons/src/lib/framework-icons.stories.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Framework, frameworkIcons } from './framework-icons';
|
||||
|
||||
const meta: Meta<typeof frameworkIcons> = {
|
||||
component: () => (
|
||||
<>
|
||||
{Object.keys(frameworkIcons).map((key) => (
|
||||
<>
|
||||
<div>{key}</div>
|
||||
<div className="h-10 w-10">
|
||||
{frameworkIcons[key as Framework].image}
|
||||
</div>
|
||||
</>
|
||||
))}
|
||||
</>
|
||||
),
|
||||
title: 'frameworkIcons',
|
||||
};
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof frameworkIcons>;
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {},
|
||||
};
|
||||
@ -1,12 +1,74 @@
|
||||
export type Framework =
|
||||
| 'reactMono'
|
||||
| 'tsMono'
|
||||
| 'jsMono'
|
||||
| 'nodeMono'
|
||||
| 'angularMono'
|
||||
| 'typescript'
|
||||
| 'javascript'
|
||||
| 'node'
|
||||
| 'angular'
|
||||
| 'youtube'
|
||||
| 'nxagents'
|
||||
| 'nxcloud'
|
||||
| 'nx'
|
||||
| 'nextjs'
|
||||
| 'nestjs'
|
||||
| 'rspack'
|
||||
| 'express'
|
||||
| 'jest'
|
||||
| 'fastify'
|
||||
| 'storybook'
|
||||
| 'solid'
|
||||
| 'lit'
|
||||
| 'vite'
|
||||
| 'trpc'
|
||||
| 'remix'
|
||||
| 'dotnet'
|
||||
| 'qwik'
|
||||
| 'gradle'
|
||||
| 'go'
|
||||
| 'vue'
|
||||
| 'rust'
|
||||
| 'nuxt'
|
||||
| 'svelte'
|
||||
| 'gatsby'
|
||||
| 'astro'
|
||||
| 'playwright'
|
||||
| 'pnpm'
|
||||
| 'monorepo'
|
||||
| 'cra'
|
||||
| 'cypress'
|
||||
| 'expo'
|
||||
| 'react'
|
||||
| 'azure'
|
||||
| 'bitbucket'
|
||||
| 'circleci'
|
||||
| 'github'
|
||||
| 'gitlab'
|
||||
| 'jenkins'
|
||||
| 'apollo'
|
||||
| 'prisma'
|
||||
| 'redis'
|
||||
| 'postgres'
|
||||
| 'planetscale'
|
||||
| 'mongodb'
|
||||
| 'mfe'
|
||||
| 'eslint';
|
||||
|
||||
export const frameworkIcons: Record<
|
||||
string,
|
||||
Framework,
|
||||
{
|
||||
image: JSX.Element;
|
||||
// this key determines whether the icon should be adaptive or not
|
||||
// if true means icon is mostly monotone (black/white), its parent needs to specify the fill color
|
||||
isAdaptiveIcon?: boolean;
|
||||
}
|
||||
> = {
|
||||
reactMono: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
className="h-full w-full"
|
||||
@ -22,6 +84,7 @@ export const frameworkIcons: Record<
|
||||
tsMono: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
className="h-full w-full"
|
||||
@ -57,6 +120,7 @@ export const frameworkIcons: Record<
|
||||
jsMono: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
className="h-full w-full"
|
||||
@ -83,6 +147,7 @@ export const frameworkIcons: Record<
|
||||
nodeMono: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
className="h-full w-full"
|
||||
@ -98,6 +163,7 @@ export const frameworkIcons: Record<
|
||||
angularMono: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
className="h-full w-full"
|
||||
@ -136,6 +202,7 @@ export const frameworkIcons: Record<
|
||||
typescript: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#3178C6"
|
||||
className="h-full w-full"
|
||||
@ -149,6 +216,7 @@ export const frameworkIcons: Record<
|
||||
youtube: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="red"
|
||||
className="h-full w-full"
|
||||
@ -178,6 +246,7 @@ export const frameworkIcons: Record<
|
||||
nxcloud: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
stroke="currentColor"
|
||||
fill="transparent"
|
||||
@ -191,6 +260,7 @@ export const frameworkIcons: Record<
|
||||
nx: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
fill="currentColor"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 24 24"
|
||||
@ -203,8 +273,8 @@ export const frameworkIcons: Record<
|
||||
node: {
|
||||
image: (
|
||||
<svg
|
||||
fill="#339933"
|
||||
role="img"
|
||||
fill="#339933"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -217,9 +287,8 @@ export const frameworkIcons: Record<
|
||||
nextjs: {
|
||||
image: (
|
||||
<svg
|
||||
fill="#000000"
|
||||
className="adaptive-icon h-full w-full"
|
||||
role="img"
|
||||
className="adaptive-icon h-full w-full"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
@ -227,12 +296,13 @@ export const frameworkIcons: Record<
|
||||
<path d="M11.5725 0c-.1763 0-.3098.0013-.3584.0067-.0516.0053-.2159.021-.3636.0328-3.4088.3073-6.6017 2.1463-8.624 4.9728C1.1004 6.584.3802 8.3666.1082 10.255c-.0962.659-.108.8537-.108 1.7474s.012 1.0884.108 1.7476c.652 4.506 3.8591 8.2919 8.2087 9.6945.7789.2511 1.6.4223 2.5337.5255.3636.04 1.9354.04 2.299 0 1.6117-.1783 2.9772-.577 4.3237-1.2643.2065-.1056.2464-.1337.2183-.1573-.0188-.0139-.8987-1.1938-1.9543-2.62l-1.919-2.592-2.4047-3.5583c-1.3231-1.9564-2.4117-3.556-2.4211-3.556-.0094-.0026-.0187 1.5787-.0235 3.509-.0067 3.3802-.0093 3.5162-.0516 3.596-.061.115-.108.1618-.2064.2134-.075.0374-.1408.0445-.495.0445h-.406l-.1078-.068a.4383.4383 0 01-.1572-.1712l-.0493-.1056.0053-4.703.0067-4.7054.0726-.0915c.0376-.0493.1174-.1125.1736-.143.0962-.047.1338-.0517.5396-.0517.4787 0 .5584.0187.6827.1547.0353.0377 1.3373 1.9987 2.895 4.3608a10760.433 10760.433 0 004.7344 7.1706l1.9002 2.8782.096-.0633c.8518-.5536 1.7525-1.3418 2.4657-2.1627 1.5179-1.7429 2.4963-3.868 2.8247-6.134.0961-.6591.1078-.854.1078-1.7475 0-.8937-.012-1.0884-.1078-1.7476-.6522-4.506-3.8592-8.2919-8.2087-9.6945-.7672-.2487-1.5836-.42-2.4985-.5232-.169-.0176-1.0835-.0366-1.6123-.037zm4.0685 7.217c.3473 0 .4082.0053.4857.047.1127.0562.204.1642.237.2767.0186.061.0234 1.3653.0186 4.3044l-.0067 4.2175-.7436-1.14-.7461-1.14v-3.066c0-1.982.0093-3.0963.0234-3.1502.0375-.1313.1196-.2346.2323-.2955.0961-.0494.1313-.054.4997-.054z" />
|
||||
</svg>
|
||||
),
|
||||
isAdaptiveIcon: true,
|
||||
},
|
||||
nestjs: {
|
||||
image: (
|
||||
<svg
|
||||
fill="#E0234E"
|
||||
role="img"
|
||||
fill="#E0234E"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -245,6 +315,7 @@ export const frameworkIcons: Record<
|
||||
rspack: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-full w-full"
|
||||
viewBox="20 0 350 339.32"
|
||||
@ -275,6 +346,7 @@ export const frameworkIcons: Record<
|
||||
jest: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-full w-full"
|
||||
viewBox="100 150 450 450"
|
||||
@ -328,7 +400,6 @@ export const frameworkIcons: Record<
|
||||
fastify: {
|
||||
image: (
|
||||
<svg
|
||||
fill="#000000"
|
||||
role="img"
|
||||
className="adaptive-icon h-full w-full"
|
||||
viewBox="0 0 24 24"
|
||||
@ -338,13 +409,13 @@ export const frameworkIcons: Record<
|
||||
<path d="M23.245 6.49L24 4.533l-.031-.121-7.473 1.967c.797-1.153.523-2.078.523-2.078s-2.387 1.524-4.193 1.485c-1.804-.04-2.387-.52-5.155.362-2.768.882-3.551 3.59-4.351 4.173-.804.583-3.32 2.477-3.32 2.477l.006.034 2.27-.724s-.622.585-1.945 2.37l-.062-.057.002.011s1.064 1.626 2.107 1.324a2.14 2.14 0 0 0 .353-.147c.419.234.967.463 1.572.525 0 0-.41-.475-.752-1.017l.238-.154.865.318-.096-.812c.003-.003.006-.003.008-.006l.849.311-.105-.738a5.65 5.65 0 0 1 .322-.158l.885-3.345 3.662-2.497-.291.733c-.741 1.826-2.135 2.256-2.135 2.256l-.582.22c-.433.512-.614.637-.764 2.353.348-.088.682-.107.984-.028 1.564.421 2.107 2.307 1.685 2.827-.104.13-.356.354-.673.617H7.77l-.008.514-.065.051h-.645l-.009.504-.17.127c-.607.011-1.373-.518-1.373-.518 0 .481.401 1.225.401 1.225l.07-.034-.061.045s1.625 1.083 2.646.681c.91-.356 3.263-2.213 5.296-3.093l6.15-1.62.811-2.1-4.688 1.235v-1.889l5.5-1.448.811-2.1-6.31 1.662V8.367zm-11.163 4l1.459-.384.02.074-.455 1.179-1.513.398zm.503 2.526l-1.512.398.489-1.266 1.459-.385.02.074zm1.971-.424l-1.513.398.49-1.266 1.459-.385.02.073Z" />
|
||||
</svg>
|
||||
),
|
||||
isAdaptiveIcon: true,
|
||||
},
|
||||
express: {
|
||||
image: (
|
||||
<svg
|
||||
fill="#000000"
|
||||
className="adaptive-icon h-full w-full"
|
||||
role="img"
|
||||
className="adaptive-icon h-full w-full"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
@ -352,12 +423,13 @@ export const frameworkIcons: Record<
|
||||
<path d="M24 18.588a1.529 1.529 0 01-1.895-.72l-3.45-4.771-.5-.667-4.003 5.444a1.466 1.466 0 01-1.802.708l5.158-6.92-4.798-6.251a1.595 1.595 0 011.9.666l3.576 4.83 3.596-4.81a1.435 1.435 0 011.788-.668L21.708 7.9l-2.522 3.283a.666.666 0 000 .994l4.804 6.412zM.002 11.576l.42-2.075c1.154-4.103 5.858-5.81 9.094-3.27 1.895 1.489 2.368 3.597 2.275 5.973H1.116C.943 16.447 4.005 19.009 7.92 17.7a4.078 4.078 0 002.582-2.876c.207-.666.548-.78 1.174-.588a5.417 5.417 0 01-2.589 3.957 6.272 6.272 0 01-7.306-.933 6.575 6.575 0 01-1.64-3.858c0-.235-.08-.455-.134-.666A88.33 88.33 0 010 11.577zm1.127-.286h9.654c-.06-3.076-2.001-5.258-4.59-5.278-2.882-.04-4.944 2.094-5.071 5.264z" />
|
||||
</svg>
|
||||
),
|
||||
isAdaptiveIcon: true,
|
||||
},
|
||||
storybook: {
|
||||
image: (
|
||||
<svg
|
||||
fill="#FF4785"
|
||||
role="img"
|
||||
fill="#FF4785"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -370,11 +442,12 @@ export const frameworkIcons: Record<
|
||||
solid: {
|
||||
image: (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 166 155.3"
|
||||
>
|
||||
<title>Solid</title>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="a"
|
||||
@ -467,8 +540,8 @@ export const frameworkIcons: Record<
|
||||
lit: {
|
||||
image: (
|
||||
<svg
|
||||
fill="#324FFF"
|
||||
role="img"
|
||||
fill="#324FFF"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -481,8 +554,8 @@ export const frameworkIcons: Record<
|
||||
vite: {
|
||||
image: (
|
||||
<svg
|
||||
viewBox="0 0 410 404"
|
||||
role="img"
|
||||
viewBox="0 0 410 404"
|
||||
className="h-full w-full"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -527,8 +600,8 @@ export const frameworkIcons: Record<
|
||||
trpc: {
|
||||
image: (
|
||||
<svg
|
||||
fill="#2596BE"
|
||||
role="img"
|
||||
fill="#2596BE"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -541,6 +614,7 @@ export const frameworkIcons: Record<
|
||||
remix: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
className="h-full w-full"
|
||||
@ -605,6 +679,7 @@ export const frameworkIcons: Record<
|
||||
dotnet: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 456 456"
|
||||
fill="none"
|
||||
@ -633,6 +708,7 @@ export const frameworkIcons: Record<
|
||||
qwik: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
className="h-full w-full"
|
||||
@ -656,6 +732,7 @@ export const frameworkIcons: Record<
|
||||
gradle: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
id="Layer_1"
|
||||
data-name="Layer 1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -686,6 +763,7 @@ export const frameworkIcons: Record<
|
||||
go: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 32 32"
|
||||
fill="none"
|
||||
@ -715,6 +793,7 @@ export const frameworkIcons: Record<
|
||||
vue: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
className="h-full w-full"
|
||||
viewBox="0 -17.5 256 256"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -738,6 +817,7 @@ export const frameworkIcons: Record<
|
||||
rust: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
version="1.1"
|
||||
viewBox="0 0 108 108"
|
||||
className="adaptive-icon h-full w-full"
|
||||
@ -820,10 +900,12 @@ export const frameworkIcons: Record<
|
||||
</g>
|
||||
</svg>
|
||||
),
|
||||
isAdaptiveIcon: true,
|
||||
},
|
||||
nuxt: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 900 900"
|
||||
fill="none"
|
||||
@ -839,6 +921,7 @@ export const frameworkIcons: Record<
|
||||
svelte: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 98.1 118"
|
||||
@ -866,6 +949,7 @@ export const frameworkIcons: Record<
|
||||
gatsby: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 32 32"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -885,8 +969,8 @@ export const frameworkIcons: Record<
|
||||
astro: {
|
||||
image: (
|
||||
<svg
|
||||
fill="#FF5D01"
|
||||
role="img"
|
||||
fill="#FF5D01"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -899,8 +983,8 @@ export const frameworkIcons: Record<
|
||||
playwright: {
|
||||
image: (
|
||||
<svg
|
||||
fill="#2EAD33"
|
||||
role="img"
|
||||
fill="#2EAD33"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -913,8 +997,8 @@ export const frameworkIcons: Record<
|
||||
pnpm: {
|
||||
image: (
|
||||
<svg
|
||||
fill="#F69220"
|
||||
role="img"
|
||||
fill="#F69220"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -927,6 +1011,7 @@ export const frameworkIcons: Record<
|
||||
monorepo: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
className="h-full w-full"
|
||||
@ -949,6 +1034,7 @@ export const frameworkIcons: Record<
|
||||
javascript: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 630 630"
|
||||
@ -962,6 +1048,7 @@ export const frameworkIcons: Record<
|
||||
cra: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#09D3AC"
|
||||
className="h-full w-full"
|
||||
@ -974,6 +1061,7 @@ export const frameworkIcons: Record<
|
||||
angular: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-full w-full text-black dark:text-white"
|
||||
viewBox="0 0 24 24"
|
||||
@ -1009,6 +1097,7 @@ export const frameworkIcons: Record<
|
||||
cypress: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="adaptive-icon h-full w-full"
|
||||
viewBox="0 0 24 24"
|
||||
@ -1017,10 +1106,12 @@ export const frameworkIcons: Record<
|
||||
<path d="M11.998 0C5.366 0 0 5.367 0 12a11.992 11.992 0 0012 12c6.633 0 12-5.367 12-12-.001-6.633-5.412-12-12.002-12zM6.37 14.575c.392.523.916.742 1.657.742.35 0 .699-.044 1.004-.175.306-.13.655-.306 1.09-.567l1.223 1.745c-1.003.83-2.138 1.222-3.447 1.222-1.048 0-1.92-.218-2.705-.654a4.393 4.393 0 01-1.746-1.92c-.392-.83-.611-1.79-.611-2.925 0-1.09.219-2.094.61-2.923a4.623 4.623 0 011.748-2.007c.741-.48 1.657-.698 2.661-.698.699 0 1.353.087 1.877.305a5.64 5.64 0 011.614.96l-1.222 1.658A4.786 4.786 0 009.12 8.77c-.305-.13-.698-.174-1.048-.174-1.483 0-2.225 1.134-2.225 3.446-.043 1.18.175 2.008.524 2.532H6.37zm12 2.705c-.436 1.353-1.091 2.357-2.008 3.098-.916.743-2.138 1.135-3.665 1.266l-.305-2.05c1.003-.132 1.745-.35 2.225-.7.174-.13.524-.523.524-.523L11.519 6.764h3.01l2.095 8.683 2.226-8.683h2.923L18.37 17.28z"></path>
|
||||
</svg>
|
||||
),
|
||||
isAdaptiveIcon: true,
|
||||
},
|
||||
expo: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="adaptive-icon h-full w-full"
|
||||
fill="#000020"
|
||||
@ -1030,12 +1121,13 @@ export const frameworkIcons: Record<
|
||||
<path d="M0 20.084c.043.53.23 1.063.718 1.778.58.849 1.576 1.315 2.303.567.49-.505 5.794-9.776 8.35-13.29a.761.761 0 011.248 0c2.556 3.514 7.86 12.785 8.35 13.29.727.748 1.723.282 2.303-.567.57-.835.728-1.42.728-2.046 0-.426-8.26-15.798-9.092-17.078-.8-1.23-1.044-1.498-2.397-1.542h-1.032c-1.353.044-1.597.311-2.398 1.542C8.267 3.991.33 18.758 0 19.77z"></path>
|
||||
</svg>
|
||||
),
|
||||
isAdaptiveIcon: true,
|
||||
},
|
||||
react: {
|
||||
image: (
|
||||
<svg
|
||||
fill="#61DAFB"
|
||||
role="img"
|
||||
fill="#61DAFB"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@ -1048,6 +1140,7 @@ export const frameworkIcons: Record<
|
||||
azure: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-full w-full"
|
||||
viewBox="0 0 728.55 727.44"
|
||||
@ -1094,6 +1187,7 @@ export const frameworkIcons: Record<
|
||||
bitbucket: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-full w-full"
|
||||
width="500"
|
||||
@ -1132,6 +1226,7 @@ export const frameworkIcons: Record<
|
||||
circleci: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
className="adaptive-icon h-full w-full"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 390 390"
|
||||
@ -1149,6 +1244,7 @@ export const frameworkIcons: Record<
|
||||
github: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
className="adaptive-icon h-full w-full"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="-1 0 100 100"
|
||||
@ -1166,6 +1262,7 @@ export const frameworkIcons: Record<
|
||||
gitlab: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-full w-full"
|
||||
width="463.16"
|
||||
@ -1201,6 +1298,7 @@ export const frameworkIcons: Record<
|
||||
jenkins: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-full w-full"
|
||||
viewBox="-2 0 448 620"
|
||||
@ -1457,7 +1555,6 @@ export const frameworkIcons: Record<
|
||||
apollo: {
|
||||
image: (
|
||||
<svg
|
||||
fill="#000000"
|
||||
role="img"
|
||||
className="adaptive-icon h-full w-full"
|
||||
height="256"
|
||||
@ -1473,7 +1570,6 @@ export const frameworkIcons: Record<
|
||||
prisma: {
|
||||
image: (
|
||||
<svg
|
||||
fill="currentColor"
|
||||
role="img"
|
||||
className="adaptive-icon h-full w-full"
|
||||
viewBox="0.34 -0.059977834648891726 33.11668247084116 39.96397783464889"
|
||||
@ -1492,6 +1588,7 @@ export const frameworkIcons: Record<
|
||||
redis: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
width="32"
|
||||
className="h-full w-full"
|
||||
height="32"
|
||||
@ -1540,6 +1637,7 @@ export const frameworkIcons: Record<
|
||||
postgres: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="32"
|
||||
height="32"
|
||||
@ -1570,6 +1668,7 @@ export const frameworkIcons: Record<
|
||||
planetscale: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
@ -1591,10 +1690,10 @@ export const frameworkIcons: Record<
|
||||
mongodb: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
width="15"
|
||||
height="15"
|
||||
viewBox="0 0 15 15"
|
||||
fill="none"
|
||||
className="adaptive-icon h-full w-full"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
@ -1608,6 +1707,7 @@ export const frameworkIcons: Record<
|
||||
mfe: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
@ -1622,4 +1722,28 @@ export const frameworkIcons: Record<
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
eslint: {
|
||||
image: (
|
||||
<svg
|
||||
role="img"
|
||||
width="256"
|
||||
height="263"
|
||||
viewBox="0 0 256 263"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-full w-full"
|
||||
>
|
||||
<g>
|
||||
<path
|
||||
d="M100.033785,262.105923 L0.598353254,172.570518 L28.4489774,41.6943426 L155.626242,0.353572377 L255.061673,89.8889774 L227.211049,220.765153 L100.033785,262.105923 L100.033785,262.105923 Z M51.9479416,155.925418 L111.239309,209.233254 L187.066985,184.537583 L203.712085,106.534077 L144.420717,53.1174502 L68.5930412,77.9219124 L51.9479416,155.925418 L51.9479416,155.925418 Z"
|
||||
fill="#3A33D1"
|
||||
></path>
|
||||
<path
|
||||
d="M181.301036,223.920106 L74.3589907,223.920106 L20.8335724,131.229748 L74.3589907,38.5393891 L181.301036,38.5393891 L234.826454,131.229748 L181.301036,223.920106 L181.301036,223.920106 Z M93.1799203,191.282656 L162.480106,191.282656 L197.184595,131.229748 L162.480106,71.1768393 L93.1799203,71.1768393 L58.5842231,131.229748 L93.1799203,191.282656 L93.1799203,191.282656 Z"
|
||||
fill="#6464E2"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
};
|
||||
22
graph/ui-icons/src/lib/technology-icon.stories.tsx
Normal file
22
graph/ui-icons/src/lib/technology-icon.stories.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { TechnologyIcon } from './technology-icon';
|
||||
|
||||
const meta: Meta<typeof TechnologyIcon> = {
|
||||
component: TechnologyIcon,
|
||||
title: 'TechnologyIcon',
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof TechnologyIcon>;
|
||||
|
||||
export const Simple: Story = {
|
||||
args: {
|
||||
technology: 'react',
|
||||
},
|
||||
};
|
||||
|
||||
export const UnknownTechnology: Story = {
|
||||
args: {
|
||||
technology: 'unknown',
|
||||
},
|
||||
};
|
||||
33
graph/ui-icons/src/lib/technology-icon.tsx
Normal file
33
graph/ui-icons/src/lib/technology-icon.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { Framework, frameworkIcons } from './framework-icons';
|
||||
|
||||
export interface TechnologyIconProps {
|
||||
technology?: string;
|
||||
showTooltip?: boolean;
|
||||
}
|
||||
|
||||
export function TechnologyIcon({
|
||||
technology,
|
||||
showTooltip,
|
||||
}: TechnologyIconProps) {
|
||||
if (!technology) {
|
||||
return null;
|
||||
}
|
||||
const image = frameworkIcons[technology as Framework]?.image;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`h-4 w-4 ${
|
||||
frameworkIcons[technology as Framework]?.isAdaptiveIcon
|
||||
? 'adpative-icon'
|
||||
: ''
|
||||
} ${
|
||||
!image
|
||||
? 'flex items-center justify-center rounded bg-slate-800 text-sm text-slate-50 dark:bg-slate-50 dark:text-slate-800'
|
||||
: ''
|
||||
}`}
|
||||
data-tooltip={showTooltip ? technology : null}
|
||||
>
|
||||
{image ?? technology[0]}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
40
graph/ui-icons/tailwind.config.js
Normal file
40
graph/ui-icons/tailwind.config.js
Normal file
@ -0,0 +1,40 @@
|
||||
const path = require('path');
|
||||
|
||||
// nx-ignore-next-line
|
||||
const { createGlobPatternsForDependencies } = require('@nx/react/tailwind');
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
path.join(__dirname, 'src/**/*.{js,ts,jsx,tsx,html}'),
|
||||
...createGlobPatternsForDependencies(__dirname),
|
||||
],
|
||||
darkMode: 'class', // or 'media' or 'class'
|
||||
theme: {
|
||||
extend: {
|
||||
typography: {
|
||||
DEFAULT: {
|
||||
css: {
|
||||
'code::before': {
|
||||
content: '',
|
||||
},
|
||||
'code::after': {
|
||||
content: '',
|
||||
},
|
||||
'blockquote p:first-of-type::before': {
|
||||
content: '',
|
||||
},
|
||||
'blockquote p:last-of-type::after': {
|
||||
content: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
extend: {
|
||||
translate: ['group-hover'],
|
||||
},
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
};
|
||||
20
graph/ui-icons/tsconfig.json
Normal file
20
graph/ui-icons/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.storybook.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../tsconfig.base.json"
|
||||
}
|
||||
27
graph/ui-icons/tsconfig.lib.json
Normal file
27
graph/ui-icons/tsconfig.lib.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": [
|
||||
"node",
|
||||
"@nx/react/typings/cssmodule.d.ts",
|
||||
"@nx/react/typings/image.d.ts"
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.test.jsx",
|
||||
"**/*.stories.ts",
|
||||
"**/*.stories.js",
|
||||
"**/*.stories.jsx",
|
||||
"**/*.stories.tsx"
|
||||
],
|
||||
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
|
||||
}
|
||||
31
graph/ui-icons/tsconfig.storybook.json
Normal file
31
graph/ui-icons/tsconfig.storybook.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": ""
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nx/react/typings/styled-jsx.d.ts",
|
||||
"../../node_modules/@nx/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nx/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.test.js"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.stories.ts",
|
||||
"src/**/*.stories.js",
|
||||
"src/**/*.stories.jsx",
|
||||
"src/**/*.stories.tsx",
|
||||
"src/**/*.stories.mdx",
|
||||
".storybook/*.js",
|
||||
".storybook/*.ts"
|
||||
]
|
||||
}
|
||||
@ -1 +1,2 @@
|
||||
export * from './lib/project-details/project-details';
|
||||
export * from './lib/utils/group-targets';
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { CopyToClipboard } from './copy-to-clipboard';
|
||||
|
||||
const meta: Meta<typeof CopyToClipboard> = {
|
||||
component: CopyToClipboard,
|
||||
title: 'CopyToClipboard',
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof CopyToClipboard>;
|
||||
|
||||
export const Simple: Story = {
|
||||
args: {
|
||||
onCopy: () => {},
|
||||
tooltipAlignment: 'left',
|
||||
},
|
||||
};
|
||||
@ -1,9 +1,11 @@
|
||||
import type { Meta } from '@storybook/react';
|
||||
import { ProjectDetails } from './project-details';
|
||||
import { StoreDecorator } from '@nx/graph/state';
|
||||
|
||||
const meta: Meta<typeof ProjectDetails> = {
|
||||
component: ProjectDetails,
|
||||
title: 'ProjectDetails',
|
||||
decorators: [StoreDecorator],
|
||||
};
|
||||
export default meta;
|
||||
|
||||
@ -208,3 +210,626 @@ export const Primary = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Gradle = {
|
||||
args: {
|
||||
project: {
|
||||
name: 'utilities',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'utilities',
|
||||
name: 'utilities',
|
||||
metadata: {
|
||||
targetGroups: {
|
||||
Build: [
|
||||
'assemble',
|
||||
'build',
|
||||
'buildDependents',
|
||||
'buildKotlinToolingMetadata',
|
||||
'buildNeeded',
|
||||
'classes',
|
||||
'clean',
|
||||
'jar',
|
||||
'kotlinSourcesJar',
|
||||
'testClasses',
|
||||
],
|
||||
Documentation: ['javadoc'],
|
||||
Help: [
|
||||
'buildEnvironment',
|
||||
'dependencies',
|
||||
'dependencyInsight',
|
||||
'help',
|
||||
'javaToolchains',
|
||||
'kotlinDslAccessorsReport',
|
||||
'outgoingVariants',
|
||||
'projects',
|
||||
'properties',
|
||||
'resolvableConfigurations',
|
||||
'tasks',
|
||||
],
|
||||
Reporting: ['projectReport'],
|
||||
Verification: [
|
||||
'check',
|
||||
'checkKotlinGradlePluginConfigurationErrors',
|
||||
'test',
|
||||
],
|
||||
},
|
||||
technologies: ['gradle'],
|
||||
},
|
||||
targets: {
|
||||
assemble: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew assemble',
|
||||
},
|
||||
cache: true,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
build: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew build',
|
||||
},
|
||||
cache: true,
|
||||
inputs: ['default', '^default'],
|
||||
outputs: ['{workspaceRoot}/utilities/build'],
|
||||
dependsOn: ['^build', 'classes'],
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
buildDependents: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew buildDependents',
|
||||
},
|
||||
cache: true,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
buildKotlinToolingMetadata: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew buildKotlinToolingMetadata',
|
||||
},
|
||||
cache: true,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
buildNeeded: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew buildNeeded',
|
||||
},
|
||||
cache: true,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
classes: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew classes',
|
||||
},
|
||||
cache: true,
|
||||
inputs: ['default', '^default'],
|
||||
outputs: ['{workspaceRoot}/utilities/build/classes'],
|
||||
dependsOn: ['^classes'],
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
clean: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew clean',
|
||||
},
|
||||
cache: true,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
jar: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command: '/Users/emily/code/tmp/gradle-plugin-test3/gradlew jar',
|
||||
},
|
||||
cache: true,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
kotlinSourcesJar: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew kotlinSourcesJar',
|
||||
},
|
||||
cache: true,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
testClasses: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew testClasses',
|
||||
},
|
||||
cache: true,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
javadoc: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew javadoc',
|
||||
},
|
||||
cache: false,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
buildEnvironment: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew buildEnvironment',
|
||||
},
|
||||
cache: false,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
dependencies: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew dependencies',
|
||||
},
|
||||
cache: false,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
dependencyInsight: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew dependencyInsight',
|
||||
},
|
||||
cache: false,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
help: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command: '/Users/emily/code/tmp/gradle-plugin-test3/gradlew help',
|
||||
},
|
||||
cache: false,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
javaToolchains: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew javaToolchains',
|
||||
},
|
||||
cache: false,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
kotlinDslAccessorsReport: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew kotlinDslAccessorsReport',
|
||||
},
|
||||
cache: false,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
outgoingVariants: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew outgoingVariants',
|
||||
},
|
||||
cache: false,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
projects: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew projects',
|
||||
},
|
||||
cache: false,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
properties: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew properties',
|
||||
},
|
||||
cache: false,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
resolvableConfigurations: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew resolvableConfigurations',
|
||||
},
|
||||
cache: false,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
tasks: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew tasks',
|
||||
},
|
||||
cache: false,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
projectReport: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew projectReport',
|
||||
},
|
||||
cache: false,
|
||||
outputs: ['{workspaceRoot}/utilities/build/reports/project'],
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
check: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew check',
|
||||
},
|
||||
cache: true,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
checkKotlinGradlePluginConfigurationErrors: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command:
|
||||
'/Users/emily/code/tmp/gradle-plugin-test3/gradlew checkKotlinGradlePluginConfigurationErrors',
|
||||
},
|
||||
cache: true,
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
test: {
|
||||
options: {
|
||||
cwd: 'utilities',
|
||||
command: '/Users/emily/code/tmp/gradle-plugin-test3/gradlew test',
|
||||
},
|
||||
cache: true,
|
||||
inputs: ['default', '^default'],
|
||||
dependsOn: ['classes'],
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
},
|
||||
implicitDependencies: [],
|
||||
tags: [],
|
||||
},
|
||||
},
|
||||
sourceMap: {
|
||||
root: ['packages/jest/project.json', 'nx-core-build-project-json-nodes'],
|
||||
name: ['packages/jest/project.json', 'nx-core-build-project-json-nodes'],
|
||||
targets: [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.nx-release-publish': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-package-json-nodes-next-to-project-json-nodes',
|
||||
],
|
||||
'targets.nx-release-publish.dependsOn': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-package-json-nodes-next-to-project-json-nodes',
|
||||
],
|
||||
'targets.nx-release-publish.executor': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-package-json-nodes-next-to-project-json-nodes',
|
||||
],
|
||||
'targets.nx-release-publish.options': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-package-json-nodes-next-to-project-json-nodes',
|
||||
],
|
||||
$schema: [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
sourceRoot: [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
projectType: [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.test': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build-base': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build-base.executor': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build-base.options': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build-base.options.assets': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build.executor': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build.outputs': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build.options': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build.options.command': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.add-extra-dependencies': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.add-extra-dependencies.command': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.lint': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Cart = {
|
||||
args: {
|
||||
project: {
|
||||
name: 'cart-e2e',
|
||||
type: 'e2e',
|
||||
data: {
|
||||
root: 'apps/cart-e2e',
|
||||
targets: {
|
||||
lint: {
|
||||
cache: true,
|
||||
options: {
|
||||
cwd: 'apps/cart-e2e',
|
||||
command: 'eslint .',
|
||||
},
|
||||
inputs: [
|
||||
'default',
|
||||
'^default',
|
||||
'{workspaceRoot}/.eslintrc.json',
|
||||
'{projectRoot}/.eslintrc.json',
|
||||
'{workspaceRoot}/tools/eslint-rules/**/*',
|
||||
{
|
||||
externalDependencies: ['eslint'],
|
||||
},
|
||||
],
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
e2e: {
|
||||
cache: true,
|
||||
inputs: ['default', '^production'],
|
||||
outputs: [
|
||||
'{workspaceRoot}/dist/cypress/apps/cart-e2e/videos',
|
||||
'{workspaceRoot}/dist/cypress/apps/cart-e2e/screenshots',
|
||||
],
|
||||
metadata: {
|
||||
technologies: ['cypress'],
|
||||
description: 'Runs Cypress Tests',
|
||||
},
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
cwd: 'apps/cart-e2e',
|
||||
command: 'cypress run',
|
||||
},
|
||||
configurations: {},
|
||||
},
|
||||
'e2e-ci--src/e2e/app.cy.ts': {
|
||||
outputs: [
|
||||
'{workspaceRoot}/dist/cypress/apps/cart-e2e/videos',
|
||||
'{workspaceRoot}/dist/cypress/apps/cart-e2e/screenshots',
|
||||
],
|
||||
inputs: [
|
||||
'default',
|
||||
'^production',
|
||||
{
|
||||
externalDependencies: ['cypress'],
|
||||
},
|
||||
],
|
||||
cache: true,
|
||||
options: {
|
||||
cwd: 'apps/cart-e2e',
|
||||
command:
|
||||
'cypress run --env webServerCommand="nx run cart:serve" --spec src/e2e/app.cy.ts',
|
||||
},
|
||||
metadata: {
|
||||
technologies: ['cypress'],
|
||||
description: 'Runs Cypress Tests in src/e2e/app.cy.ts in CI',
|
||||
},
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
'e2e-ci': {
|
||||
executor: 'nx:noop',
|
||||
cache: true,
|
||||
inputs: [
|
||||
'default',
|
||||
'^production',
|
||||
{
|
||||
externalDependencies: ['cypress'],
|
||||
},
|
||||
],
|
||||
outputs: [
|
||||
'{workspaceRoot}/dist/cypress/apps/cart-e2e/videos',
|
||||
'{workspaceRoot}/dist/cypress/apps/cart-e2e/screenshots',
|
||||
],
|
||||
dependsOn: [
|
||||
{
|
||||
target: 'e2e-ci--src/e2e/app.cy.ts',
|
||||
projects: 'self',
|
||||
params: 'forward',
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
technologies: ['cypress'],
|
||||
description: 'Runs Cypress Tests in CI',
|
||||
},
|
||||
options: {},
|
||||
configurations: {},
|
||||
},
|
||||
'open-cypress': {
|
||||
options: {
|
||||
cwd: 'apps/cart-e2e',
|
||||
command: 'cypress open',
|
||||
},
|
||||
metadata: {
|
||||
technologies: ['cypress'],
|
||||
description: 'Opens Cypress',
|
||||
},
|
||||
executor: 'nx:run-commands',
|
||||
configurations: {},
|
||||
},
|
||||
},
|
||||
projectType: 'application',
|
||||
metadata: {
|
||||
targetGroups: {
|
||||
'E2E (CI)': ['e2e-ci--src/e2e/app.cy.ts', 'e2e-ci'],
|
||||
},
|
||||
},
|
||||
name: 'cart-e2e',
|
||||
$schema: '../../node_modules/nx/schemas/project-schema.json',
|
||||
sourceRoot: 'apps/cart-e2e/src',
|
||||
tags: ['scope:cart', 'type:e2e'],
|
||||
implicitDependencies: ['cart'],
|
||||
},
|
||||
},
|
||||
sourceMap: {
|
||||
root: ['packages/jest/project.json', 'nx-core-build-project-json-nodes'],
|
||||
name: ['packages/jest/project.json', 'nx-core-build-project-json-nodes'],
|
||||
targets: [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.nx-release-publish': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-package-json-nodes-next-to-project-json-nodes',
|
||||
],
|
||||
'targets.nx-release-publish.dependsOn': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-package-json-nodes-next-to-project-json-nodes',
|
||||
],
|
||||
'targets.nx-release-publish.executor': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-package-json-nodes-next-to-project-json-nodes',
|
||||
],
|
||||
'targets.nx-release-publish.options': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-package-json-nodes-next-to-project-json-nodes',
|
||||
],
|
||||
$schema: [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
sourceRoot: [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
projectType: [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.test': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build-base': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build-base.executor': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build-base.options': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build-base.options.assets': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build.executor': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build.outputs': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build.options': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.build.options.command': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.add-extra-dependencies': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.add-extra-dependencies.command': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
'targets.lint': [
|
||||
'packages/jest/project.json',
|
||||
'nx-core-build-project-json-nodes',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@ -2,32 +2,20 @@
|
||||
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
import type { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
|
||||
import { EyeIcon } from '@heroicons/react/24/outline';
|
||||
import { PropertyInfoTooltip, Tooltip } from '@nx/graph/ui-tooltips';
|
||||
import {
|
||||
TargetConfigurationDetails,
|
||||
TargetConfigurationDetailsHandle,
|
||||
} from '../target-configuration-details/target-configuration-details';
|
||||
import { TooltipTriggerText } from '../target-configuration-details/tooltip-trigger-text';
|
||||
import {
|
||||
createRef,
|
||||
ForwardedRef,
|
||||
forwardRef,
|
||||
RefObject,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { Pill } from '../pill';
|
||||
import { TargetConfigurationDetailsList } from '../target-configuration-details-list/target-configuration-details-list';
|
||||
import { TargetTechnologies } from '../target-technologies/target-technologies';
|
||||
|
||||
export interface ProjectDetailsProps {
|
||||
project: ProjectGraphProjectNode;
|
||||
sourceMap: Record<string, string[]>;
|
||||
variant?: 'default' | 'compact';
|
||||
onTargetCollapse?: (targetName: string) => void;
|
||||
onTargetExpand?: (targetName: string) => void;
|
||||
onViewInProjectGraph?: (data: { projectName: string }) => void;
|
||||
onViewInTaskGraph?: (data: {
|
||||
projectName: string;
|
||||
@ -36,143 +24,121 @@ export interface ProjectDetailsProps {
|
||||
onRunTarget?: (data: { projectName: string; targetName: string }) => void;
|
||||
}
|
||||
|
||||
export interface ProjectDetailsImperativeHandle {
|
||||
collapseTarget: (targetName: string) => void;
|
||||
expandTarget: (targetName: string) => void;
|
||||
}
|
||||
export const ProjectDetails = ({
|
||||
project,
|
||||
sourceMap,
|
||||
variant,
|
||||
onViewInProjectGraph,
|
||||
onViewInTaskGraph,
|
||||
onRunTarget,
|
||||
}: ProjectDetailsProps) => {
|
||||
const projectData = project.data;
|
||||
const isCompact = variant === 'compact';
|
||||
|
||||
export const ProjectDetails = forwardRef(
|
||||
(
|
||||
{
|
||||
project: {
|
||||
name,
|
||||
data: { root, ...projectData },
|
||||
},
|
||||
sourceMap,
|
||||
variant,
|
||||
onTargetCollapse,
|
||||
onTargetExpand,
|
||||
onViewInProjectGraph,
|
||||
onViewInTaskGraph,
|
||||
onRunTarget,
|
||||
}: ProjectDetailsProps,
|
||||
ref: ForwardedRef<ProjectDetailsImperativeHandle>
|
||||
) => {
|
||||
const isCompact = variant === 'compact';
|
||||
const projectTargets = Object.keys(projectData.targets ?? {});
|
||||
const targetRefs = useRef(
|
||||
projectTargets.reduce((acc, targetName) => {
|
||||
acc[targetName] = createRef<TargetConfigurationDetailsHandle>();
|
||||
return acc;
|
||||
}, {} as Record<string, RefObject<TargetConfigurationDetailsHandle>>)
|
||||
);
|
||||
const displayType =
|
||||
projectData.projectType &&
|
||||
projectData.projectType?.charAt(0)?.toUpperCase() +
|
||||
projectData.projectType?.slice(1);
|
||||
|
||||
const displayType =
|
||||
projectData.projectType &&
|
||||
projectData.projectType?.charAt(0)?.toUpperCase() +
|
||||
projectData.projectType?.slice(1);
|
||||
const technologies = [
|
||||
...new Set(
|
||||
[
|
||||
...(projectData.metadata?.technologies ?? []),
|
||||
...Object.values(projectData.targets ?? {})
|
||||
.map((target) => target?.metadata?.technologies)
|
||||
.flat(),
|
||||
].filter(Boolean)
|
||||
),
|
||||
] as string[];
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
collapseTarget: (targetName: string) => {
|
||||
targetRefs.current[targetName]?.current?.collapse();
|
||||
},
|
||||
expandTarget: (targetName: string) => {
|
||||
targetRefs.current[targetName]?.current?.expand();
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<header
|
||||
return (
|
||||
<>
|
||||
<header
|
||||
className={twMerge(
|
||||
`border-b border-slate-900/10 dark:border-slate-300/10`,
|
||||
isCompact ? 'mb-2' : 'mb-4'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
`border-b border-slate-900/10 dark:border-slate-300/10`,
|
||||
isCompact ? 'mb-2' : 'mb-4'
|
||||
`flex items-center justify-between`,
|
||||
isCompact ? `gap-1` : `mb-4 gap-2`
|
||||
)}
|
||||
>
|
||||
<h1
|
||||
className={twMerge(
|
||||
`flex items-center justify-between dark:text-slate-100`,
|
||||
isCompact ? `text-2xl gap-1` : `text-4xl mb-4 gap-2`
|
||||
)}
|
||||
>
|
||||
<span>{name}</span>
|
||||
<span>
|
||||
{onViewInProjectGraph ? (
|
||||
<button
|
||||
className="text-base cursor-pointer items-center inline-flex gap-2 text-slate-600 dark:text-slate-300 ring-2 ring-inset ring-slate-400/40 dark:ring-slate-400/30 hover:bg-slate-50 dark:hover:bg-slate-800/60 rounded-md py-1 px-2"
|
||||
onClick={() => onViewInProjectGraph({ projectName: name })}
|
||||
>
|
||||
<EyeIcon className="h-5 w-5 "></EyeIcon>
|
||||
<span>View In Graph</span>
|
||||
</button>
|
||||
) : null}{' '}
|
||||
</span>
|
||||
</h1>
|
||||
<div className="py-2 ">
|
||||
{projectData.tags && projectData.tags.length ? (
|
||||
<p>
|
||||
<span className="font-medium inline-block w-10">Tags:</span>
|
||||
{projectData.tags?.map((tag) => (
|
||||
<span className="ml-2 font-mono">
|
||||
<Pill text={tag} />
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
) : null}
|
||||
<p>
|
||||
<span className="font-medium inline-block w-10">Root:</span>
|
||||
<span className="font-mono"> {root}</span>
|
||||
</p>
|
||||
{displayType ? (
|
||||
<p>
|
||||
<span className="font-medium inline-block w-10">Type:</span>
|
||||
<span className="font-mono"> {displayType}</span>
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<h2 className={isCompact ? `text-lg mb-3` : `text-xl mb-4`}>
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={(<PropertyInfoTooltip type="targets" />) as any}
|
||||
<div className="flex items-center gap-2">
|
||||
<h1
|
||||
className={twMerge(
|
||||
`dark:text-slate-100`,
|
||||
isCompact ? `text-2xl` : `text-4xl`
|
||||
)}
|
||||
>
|
||||
<span className="text-slate-800 dark:text-slate-200">
|
||||
<TooltipTriggerText>Targets</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</h2>
|
||||
<ul>
|
||||
{projectTargets.sort(sortNxReleasePublishLast).map((targetName) => {
|
||||
const target = projectData.targets?.[targetName];
|
||||
return target && targetRefs.current[targetName] ? (
|
||||
<li className="mb-4 last:mb-0" key={`target-${targetName}`}>
|
||||
<TargetConfigurationDetails
|
||||
ref={targetRefs.current[targetName]}
|
||||
variant={variant}
|
||||
projectName={name}
|
||||
targetName={targetName}
|
||||
targetConfiguration={target}
|
||||
sourceMap={sourceMap}
|
||||
onRunTarget={onRunTarget}
|
||||
onViewInTaskGraph={onViewInTaskGraph}
|
||||
onCollapse={onTargetCollapse}
|
||||
onExpand={onTargetExpand}
|
||||
/>
|
||||
</li>
|
||||
) : null;
|
||||
})}
|
||||
</ul>
|
||||
{project.name}
|
||||
</h1>
|
||||
<TargetTechnologies
|
||||
technologies={technologies}
|
||||
showTooltip={true}
|
||||
/>
|
||||
</div>
|
||||
<span>
|
||||
{onViewInProjectGraph ? (
|
||||
<button
|
||||
className="inline-flex cursor-pointer items-center gap-2 rounded-md py-1 px-2 text-base text-slate-600 ring-2 ring-inset ring-slate-400/40 hover:bg-slate-50 dark:text-slate-300 dark:ring-slate-400/30 dark:hover:bg-slate-800/60"
|
||||
onClick={() =>
|
||||
onViewInProjectGraph({ projectName: project.name })
|
||||
}
|
||||
>
|
||||
<EyeIcon className="h-5 w-5 "></EyeIcon>
|
||||
<span>View In Graph</span>
|
||||
</button>
|
||||
) : null}{' '}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
<div className="py-2 ">
|
||||
{projectData.tags && projectData.tags.length ? (
|
||||
<p>
|
||||
<span className="inline-block w-10 font-medium">Tags:</span>
|
||||
{projectData.tags?.map((tag) => (
|
||||
<span className="ml-2 font-mono">
|
||||
<Pill text={tag} />
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
) : null}
|
||||
<p>
|
||||
<span className="inline-block w-10 font-medium">Root:</span>
|
||||
<span className="font-mono"> {projectData.root}</span>
|
||||
</p>
|
||||
{displayType ? (
|
||||
<p>
|
||||
<span className="inline-block w-10 font-medium">Type:</span>
|
||||
<span className="font-mono"> {displayType}</span>
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<h2 className={isCompact ? `mb-3 text-lg` : `mb-4 text-xl`}>
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={(<PropertyInfoTooltip type="targets" />) as any}
|
||||
>
|
||||
<span className="text-slate-800 dark:text-slate-200">
|
||||
<TooltipTriggerText>Targets</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</h2>
|
||||
|
||||
function sortNxReleasePublishLast(a: string, b: string) {
|
||||
if (a === 'nx-release-publish') return 1;
|
||||
if (b === 'nx-release-publish') return -1;
|
||||
return 1;
|
||||
}
|
||||
<TargetConfigurationDetailsList
|
||||
className="w-full"
|
||||
project={project}
|
||||
sourceMap={sourceMap}
|
||||
variant={variant}
|
||||
onRunTarget={onRunTarget}
|
||||
onViewInTaskGraph={onViewInTaskGraph}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectDetails;
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { SourceInfo } from './source-info';
|
||||
|
||||
const meta: Meta<typeof SourceInfo> = {
|
||||
component: SourceInfo,
|
||||
title: 'SourceInfo',
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof SourceInfo>;
|
||||
|
||||
export const Simple: Story = {
|
||||
args: {
|
||||
data: ['data1', 'data2'],
|
||||
propertyKey: 'test',
|
||||
},
|
||||
};
|
||||
@ -11,7 +11,7 @@ export function SourceInfo(props: {
|
||||
// Every other property within in the target has the form `target.${targetName}.${propertyName}
|
||||
const isTarget = props.propertyKey.split('.').length === 2;
|
||||
return (
|
||||
<span className="inline-flex shrink-1 min-w-0 items-center gap-2">
|
||||
<span className="inline-flex min-w-0 items-center gap-2">
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
strategy="fixed"
|
||||
@ -36,7 +36,7 @@ export function SourceInfo(props: {
|
||||
{/*</span>*/}
|
||||
<span
|
||||
className={twMerge(
|
||||
'italic text-sm min-w-0 truncate',
|
||||
'min-w-0 truncate text-sm italic',
|
||||
props.color ?? 'text-gray-500'
|
||||
)}
|
||||
>
|
||||
@ -0,0 +1,33 @@
|
||||
import { forwardRef } from 'react';
|
||||
import { TargetConfigurationGroupHeader } from '../target-configuration-details-group-header/target-configuration-details-group-header';
|
||||
|
||||
export interface TargetConfigurationGroupContainerProps {
|
||||
targetGroupName: string;
|
||||
targetsNumber: number;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const TargetConfigurationGroupContainer = forwardRef(
|
||||
(
|
||||
{
|
||||
targetGroupName,
|
||||
targetsNumber,
|
||||
children,
|
||||
}: TargetConfigurationGroupContainerProps,
|
||||
ref: React.Ref<any>
|
||||
) => {
|
||||
return (
|
||||
<div>
|
||||
<div ref={ref} className="mb-4 w-full">
|
||||
<TargetConfigurationGroupHeader
|
||||
targetGroupName={targetGroupName}
|
||||
targetsNumber={targetsNumber}
|
||||
/>
|
||||
<div className="rounded-md border border-slate-200 p-2 dark:border-slate-700">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
@ -0,0 +1,17 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { TargetConfigurationGroupHeader } from './target-configuration-details-group-header';
|
||||
|
||||
const meta: Meta<typeof TargetConfigurationGroupHeader> = {
|
||||
component: TargetConfigurationGroupHeader,
|
||||
title: 'TargetConfigurationGroupHeader',
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof TargetConfigurationGroupHeader>;
|
||||
|
||||
export const Simple: Story = {
|
||||
args: {
|
||||
targetGroupName: 'Target Group Name',
|
||||
targetsNumber: 5,
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,25 @@
|
||||
import { Pill } from '../pill';
|
||||
|
||||
export interface TargetConfigurationGroupHeaderProps {
|
||||
targetGroupName: string;
|
||||
targetsNumber: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const TargetConfigurationGroupHeader = ({
|
||||
targetGroupName,
|
||||
targetsNumber,
|
||||
className = '',
|
||||
}: TargetConfigurationGroupHeaderProps) => {
|
||||
return (
|
||||
<header className={`px-4 py-2 text-lg ${className}`}>
|
||||
{targetGroupName}{' '}
|
||||
<Pill
|
||||
text={
|
||||
targetsNumber.toString() +
|
||||
(targetsNumber === 1 ? ' target' : ' targets')
|
||||
}
|
||||
/>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,94 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import {
|
||||
TargetConfigurationGroupList,
|
||||
TargetConfigurationGroupListProps,
|
||||
} from './target-configuration-details-group-list';
|
||||
import { StoreDecorator } from '@nx/graph/state';
|
||||
|
||||
const meta: Meta<typeof TargetConfigurationGroupList> = {
|
||||
component: TargetConfigurationGroupList,
|
||||
title: 'TargetConfigurationGroupList',
|
||||
decorators: [StoreDecorator],
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof TargetConfigurationGroupList>;
|
||||
|
||||
export const OneTarget: Story = {
|
||||
args: {
|
||||
project: {
|
||||
name: 'react',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'libs/react',
|
||||
targets: {
|
||||
build: {
|
||||
executor: 'nx',
|
||||
options: {},
|
||||
configurations: {
|
||||
production: {
|
||||
executor: 'nx',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
lint: {
|
||||
executor: 'nx',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sourceMap: {
|
||||
react: ['react'],
|
||||
},
|
||||
variant: 'default',
|
||||
onRunTarget: () => {},
|
||||
onViewInTaskGraph: () => {},
|
||||
selectedTargetGroup: 'build',
|
||||
setExpandTargets: () => {},
|
||||
collapseAllTargets: () => {},
|
||||
} as TargetConfigurationGroupListProps,
|
||||
};
|
||||
|
||||
export const TwoTargets: Story = {
|
||||
args: {
|
||||
project: {
|
||||
name: 'react',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'libs/react',
|
||||
targets: {
|
||||
build1: {
|
||||
executor: 'nx',
|
||||
options: {},
|
||||
configurations: {
|
||||
production: {
|
||||
executor: 'nx',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
build2: {
|
||||
executor: 'nx',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
targetGroups: {
|
||||
build: ['build1', 'build2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sourceMap: {
|
||||
react: ['react'],
|
||||
},
|
||||
variant: 'default',
|
||||
onRunTarget: () => {},
|
||||
onViewInTaskGraph: () => {},
|
||||
selectedTargetGroup: 'build',
|
||||
setExpandTargets: () => {},
|
||||
collapseAllTargets: () => {},
|
||||
} as TargetConfigurationGroupListProps,
|
||||
};
|
||||
@ -0,0 +1,147 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import type { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
import { RefObject, createRef, useEffect, useRef, useState } from 'react';
|
||||
import { Transition } from '@headlessui/react';
|
||||
|
||||
import { TargetConfigurationDetailsListItem } from '../target-configuration-details-list-item/target-configuration-details-list-item';
|
||||
import { TargetConfigurationGroupContainer } from '../target-configuration-details-group-container/target-configuration-details-group-container';
|
||||
import { TargetConfigurationGroupHeader } from '../target-configuration-details-group-header/target-configuration-details-group-header';
|
||||
import { groupTargets } from '../utils/group-targets';
|
||||
|
||||
export interface TargetConfigurationGroupListProps {
|
||||
project: ProjectGraphProjectNode;
|
||||
sourceMap: Record<string, string[]>;
|
||||
variant?: 'default' | 'compact';
|
||||
onRunTarget?: (data: { projectName: string; targetName: string }) => void;
|
||||
onViewInTaskGraph?: (data: {
|
||||
projectName: string;
|
||||
targetName: string;
|
||||
}) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function TargetConfigurationGroupList({
|
||||
project,
|
||||
variant,
|
||||
sourceMap,
|
||||
onRunTarget,
|
||||
onViewInTaskGraph,
|
||||
className = '',
|
||||
}: TargetConfigurationGroupListProps) {
|
||||
const [stickyHeaderContent, setStickHeaderContent] = useState('');
|
||||
const targetsGroup = groupTargets(project);
|
||||
const targetGroupRefs = useRef(
|
||||
Object.keys(targetsGroup.groups).reduce((acc, targetGroupName) => {
|
||||
acc[targetGroupName] = createRef();
|
||||
return acc;
|
||||
}, {} as Record<string, RefObject<any>>)
|
||||
);
|
||||
const targetNameRefs = useRef(
|
||||
targetsGroup.targets.reduce((acc, targetName) => {
|
||||
acc[targetName] = createRef();
|
||||
return acc;
|
||||
}, {} as Record<string, RefObject<any>>)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', isSticky);
|
||||
return () => {
|
||||
window.removeEventListener('scroll', isSticky);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const isSticky = () => {
|
||||
const scrollTop = window.scrollY + 30; // 30px for the header
|
||||
const foundTargetGroup: string | undefined = Object.keys(
|
||||
targetGroupRefs.current
|
||||
).find((targetGroupName) => {
|
||||
const targetGroup = targetGroupRefs.current[targetGroupName];
|
||||
if (
|
||||
targetGroup &&
|
||||
targetGroup.current &&
|
||||
scrollTop >= targetGroup.current.offsetTop &&
|
||||
scrollTop <
|
||||
targetGroup.current.offsetTop + targetGroup.current.offsetHeight
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (foundTargetGroup) {
|
||||
setStickHeaderContent(foundTargetGroup);
|
||||
} else {
|
||||
setStickHeaderContent('');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition
|
||||
show={!!stickyHeaderContent}
|
||||
enter="transition-opacity ease-linear duration-100"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity ease-linear duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="fixed top-0 left-0 right-0 z-10 mb-8 border-b-2 border-slate-900/10 bg-slate-50 dark:border-slate-300/10 dark:bg-slate-800 dark:text-slate-300">
|
||||
<div className="mx-auto max-w-6xl px-8 pt-2">
|
||||
<TargetConfigurationGroupHeader
|
||||
targetGroupName={stickyHeaderContent}
|
||||
targetsNumber={
|
||||
project.data.metadata?.targetGroups?.[stickyHeaderContent]
|
||||
?.length ?? 0
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
{Object.entries(targetsGroup.groups).map(([targetGroupName, targets]) => {
|
||||
return (
|
||||
<TargetConfigurationGroupContainer
|
||||
ref={targetGroupRefs.current[targetGroupName]}
|
||||
targetGroupName={targetGroupName}
|
||||
targetsNumber={targets.length}
|
||||
key={targetGroupName}
|
||||
>
|
||||
<ul className={className}>
|
||||
{targets.map((targetName) => (
|
||||
<TargetConfigurationDetailsListItem
|
||||
ref={targetNameRefs.current[targetName]}
|
||||
project={project}
|
||||
sourceMap={sourceMap}
|
||||
variant={variant}
|
||||
onRunTarget={onRunTarget}
|
||||
onViewInTaskGraph={onViewInTaskGraph}
|
||||
targetName={targetName}
|
||||
collapsable={true}
|
||||
key={targetName}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</TargetConfigurationGroupContainer>
|
||||
);
|
||||
})}
|
||||
<ul className={`mt-8 p-2 ${className}`}>
|
||||
{targetsGroup.targets.map((targetName) => {
|
||||
return (
|
||||
<TargetConfigurationDetailsListItem
|
||||
ref={targetNameRefs.current[targetName]}
|
||||
project={project}
|
||||
sourceMap={sourceMap}
|
||||
variant={variant}
|
||||
onRunTarget={onRunTarget}
|
||||
onViewInTaskGraph={onViewInTaskGraph}
|
||||
targetName={targetName}
|
||||
collapsable={true}
|
||||
key={targetName}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import {
|
||||
TargetConfigurationDetailsHeader,
|
||||
TargetConfigurationDetailsHeaderProps,
|
||||
} from './target-configuration-details-header';
|
||||
|
||||
const meta: Meta<typeof TargetConfigurationDetailsHeader> = {
|
||||
component: TargetConfigurationDetailsHeader,
|
||||
title: 'TargetConfigurationDetailsHeader',
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof TargetConfigurationDetailsHeader>;
|
||||
|
||||
export const Compact: Story = {
|
||||
args: {
|
||||
isCollasped: true,
|
||||
toggleCollapse: () => {},
|
||||
collapsable: false,
|
||||
isCompact: true,
|
||||
targetConfiguration: {},
|
||||
projectName: 'jest',
|
||||
targetName: 'test',
|
||||
sourceMap: {},
|
||||
onRunTarget: () => {},
|
||||
onViewInTaskGraph: () => {},
|
||||
} as TargetConfigurationDetailsHeaderProps,
|
||||
};
|
||||
|
||||
export const NotCompact: Story = {
|
||||
args: {
|
||||
isCollasped: true,
|
||||
toggleCollapse: () => {},
|
||||
collapsable: false,
|
||||
isCompact: false,
|
||||
targetConfiguration: {},
|
||||
projectName: 'jest',
|
||||
targetName: 'test',
|
||||
sourceMap: {},
|
||||
onRunTarget: () => {},
|
||||
onViewInTaskGraph: () => {},
|
||||
} as TargetConfigurationDetailsHeaderProps,
|
||||
};
|
||||
|
||||
export const Expanded: Story = {
|
||||
args: {
|
||||
isCollasped: false,
|
||||
toggleCollapse: () => {},
|
||||
collapsable: true,
|
||||
isCompact: false,
|
||||
targetConfiguration: {},
|
||||
projectName: 'jest',
|
||||
targetName: 'test',
|
||||
sourceMap: {},
|
||||
onRunTarget: () => {},
|
||||
onViewInTaskGraph: () => {},
|
||||
} as TargetConfigurationDetailsHeaderProps,
|
||||
};
|
||||
|
||||
export const Collapsed: Story = {
|
||||
args: {
|
||||
isCollasped: true,
|
||||
toggleCollapse: () => {},
|
||||
collapsable: true,
|
||||
isCompact: false,
|
||||
targetConfiguration: {},
|
||||
projectName: 'jest',
|
||||
targetName: 'test',
|
||||
sourceMap: {},
|
||||
onRunTarget: () => {},
|
||||
onViewInTaskGraph: () => {},
|
||||
} as TargetConfigurationDetailsHeaderProps,
|
||||
};
|
||||
@ -0,0 +1,176 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import type { TargetConfiguration } from '@nx/devkit';
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
EyeIcon,
|
||||
PlayIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
|
||||
import { PropertyInfoTooltip, Tooltip } from '@nx/graph/ui-tooltips';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { Pill } from '../pill';
|
||||
import { TargetTechnologies } from '../target-technologies/target-technologies';
|
||||
import { SourceInfo } from '../source-info/source-info';
|
||||
import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard';
|
||||
|
||||
export interface TargetConfigurationDetailsHeaderProps {
|
||||
isCollasped: boolean;
|
||||
toggleCollapse?: () => void;
|
||||
collapsable: boolean;
|
||||
isCompact?: boolean;
|
||||
targetConfiguration: TargetConfiguration;
|
||||
projectName: string;
|
||||
targetName: string;
|
||||
sourceMap: Record<string, string[]>;
|
||||
onRunTarget?: (data: { projectName: string; targetName: string }) => void;
|
||||
onViewInTaskGraph?: (data: {
|
||||
projectName: string;
|
||||
targetName: string;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
export const TargetConfigurationDetailsHeader = ({
|
||||
isCollasped,
|
||||
toggleCollapse,
|
||||
collapsable,
|
||||
isCompact,
|
||||
targetConfiguration,
|
||||
projectName,
|
||||
targetName,
|
||||
sourceMap,
|
||||
onRunTarget,
|
||||
onViewInTaskGraph,
|
||||
}: TargetConfigurationDetailsHeaderProps) => {
|
||||
const handleCopyClick = async (copyText: string) => {
|
||||
await window.navigator.clipboard.writeText(copyText);
|
||||
};
|
||||
|
||||
if (!collapsable) {
|
||||
// when collapsable is false, isCollasped should be false
|
||||
isCollasped = false;
|
||||
}
|
||||
|
||||
const singleCommand =
|
||||
targetConfiguration.executor === 'nx:run-commands'
|
||||
? targetConfiguration.command ?? targetConfiguration.options?.command
|
||||
: null;
|
||||
|
||||
return (
|
||||
<header
|
||||
className={twMerge(
|
||||
`group hover:bg-slate-50 dark:hover:bg-slate-800/60`,
|
||||
collapsable ? 'cursor-pointer' : '',
|
||||
isCompact ? 'px-2 py-1' : 'p-2',
|
||||
!isCollasped || !collapsable
|
||||
? 'border-b bg-slate-50 dark:border-slate-700/60 dark:border-slate-300/10 dark:bg-slate-800 '
|
||||
: ''
|
||||
)}
|
||||
onClick={collapsable ? toggleCollapse : undefined}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||
{collapsable &&
|
||||
(isCollasped ? (
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
) : (
|
||||
<ChevronUpIcon className="h-3 w-3" />
|
||||
))}
|
||||
<h3 className="font-medium dark:text-slate-300">{targetName}</h3>
|
||||
<TargetTechnologies
|
||||
technologies={targetConfiguration.metadata?.technologies}
|
||||
showTooltip={!isCollasped}
|
||||
/>
|
||||
{isCollasped &&
|
||||
targetConfiguration?.executor !== '@nx/js:release-publish' && (
|
||||
<p className="min-w-0 flex-1 truncate text-sm text-slate-400">
|
||||
{singleCommand ? singleCommand : targetConfiguration.executor}
|
||||
</p>
|
||||
)}
|
||||
{targetName === 'nx-release-publish' && (
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
strategy="fixed"
|
||||
content={(<PropertyInfoTooltip type="release" />) as any}
|
||||
>
|
||||
<span className="inline-flex">
|
||||
<Pill text="nx release" color="grey" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
{targetConfiguration.cache && (
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
strategy="fixed"
|
||||
content={(<PropertyInfoTooltip type="cacheable" />) as any}
|
||||
>
|
||||
<span className="inline-flex">
|
||||
<Pill text="Cacheable" color="green" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{onViewInTaskGraph && (
|
||||
<button
|
||||
className="rounded-md bg-inherit p-1 text-sm text-slate-600 ring-1 ring-inset ring-slate-400/40 hover:bg-slate-200 dark:text-slate-300 dark:ring-slate-400/30 dark:hover:bg-slate-700/60"
|
||||
// TODO: fix tooltip overflow in collapsed state
|
||||
data-tooltip={isCollasped ? false : 'View in Task Graph'}
|
||||
data-tooltip-align-right
|
||||
>
|
||||
<EyeIcon
|
||||
className={`h-5 w-5 !cursor-pointer`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onViewInTaskGraph({ projectName, targetName });
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{onRunTarget && (
|
||||
<span
|
||||
className="rounded-md bg-inherit p-1 text-sm text-slate-600 ring-1 ring-inset ring-slate-400/40 hover:bg-slate-200 dark:text-slate-300 dark:ring-slate-400/30 dark:hover:bg-slate-700/60"
|
||||
// TODO: fix tooltip overflow in collapsed state
|
||||
data-tooltip={isCollasped ? false : 'Run Target'}
|
||||
data-tooltip-align-right
|
||||
>
|
||||
<PlayIcon
|
||||
className="h-5 w-5 !cursor-pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRunTarget({ projectName, targetName });
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!isCollasped && (
|
||||
<div className="mt-2 ml-5 text-sm">
|
||||
<SourceInfo
|
||||
data={sourceMap[`targets.${targetName}`]}
|
||||
propertyKey={`targets.${targetName}`}
|
||||
color="text-gray-500 dark:text-slate-400"
|
||||
/>
|
||||
{targetName !== 'nx-release-publish' && (
|
||||
<div className="text-right">
|
||||
<code className="ml-4 rounded bg-gray-100 px-2 py-1 font-mono text-gray-800 dark:bg-gray-700 dark:text-gray-300">
|
||||
nx run {projectName}:{targetName}
|
||||
</code>
|
||||
<span>
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(`nx run ${projectName}:${targetName}`)
|
||||
}
|
||||
tooltipAlignment="right"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,140 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import {
|
||||
TargetConfigurationDetailsListItem,
|
||||
TargetConfigurationDetailsListItemProps,
|
||||
} from './target-configuration-details-list-item';
|
||||
import { StoreDecorator } from '@nx/graph/state';
|
||||
|
||||
const meta: Meta<typeof TargetConfigurationDetailsListItem> = {
|
||||
component: TargetConfigurationDetailsListItem,
|
||||
title: 'TargetConfigurationDetailsListItem',
|
||||
decorators: [StoreDecorator],
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof TargetConfigurationDetailsListItem>;
|
||||
|
||||
export const Simple: Story = {
|
||||
args: {
|
||||
isCollasped: true,
|
||||
toggleCollapse: () => {},
|
||||
collapsable: false,
|
||||
isCompact: true,
|
||||
targetConfiguration: {},
|
||||
projectName: 'jest',
|
||||
targetName: 'test',
|
||||
sourceMap: {},
|
||||
onRunTarget: () => {},
|
||||
onViewInTaskGraph: () => {},
|
||||
project: {
|
||||
name: 'jest',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'packages/jest',
|
||||
name: 'jest',
|
||||
targets: {
|
||||
'nx-release-publish': {
|
||||
dependsOn: ['^nx-release-publish'],
|
||||
executor: '@nx/js:release-publish',
|
||||
options: { packageRoot: 'build/packages/jest' },
|
||||
configurations: {},
|
||||
},
|
||||
test: {
|
||||
dependsOn: ['test-native', 'build-native', '^build-native'],
|
||||
inputs: [
|
||||
'default',
|
||||
'^production',
|
||||
'{workspaceRoot}/jest.preset.js',
|
||||
],
|
||||
executor: '@nx/jest:jest',
|
||||
outputs: ['{workspaceRoot}/coverage/{projectRoot}'],
|
||||
cache: true,
|
||||
options: {
|
||||
jestConfig: 'packages/jest/jest.config.ts',
|
||||
passWithNoTests: true,
|
||||
},
|
||||
configurations: {},
|
||||
},
|
||||
'build-base': {
|
||||
dependsOn: ['^build-base', 'build-native'],
|
||||
inputs: ['production', '^production'],
|
||||
executor: '@nx/js:tsc',
|
||||
outputs: ['{options.outputPath}'],
|
||||
cache: true,
|
||||
options: {
|
||||
outputPath: 'build/packages/jest',
|
||||
tsConfig: 'packages/jest/tsconfig.lib.json',
|
||||
main: 'packages/jest/index.ts',
|
||||
assets: [
|
||||
{
|
||||
input: 'packages/jest',
|
||||
glob: '**/@(files|files-angular)/**',
|
||||
output: '/',
|
||||
},
|
||||
{
|
||||
input: 'packages/jest',
|
||||
glob: '**/files/**/.gitkeep',
|
||||
output: '/',
|
||||
},
|
||||
{
|
||||
input: 'packages/jest',
|
||||
glob: '**/*.json',
|
||||
ignore: [
|
||||
'**/tsconfig*.json',
|
||||
'project.json',
|
||||
'.eslintrc.json',
|
||||
],
|
||||
output: '/',
|
||||
},
|
||||
{
|
||||
input: 'packages/jest',
|
||||
glob: '**/*.js',
|
||||
ignore: ['**/jest.config.js'],
|
||||
output: '/',
|
||||
},
|
||||
{ input: 'packages/jest', glob: '**/*.d.ts', output: '/' },
|
||||
{ input: '', glob: 'LICENSE', output: '/' },
|
||||
],
|
||||
},
|
||||
configurations: {},
|
||||
},
|
||||
build: {
|
||||
dependsOn: ['build-base', 'build-native'],
|
||||
inputs: ['production', '^production'],
|
||||
cache: true,
|
||||
executor: 'nx:run-commands',
|
||||
outputs: ['{workspaceRoot}/build/packages/jest'],
|
||||
options: { command: 'node ./scripts/copy-readme.js jest' },
|
||||
configurations: {},
|
||||
},
|
||||
'add-extra-dependencies': {
|
||||
executor: 'nx:run-commands',
|
||||
options: {
|
||||
command:
|
||||
'node ./scripts/add-dependency-to-build.js jest @nrwl/jest',
|
||||
},
|
||||
configurations: {},
|
||||
},
|
||||
lint: {
|
||||
dependsOn: ['build-native', '^build-native'],
|
||||
inputs: [
|
||||
'default',
|
||||
'{workspaceRoot}/.eslintrc.json',
|
||||
'{workspaceRoot}/tools/eslint-rules/**/*',
|
||||
],
|
||||
executor: '@nx/eslint:lint',
|
||||
outputs: ['{options.outputFile}'],
|
||||
cache: true,
|
||||
options: { lintFilePatterns: ['packages/jest'] },
|
||||
configurations: {},
|
||||
},
|
||||
},
|
||||
$schema: '../../node_modules/nx/schemas/project-schema.json',
|
||||
sourceRoot: 'packages/jest',
|
||||
projectType: 'library',
|
||||
implicitDependencies: [],
|
||||
tags: [],
|
||||
},
|
||||
},
|
||||
} as TargetConfigurationDetailsListItemProps,
|
||||
};
|
||||
@ -0,0 +1,52 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import type { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
import { forwardRef, Ref } from 'react';
|
||||
import TargetConfigurationDetails from '../target-configuration-details/target-configuration-details';
|
||||
|
||||
export interface TargetConfigurationDetailsListItemProps {
|
||||
project: ProjectGraphProjectNode;
|
||||
sourceMap: Record<string, string[]>;
|
||||
variant?: 'default' | 'compact';
|
||||
onRunTarget?: (data: { projectName: string; targetName: string }) => void;
|
||||
onViewInTaskGraph?: (data: {
|
||||
projectName: string;
|
||||
targetName: string;
|
||||
}) => void;
|
||||
targetName: string;
|
||||
collapsable: boolean;
|
||||
}
|
||||
|
||||
export const TargetConfigurationDetailsListItem = forwardRef(
|
||||
(
|
||||
{
|
||||
project,
|
||||
variant,
|
||||
sourceMap,
|
||||
onRunTarget,
|
||||
onViewInTaskGraph,
|
||||
targetName,
|
||||
collapsable,
|
||||
}: TargetConfigurationDetailsListItemProps,
|
||||
ref: Ref<HTMLLIElement>
|
||||
) => {
|
||||
const target = project.data.targets?.[targetName];
|
||||
if (!target) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<li className="mb-4 last:mb-0" key={`target-${targetName}`} ref={ref}>
|
||||
<TargetConfigurationDetails
|
||||
variant={variant}
|
||||
projectName={project.name}
|
||||
targetName={targetName}
|
||||
targetConfiguration={target}
|
||||
sourceMap={sourceMap}
|
||||
onRunTarget={onRunTarget}
|
||||
onViewInTaskGraph={onViewInTaskGraph}
|
||||
collapsable={collapsable}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
);
|
||||
@ -0,0 +1,28 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import { AppDispatch, RootState, expandTargetActions } from '@nx/graph/state';
|
||||
|
||||
const mapStateToProps = (state: RootState) => {
|
||||
return {};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch: AppDispatch) => {
|
||||
return {
|
||||
setExpandTargets(targets: string[]) {
|
||||
dispatch(expandTargetActions.setExpandTargets(targets));
|
||||
},
|
||||
collapseAllTargets() {
|
||||
dispatch(expandTargetActions.collapseAllTargets());
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type mapStateToPropsType = ReturnType<typeof mapStateToProps>;
|
||||
type mapDispatchToPropsType = ReturnType<typeof mapDispatchToProps>;
|
||||
|
||||
export {
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
mapStateToPropsType,
|
||||
mapDispatchToPropsType,
|
||||
};
|
||||
@ -0,0 +1,94 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import {
|
||||
TargetConfigurationDetailsListComponent,
|
||||
TargetConfigurationDetailsListProps,
|
||||
} from './target-configuration-details-list';
|
||||
import { StoreDecorator } from '@nx/graph/state';
|
||||
|
||||
const meta: Meta<typeof TargetConfigurationDetailsListComponent> = {
|
||||
component: TargetConfigurationDetailsListComponent,
|
||||
title: 'TargetConfigurationDetailsListComponent',
|
||||
decorators: [StoreDecorator],
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof TargetConfigurationDetailsListComponent>;
|
||||
|
||||
export const OneTarget: Story = {
|
||||
args: {
|
||||
project: {
|
||||
name: 'react',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'libs/react',
|
||||
targets: {
|
||||
build: {
|
||||
executor: 'nx',
|
||||
options: {},
|
||||
configurations: {
|
||||
production: {
|
||||
executor: 'nx',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
lint: {
|
||||
executor: 'nx',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sourceMap: {
|
||||
react: ['react'],
|
||||
},
|
||||
variant: 'default',
|
||||
onRunTarget: () => {},
|
||||
onViewInTaskGraph: () => {},
|
||||
selectedTargetGroup: 'build',
|
||||
setExpandTargets: () => {},
|
||||
collapseAllTargets: () => {},
|
||||
} as TargetConfigurationDetailsListProps,
|
||||
};
|
||||
|
||||
export const TwoTargets: Story = {
|
||||
args: {
|
||||
project: {
|
||||
name: 'react',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'libs/react',
|
||||
targets: {
|
||||
build1: {
|
||||
executor: 'nx',
|
||||
options: {},
|
||||
configurations: {
|
||||
production: {
|
||||
executor: 'nx',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
build2: {
|
||||
executor: 'nx',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
targetGroups: {
|
||||
build: ['build1', 'build2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sourceMap: {
|
||||
react: ['react'],
|
||||
},
|
||||
variant: 'default',
|
||||
onRunTarget: () => {},
|
||||
onViewInTaskGraph: () => {},
|
||||
selectedTargetGroup: 'build',
|
||||
setExpandTargets: () => {},
|
||||
collapseAllTargets: () => {},
|
||||
} as TargetConfigurationDetailsListProps,
|
||||
};
|
||||
@ -0,0 +1,50 @@
|
||||
import { connect } from 'react-redux';
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import type { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
import {
|
||||
mapDispatchToProps,
|
||||
mapDispatchToPropsType,
|
||||
mapStateToProps,
|
||||
mapStateToPropsType,
|
||||
} from './target-configuration-details-list.state';
|
||||
import { TargetConfigurationGroupList } from '../target-configuration-details-group-list/target-configuration-details-group-list';
|
||||
|
||||
export type TargetConfigurationDetailsListProps = mapStateToPropsType &
|
||||
mapDispatchToPropsType & {
|
||||
project: ProjectGraphProjectNode;
|
||||
sourceMap: Record<string, string[]>;
|
||||
variant?: 'default' | 'compact';
|
||||
onRunTarget?: (data: { projectName: string; targetName: string }) => void;
|
||||
onViewInTaskGraph?: (data: {
|
||||
projectName: string;
|
||||
targetName: string;
|
||||
}) => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function TargetConfigurationDetailsListComponent({
|
||||
project,
|
||||
variant,
|
||||
sourceMap,
|
||||
onRunTarget,
|
||||
onViewInTaskGraph,
|
||||
className,
|
||||
}: TargetConfigurationDetailsListProps) {
|
||||
return (
|
||||
<TargetConfigurationGroupList
|
||||
project={project}
|
||||
sourceMap={sourceMap}
|
||||
variant={variant}
|
||||
onRunTarget={onRunTarget}
|
||||
onViewInTaskGraph={onViewInTaskGraph}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const TargetConfigurationDetailsList = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TargetConfigurationDetailsListComponent);
|
||||
export default TargetConfigurationDetailsList;
|
||||
@ -0,0 +1,36 @@
|
||||
import {
|
||||
AppDispatch,
|
||||
RootState,
|
||||
expandTargetActions,
|
||||
getExpandedTargets,
|
||||
} from '@nx/graph/state';
|
||||
|
||||
const mapStateToProps = (state: RootState) => {
|
||||
return {
|
||||
expandedTargets: getExpandedTargets(state),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch: AppDispatch) => {
|
||||
return {
|
||||
expandTarget(target: string) {
|
||||
dispatch(expandTargetActions.expandTarget(target));
|
||||
},
|
||||
collapseTarget(target: string) {
|
||||
dispatch(expandTargetActions.collapseTarget(target));
|
||||
},
|
||||
toggleExpandTarget(target: string) {
|
||||
dispatch(expandTargetActions.toggleExpandTarget(target));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type mapStateToPropsType = ReturnType<typeof mapStateToProps>;
|
||||
type mapDispatchToPropsType = ReturnType<typeof mapDispatchToProps>;
|
||||
|
||||
export {
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
mapStateToPropsType,
|
||||
mapDispatchToPropsType,
|
||||
};
|
||||
@ -1,547 +1,429 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
EyeIcon,
|
||||
PlayIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import type { TargetConfiguration } from '@nx/devkit';
|
||||
|
||||
// nx-ignore-next-line
|
||||
import { TargetConfiguration } from '@nx/devkit';
|
||||
import { JsonCodeBlock } from '@nx/graph/ui-code-block';
|
||||
import {
|
||||
ForwardedRef,
|
||||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { SourceInfo } from './source-info';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { SourceInfo } from '../source-info/source-info';
|
||||
import { FadingCollapsible } from './fading-collapsible';
|
||||
import { TargetConfigurationProperty } from './target-configuration-property';
|
||||
import { selectSourceInfo } from './target-configuration-details.util';
|
||||
import { CopyToClipboard } from './copy-to-clipboard';
|
||||
import { CopyToClipboard } from '../copy-to-clipboard/copy-to-clipboard';
|
||||
import {
|
||||
ExternalLink,
|
||||
PropertyInfoTooltip,
|
||||
Tooltip,
|
||||
} from '@nx/graph/ui-tooltips';
|
||||
import { TooltipTriggerText } from './tooltip-trigger-text';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { Pill } from '../pill';
|
||||
import {
|
||||
mapDispatchToProps,
|
||||
mapStateToProps,
|
||||
mapDispatchToPropsType,
|
||||
mapStateToPropsType,
|
||||
} from './target-configuration-details.state';
|
||||
import { connect } from 'react-redux';
|
||||
import { TargetConfigurationDetailsHeader } from '../target-configuration-details-header/target-configuration-details-header';
|
||||
|
||||
/* eslint-disable-next-line */
|
||||
export interface TargetProps {
|
||||
projectName: string;
|
||||
targetName: string;
|
||||
targetConfiguration: TargetConfiguration;
|
||||
sourceMap: Record<string, string[]>;
|
||||
variant?: 'default' | 'compact';
|
||||
onCollapse?: (targetName: string) => void;
|
||||
onExpand?: (targetName: string) => void;
|
||||
onRunTarget?: (data: { projectName: string; targetName: string }) => void;
|
||||
onViewInTaskGraph?: (data: {
|
||||
type TargetConfigurationDetailsProps = mapStateToPropsType &
|
||||
mapDispatchToPropsType & {
|
||||
projectName: string;
|
||||
targetName: string;
|
||||
}) => void;
|
||||
}
|
||||
targetConfiguration: TargetConfiguration;
|
||||
sourceMap: Record<string, string[]>;
|
||||
variant?: 'default' | 'compact';
|
||||
onCollapse?: (targetName: string) => void;
|
||||
onExpand?: (targetName: string) => void;
|
||||
onRunTarget?: (data: { projectName: string; targetName: string }) => void;
|
||||
onViewInTaskGraph?: (data: {
|
||||
projectName: string;
|
||||
targetName: string;
|
||||
}) => void;
|
||||
collapsable: boolean;
|
||||
};
|
||||
|
||||
export interface TargetConfigurationDetailsHandle {
|
||||
collapse: () => void;
|
||||
expand: () => void;
|
||||
}
|
||||
export const TargetConfigurationDetailsComponent = ({
|
||||
variant,
|
||||
projectName,
|
||||
targetName,
|
||||
targetConfiguration,
|
||||
sourceMap,
|
||||
onViewInTaskGraph,
|
||||
onRunTarget,
|
||||
expandedTargets,
|
||||
toggleExpandTarget,
|
||||
collapsable,
|
||||
}: TargetConfigurationDetailsProps) => {
|
||||
const isCompact = variant === 'compact';
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
|
||||
export const TargetConfigurationDetails = forwardRef(
|
||||
(
|
||||
{
|
||||
variant,
|
||||
projectName,
|
||||
targetName,
|
||||
targetConfiguration,
|
||||
sourceMap,
|
||||
onExpand,
|
||||
onCollapse,
|
||||
onViewInTaskGraph,
|
||||
onRunTarget,
|
||||
}: TargetProps,
|
||||
ref: ForwardedRef<TargetConfigurationDetailsHandle>
|
||||
) => {
|
||||
const isCompact = variant === 'compact';
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const handleCopyClick = async (copyText: string) => {
|
||||
await window.navigator.clipboard.writeText(copyText);
|
||||
};
|
||||
|
||||
const handleCopyClick = async (copyText: string) => {
|
||||
await window.navigator.clipboard.writeText(copyText);
|
||||
};
|
||||
const handleCollapseToggle = useCallback(() => {
|
||||
toggleExpandTarget(targetName);
|
||||
}, [toggleExpandTarget, targetName]);
|
||||
|
||||
const handleCollapseToggle = useCallback(
|
||||
() => setCollapsed((collapsed) => !collapsed),
|
||||
[setCollapsed]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (collapsed) {
|
||||
onCollapse?.(targetName);
|
||||
} else {
|
||||
onExpand?.(targetName);
|
||||
}
|
||||
}, [collapsed, onCollapse, onExpand, projectName, targetName]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
collapse: () => {
|
||||
!collapsed && setCollapsed(true);
|
||||
},
|
||||
expand: () => {
|
||||
collapsed && setCollapsed(false);
|
||||
},
|
||||
}));
|
||||
|
||||
let executorLink: string | null = null;
|
||||
|
||||
// TODO: Handle this better because this will not work with labs
|
||||
if (targetConfiguration.executor?.startsWith('@nx/')) {
|
||||
const packageName = targetConfiguration.executor
|
||||
.split('/')[1]
|
||||
.split(':')[0];
|
||||
const executorName = targetConfiguration.executor
|
||||
.split('/')[1]
|
||||
.split(':')[1];
|
||||
executorLink = `https://nx.dev/nx-api/${packageName}/executors/${executorName}`;
|
||||
} else if (targetConfiguration.executor === 'nx:run-commands') {
|
||||
executorLink = `https://nx.dev/nx-api/nx/executors/run-commands`;
|
||||
} else if (targetConfiguration.executor === 'nx:run-script') {
|
||||
executorLink = `https://nx.dev/nx-api/nx/executors/run-script`;
|
||||
useEffect(() => {
|
||||
if (!collapsable) {
|
||||
setCollapsed(false);
|
||||
return;
|
||||
}
|
||||
if (expandedTargets.includes(targetName)) {
|
||||
setCollapsed(false);
|
||||
} else {
|
||||
setCollapsed(true);
|
||||
}
|
||||
}, [expandedTargets, targetName, collapsable]);
|
||||
|
||||
const singleCommand =
|
||||
targetConfiguration.executor === 'nx:run-commands'
|
||||
? targetConfiguration.command ?? targetConfiguration.options?.command
|
||||
: null;
|
||||
const options = useMemo(() => {
|
||||
if (singleCommand) {
|
||||
const { command, ...rest } = targetConfiguration.options;
|
||||
return rest;
|
||||
} else {
|
||||
return targetConfiguration.options;
|
||||
}
|
||||
}, [targetConfiguration.options, singleCommand]);
|
||||
let executorLink: string | null = null;
|
||||
|
||||
const configurations = targetConfiguration.configurations;
|
||||
// TODO: Handle this better because this will not work with labs
|
||||
if (targetConfiguration.executor?.startsWith('@nx/')) {
|
||||
const packageName = targetConfiguration.executor
|
||||
.split('/')[1]
|
||||
.split(':')[0];
|
||||
const executorName = targetConfiguration.executor
|
||||
.split('/')[1]
|
||||
.split(':')[1];
|
||||
executorLink = `https://nx.dev/nx-api/${packageName}/executors/${executorName}`;
|
||||
} else if (targetConfiguration.executor === 'nx:run-commands') {
|
||||
executorLink = `https://nx.dev/nx-api/nx/executors/run-commands`;
|
||||
} else if (targetConfiguration.executor === 'nx:run-script') {
|
||||
executorLink = `https://nx.dev/nx-api/nx/executors/run-script`;
|
||||
}
|
||||
|
||||
const shouldRenderOptions =
|
||||
options &&
|
||||
(typeof options === 'object' ? Object.keys(options).length : true);
|
||||
const singleCommand =
|
||||
targetConfiguration.executor === 'nx:run-commands'
|
||||
? targetConfiguration.command ?? targetConfiguration.options?.command
|
||||
: null;
|
||||
const options = useMemo(() => {
|
||||
if (singleCommand) {
|
||||
const { command, ...rest } = targetConfiguration.options;
|
||||
return rest;
|
||||
} else {
|
||||
return targetConfiguration.options;
|
||||
}
|
||||
}, [targetConfiguration.options, singleCommand]);
|
||||
|
||||
const shouldRenderConfigurations =
|
||||
configurations &&
|
||||
(typeof configurations === 'object'
|
||||
? Object.keys(configurations).length
|
||||
: true);
|
||||
const configurations = targetConfiguration.configurations;
|
||||
|
||||
return (
|
||||
<div className="relative overflow-hidden rounded-md border border-slate-200 dark:border-slate-700/60">
|
||||
<header
|
||||
className={twMerge(
|
||||
`group cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-800/60`,
|
||||
isCompact ? 'px-2 py-1' : 'p-2',
|
||||
!collapsed
|
||||
? 'border-b bg-slate-50 dark:border-slate-700/60 dark:border-slate-300/10 dark:bg-slate-800/60 '
|
||||
: ''
|
||||
)}
|
||||
onClick={handleCollapseToggle}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{collapsed ? (
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
const shouldRenderOptions =
|
||||
options &&
|
||||
(typeof options === 'object' ? Object.keys(options).length : true);
|
||||
|
||||
const shouldRenderConfigurations =
|
||||
configurations &&
|
||||
(typeof configurations === 'object'
|
||||
? Object.keys(configurations).length
|
||||
: true);
|
||||
|
||||
return (
|
||||
<div className="relative overflow-hidden rounded-md border border-slate-200 dark:border-slate-700/60">
|
||||
<TargetConfigurationDetailsHeader
|
||||
isCollasped={collapsed}
|
||||
toggleCollapse={handleCollapseToggle}
|
||||
collapsable={collapsable}
|
||||
isCompact={isCompact}
|
||||
targetConfiguration={targetConfiguration}
|
||||
projectName={projectName}
|
||||
targetName={targetName}
|
||||
sourceMap={sourceMap}
|
||||
onRunTarget={onRunTarget}
|
||||
onViewInTaskGraph={onViewInTaskGraph}
|
||||
/>
|
||||
{/* body */}
|
||||
{!collapsed && (
|
||||
<div className="p-4 text-base">
|
||||
<div className="group mb-4">
|
||||
<h4 className="mb-4">
|
||||
{singleCommand ? (
|
||||
<span className="font-medium">
|
||||
Command
|
||||
<span className="ml-2 mb-1 hidden group-hover:inline">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(`"command": "${singleCommand}"`)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
) : (
|
||||
<ChevronUpIcon className="h-3 w-3" />
|
||||
)}
|
||||
<h3 className="font-medium dark:text-slate-300">{targetName}</h3>
|
||||
{collapsed &&
|
||||
targetConfiguration?.executor !== '@nx/js:release-publish' && (
|
||||
<p className="text-sm text-slate-400">
|
||||
{singleCommand
|
||||
? singleCommand
|
||||
: targetConfiguration.executor}
|
||||
</p>
|
||||
)}
|
||||
{targetName === 'nx-release-publish' && (
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
strategy="fixed"
|
||||
content={(<PropertyInfoTooltip type="release" />) as any}
|
||||
content={(<PropertyInfoTooltip type="executors" />) as any}
|
||||
>
|
||||
<span className="inline-flex">
|
||||
<Pill text="nx release" color="grey" />
|
||||
<span className="font-medium">
|
||||
<TooltipTriggerText>Executor</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
{targetConfiguration.cache && (
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
strategy="fixed"
|
||||
content={(<PropertyInfoTooltip type="cacheable" />) as any}
|
||||
>
|
||||
<span className="inline-flex">
|
||||
<Pill text="Cacheable" color="green" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{onViewInTaskGraph && (
|
||||
<button
|
||||
className="rounded-md bg-inherit p-1 text-sm text-slate-600 ring-1 ring-inset ring-slate-400/40 hover:bg-slate-200 dark:text-slate-300 dark:ring-slate-400/30 dark:hover:bg-slate-700/60"
|
||||
// TODO: fix tooltip overflow in collapsed state
|
||||
data-tooltip={collapsed ? false : 'View in Task Graph'}
|
||||
data-tooltip-align-right
|
||||
>
|
||||
<EyeIcon
|
||||
className={`h-5 w-5 !cursor-pointer`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onViewInTaskGraph({ projectName, targetName });
|
||||
}}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
|
||||
{onRunTarget && (
|
||||
<span
|
||||
className="rounded-md bg-inherit p-1 text-sm text-slate-600 ring-1 ring-inset ring-slate-400/40 hover:bg-slate-200 dark:text-slate-300 dark:ring-slate-400/30 dark:hover:bg-slate-700/60"
|
||||
// TODO: fix tooltip overflow in collapsed state
|
||||
data-tooltip={collapsed ? false : 'Run Target'}
|
||||
data-tooltip-align-right
|
||||
>
|
||||
<PlayIcon
|
||||
className="h-5 w-5 !cursor-pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRunTarget({ projectName, targetName });
|
||||
}}
|
||||
</h4>
|
||||
<p className="pl-5 font-mono">
|
||||
{executorLink ? (
|
||||
<span>
|
||||
<ExternalLink
|
||||
href={executorLink ?? 'https://nx.dev/nx-api'}
|
||||
text={
|
||||
singleCommand
|
||||
? singleCommand
|
||||
: targetConfiguration.executor
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
) : singleCommand ? (
|
||||
singleCommand
|
||||
) : (
|
||||
targetConfiguration.executor
|
||||
)}
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
{!collapsed && (
|
||||
<div className="mt-2 ml-5 flex items-center text-sm">
|
||||
<span className="flex min-w-0 flex-1 items-center">
|
||||
<SourceInfo
|
||||
data={sourceMap[`targets.${targetName}`]}
|
||||
propertyKey={`targets.${targetName}`}
|
||||
color="text-gray-500 dark:text-slate-400"
|
||||
/>
|
||||
</span>
|
||||
{targetName !== 'nx-release-publish' && (
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="ml-4 rounded bg-gray-100 px-2 py-1 font-mono text-gray-800 dark:bg-gray-700 dark:text-gray-300">
|
||||
nx run {projectName}:{targetName}
|
||||
</code>
|
||||
<span>
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(`nx run ${projectName}:${targetName}`)
|
||||
}
|
||||
tooltipAlignment="right"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
{/* body */}
|
||||
{!collapsed && (
|
||||
<div className="p-4 text-base">
|
||||
<div className="group mb-4">
|
||||
|
||||
{targetConfiguration.inputs && (
|
||||
<div className="group">
|
||||
<h4 className="mb-4">
|
||||
{singleCommand ? (
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={(<PropertyInfoTooltip type="inputs" />) as any}
|
||||
>
|
||||
<span className="font-medium">
|
||||
Command
|
||||
<span className="ml-2 mb-1 hidden group-hover:inline">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(`"command": "${singleCommand}"`)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
<TooltipTriggerText>Inputs</TooltipTriggerText>
|
||||
</span>
|
||||
) : (
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={(<PropertyInfoTooltip type="executors" />) as any}
|
||||
>
|
||||
<span className="font-medium">
|
||||
<TooltipTriggerText>Executor</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Tooltip>
|
||||
<span className="ml-2 mb-1 hidden group-hover:inline">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"inputs": ${JSON.stringify(
|
||||
targetConfiguration.inputs
|
||||
)}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
<p className="pl-5 font-mono">
|
||||
{executorLink ? (
|
||||
<span>
|
||||
<ExternalLink
|
||||
href={executorLink ?? 'https://nx.dev/nx-api'}
|
||||
text={
|
||||
singleCommand
|
||||
? singleCommand
|
||||
: targetConfiguration.executor
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
) : singleCommand ? (
|
||||
singleCommand
|
||||
) : (
|
||||
targetConfiguration.executor
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{targetConfiguration.inputs && (
|
||||
<div className="group">
|
||||
<h4 className="mb-4">
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={(<PropertyInfoTooltip type="inputs" />) as any}
|
||||
>
|
||||
<span className="font-medium">
|
||||
<TooltipTriggerText>Inputs</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="ml-2 mb-1 hidden group-hover:inline">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"inputs": ${JSON.stringify(
|
||||
targetConfiguration.inputs
|
||||
)}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
<ul className="mb-4 list-disc pl-5">
|
||||
{targetConfiguration.inputs.map((input, idx) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.inputs`
|
||||
);
|
||||
return (
|
||||
<li
|
||||
className="group/line overflow-hidden whitespace-nowrap"
|
||||
key={`input-${idx}`}
|
||||
>
|
||||
<TargetConfigurationProperty data={input}>
|
||||
{sourceInfo && (
|
||||
<span className="shrink-1 inline flex min-w-0 pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.inputs`}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</TargetConfigurationProperty>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{targetConfiguration.outputs && (
|
||||
<div className="group">
|
||||
<h4 className="mb-4">
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={(<PropertyInfoTooltip type="outputs" />) as any}
|
||||
>
|
||||
<span className="font-medium">
|
||||
<TooltipTriggerText>Outputs</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="ml-2 mb-1 hidden group-hover:inline">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"outputs": ${JSON.stringify(
|
||||
targetConfiguration.outputs
|
||||
)}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
<ul className="mb-4 list-disc pl-5">
|
||||
{targetConfiguration.outputs?.map((output, idx) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.outputs`
|
||||
);
|
||||
return (
|
||||
<li
|
||||
className="group/line overflow-hidden whitespace-nowrap"
|
||||
key={`output-${idx}`}
|
||||
>
|
||||
<TargetConfigurationProperty data={output}>
|
||||
{sourceInfo && (
|
||||
<span className="shrink-1 inline flex min-w-0 pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.outputs`}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</TargetConfigurationProperty>
|
||||
</li>
|
||||
);
|
||||
}) ?? <span>no outputs</span>}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{targetConfiguration.dependsOn && (
|
||||
<div className="group">
|
||||
<h4 className="mb-4">
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={(<PropertyInfoTooltip type="dependsOn" />) as any}
|
||||
>
|
||||
<span className="font-medium">
|
||||
<TooltipTriggerText>Depends On</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="inline pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"dependsOn": ${JSON.stringify(
|
||||
targetConfiguration.dependsOn
|
||||
)}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
<ul className="mb-4 list-disc pl-5">
|
||||
{targetConfiguration.dependsOn.map((dep, idx) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.dependsOn`
|
||||
);
|
||||
|
||||
return (
|
||||
<li
|
||||
className="group/line overflow-hidden whitespace-nowrap"
|
||||
key={`dependsOn-${idx}`}
|
||||
>
|
||||
<TargetConfigurationProperty data={dep}>
|
||||
<span className="shrink-1 inline flex min-w-0 pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
|
||||
{sourceInfo && (
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.dependsOn`}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</TargetConfigurationProperty>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shouldRenderOptions ? (
|
||||
<>
|
||||
<h4 className="mb-4">
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={(<PropertyInfoTooltip type="options" />) as any}
|
||||
>
|
||||
<span className="font-medium">
|
||||
<TooltipTriggerText>Options</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</h4>
|
||||
<div className="mb-4">
|
||||
<FadingCollapsible>
|
||||
<JsonCodeBlock
|
||||
data={options}
|
||||
renderSource={(propertyName: string) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.options.${propertyName}`
|
||||
);
|
||||
return sourceInfo ? (
|
||||
<span className="shrink-1 flex min-w-0 pl-4">
|
||||
<ul className="mb-4 list-disc pl-5">
|
||||
{targetConfiguration.inputs.map((input, idx) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.inputs`
|
||||
);
|
||||
return (
|
||||
<li
|
||||
className="group/line overflow-hidden whitespace-nowrap"
|
||||
key={`input-${idx}`}
|
||||
>
|
||||
<TargetConfigurationProperty data={input}>
|
||||
{sourceInfo && (
|
||||
<span className="inline flex min-w-0 pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.options.${propertyName}`}
|
||||
propertyKey={`targets.${targetName}.inputs`}
|
||||
/>
|
||||
</span>
|
||||
) : null;
|
||||
}}
|
||||
/>
|
||||
</FadingCollapsible>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
|
||||
{shouldRenderConfigurations ? (
|
||||
<>
|
||||
<h4 className="mb-4 py-2">
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={
|
||||
(<PropertyInfoTooltip type="configurations" />) as any
|
||||
)}
|
||||
</TargetConfigurationProperty>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{targetConfiguration.outputs && (
|
||||
<div className="group">
|
||||
<h4 className="mb-4">
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={(<PropertyInfoTooltip type="outputs" />) as any}
|
||||
>
|
||||
<span className="font-medium">
|
||||
<TooltipTriggerText>Outputs</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="ml-2 mb-1 hidden group-hover:inline">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"outputs": ${JSON.stringify(
|
||||
targetConfiguration.outputs
|
||||
)}`
|
||||
)
|
||||
}
|
||||
>
|
||||
<span className="font-medium">
|
||||
<TooltipTriggerText>Configurations</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>{' '}
|
||||
{targetConfiguration.defaultConfiguration && (
|
||||
<span className="ml-3 cursor-help">
|
||||
<Pill
|
||||
tooltip="Default Configuration"
|
||||
text={targetConfiguration.defaultConfiguration}
|
||||
color="yellow"
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</h4>
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
<ul className="mb-4 list-disc pl-5">
|
||||
{targetConfiguration.outputs?.map((output, idx) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.outputs`
|
||||
);
|
||||
return (
|
||||
<li
|
||||
className="group/line overflow-hidden whitespace-nowrap"
|
||||
key={`output-${idx}`}
|
||||
>
|
||||
<TargetConfigurationProperty data={output}>
|
||||
{sourceInfo && (
|
||||
<span className="inline flex min-w-0 pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.outputs`}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</TargetConfigurationProperty>
|
||||
</li>
|
||||
);
|
||||
}) ?? <span>no outputs</span>}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{targetConfiguration.dependsOn && (
|
||||
<div className="group">
|
||||
<h4 className="mb-4">
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={(<PropertyInfoTooltip type="dependsOn" />) as any}
|
||||
>
|
||||
<span className="font-medium">
|
||||
<TooltipTriggerText>Depends On</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="inline pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"dependsOn": ${JSON.stringify(
|
||||
targetConfiguration.dependsOn
|
||||
)}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
<ul className="mb-4 list-disc pl-5">
|
||||
{targetConfiguration.dependsOn.map((dep, idx) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.dependsOn`
|
||||
);
|
||||
|
||||
return (
|
||||
<li
|
||||
className="group/line overflow-hidden whitespace-nowrap"
|
||||
key={`dependsOn-${idx}`}
|
||||
>
|
||||
<TargetConfigurationProperty data={dep}>
|
||||
<span className="inline flex min-w-0 pl-4 opacity-0 transition-opacity duration-150 ease-in-out group-hover/line:opacity-100">
|
||||
{sourceInfo && (
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.dependsOn`}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</TargetConfigurationProperty>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shouldRenderOptions ? (
|
||||
<>
|
||||
<h4 className="mb-4">
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={(<PropertyInfoTooltip type="options" />) as any}
|
||||
>
|
||||
<span className="font-medium">
|
||||
<TooltipTriggerText>Options</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</h4>
|
||||
<div className="mb-4">
|
||||
<FadingCollapsible>
|
||||
<JsonCodeBlock
|
||||
data={targetConfiguration.configurations}
|
||||
data={options}
|
||||
renderSource={(propertyName: string) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.configurations.${propertyName}`
|
||||
`targets.${targetName}.options.${propertyName}`
|
||||
);
|
||||
return sourceInfo ? (
|
||||
<span className="shrink-1 flex min-w-0 pl-4">
|
||||
<span className="flex min-w-0 pl-4">
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.configurations.${propertyName}`}
|
||||
/>{' '}
|
||||
propertyKey={`targets.${targetName}.options.${propertyName}`}
|
||||
/>
|
||||
</span>
|
||||
) : null;
|
||||
}}
|
||||
/>
|
||||
</FadingCollapsible>
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
|
||||
{shouldRenderConfigurations ? (
|
||||
<>
|
||||
<h4 className="mb-4 py-2">
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={
|
||||
(<PropertyInfoTooltip type="configurations" />) as any
|
||||
}
|
||||
>
|
||||
<span className="font-medium">
|
||||
<TooltipTriggerText>Configurations</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>{' '}
|
||||
{targetConfiguration.defaultConfiguration && (
|
||||
<span className="ml-3 cursor-help">
|
||||
<Pill
|
||||
tooltip="Default Configuration"
|
||||
text={targetConfiguration.defaultConfiguration}
|
||||
color="yellow"
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</h4>
|
||||
<FadingCollapsible>
|
||||
<JsonCodeBlock
|
||||
data={targetConfiguration.configurations}
|
||||
renderSource={(propertyName: string) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.configurations.${propertyName}`
|
||||
);
|
||||
return sourceInfo ? (
|
||||
<span className="flex min-w-0 pl-4">
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.configurations.${propertyName}`}
|
||||
/>{' '}
|
||||
</span>
|
||||
) : null;
|
||||
}}
|
||||
/>
|
||||
</FadingCollapsible>
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const TargetConfigurationDetails = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TargetConfigurationDetailsComponent);
|
||||
export default TargetConfigurationDetails;
|
||||
|
||||
@ -11,7 +11,7 @@ export function TargetConfigurationProperty({
|
||||
}: RenderPropertyProps): JSX.Element | null {
|
||||
if (typeof data === 'string') {
|
||||
return (
|
||||
<span className="font-mono flex shrink-1 text-sm">
|
||||
<span className="flex font-mono text-sm">
|
||||
{data}
|
||||
{children}
|
||||
</span>
|
||||
@ -20,7 +20,7 @@ export function TargetConfigurationProperty({
|
||||
return (
|
||||
<ul>
|
||||
{data.map((item, index) => (
|
||||
<li key={index} className="font-mono flex shrink-1 text-sm">
|
||||
<li key={index} className="flex font-mono text-sm">
|
||||
{String(item)}
|
||||
{children}
|
||||
</li>
|
||||
@ -31,7 +31,7 @@ export function TargetConfigurationProperty({
|
||||
return (
|
||||
<ul>
|
||||
{Object.entries(data).map(([key, value], index) => (
|
||||
<li key={index} className="font-mono flex shrink-1 text-sm">
|
||||
<li key={index} className="flex font-mono text-sm">
|
||||
<strong>{key}</strong>: {String(value)}
|
||||
{children}
|
||||
</li>
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { TargetTechnologies } from './target-technologies';
|
||||
|
||||
const meta: Meta<typeof TargetTechnologies> = {
|
||||
component: TargetTechnologies,
|
||||
title: 'TargetTechnologies',
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof TargetTechnologies>;
|
||||
|
||||
export const Simple: Story = {
|
||||
args: {
|
||||
technologies: ['react', 'angular'],
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
import { TechnologyIcon } from '@nx/graph/ui-icons';
|
||||
|
||||
export interface TargetTechnologiesProps {
|
||||
technologies?: string[];
|
||||
showTooltip?: boolean;
|
||||
}
|
||||
|
||||
export function TargetTechnologies({
|
||||
technologies,
|
||||
showTooltip,
|
||||
}: TargetTechnologiesProps) {
|
||||
if (!technologies || technologies.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
{technologies.map((technology, index) => (
|
||||
<TechnologyIcon
|
||||
key={index}
|
||||
technology={technology}
|
||||
showTooltip={showTooltip}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
35
graph/ui-project-details/src/lib/utils/group-targets.ts
Normal file
35
graph/ui-project-details/src/lib/utils/group-targets.ts
Normal file
@ -0,0 +1,35 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import type { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
|
||||
/**
|
||||
* This function groups targets based on the targetGroups metadata
|
||||
* If there is no targetGroups metadata, it will create a group for each target
|
||||
* @param project
|
||||
* @returns
|
||||
*/
|
||||
export function groupTargets(project: ProjectGraphProjectNode): {
|
||||
groups: Record<string, string[]>;
|
||||
targets: string[];
|
||||
} {
|
||||
const targetGroups = project.data.metadata?.targetGroups ?? {};
|
||||
Object.entries(targetGroups).forEach(([group, targets]) => {
|
||||
targetGroups[group] = targets.sort(sortNxReleasePublishLast);
|
||||
});
|
||||
const allTargetsInTargetGroups: string[] = Object.values(targetGroups).flat();
|
||||
const targets: string[] = Object.keys(project.data.targets ?? {})
|
||||
.filter((target) => {
|
||||
return !allTargetsInTargetGroups.includes(target);
|
||||
})
|
||||
.sort(sortNxReleasePublishLast);
|
||||
return {
|
||||
groups: targetGroups ?? {},
|
||||
targets: targets ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
function sortNxReleasePublishLast(a: string, b: string) {
|
||||
if (a === 'nx-release-publish') return 1;
|
||||
if (b === 'nx-release-publish') return -1;
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
@ -1,7 +1,6 @@
|
||||
import {
|
||||
Attributes,
|
||||
cloneElement,
|
||||
Fragment,
|
||||
HTMLAttributes,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
@ -11,7 +10,6 @@ import {
|
||||
} from 'react';
|
||||
|
||||
import {
|
||||
FloatingPortal,
|
||||
useClick,
|
||||
arrow,
|
||||
autoUpdate,
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
import { NextSeo } from 'next-seo';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { frameworkIcons } from '@nx/nx-dev/ui-markdoc';
|
||||
import { frameworkIcons } from '@nx/graph/ui-icons';
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
|
||||
interface NewYearTip {
|
||||
|
||||
@ -54,7 +54,6 @@ import { VideoLink, videoLink } from './lib/tags/video-link.component';
|
||||
// import { SvgAnimation, svgAnimation } from './lib/tags/svg-animation.component';
|
||||
import { Pill } from './lib/tags/pill.component';
|
||||
import { pill } from './lib/tags/pill.schema';
|
||||
import { frameworkIcons } from './lib/icons';
|
||||
import { fence } from './lib/nodes/fence.schema';
|
||||
import { FenceWrapper } from './lib/nodes/fence-wrapper.component';
|
||||
|
||||
@ -139,8 +138,6 @@ export const parseMarkdown: (markdown: string) => Node = (markdown) => {
|
||||
return parse(tokens);
|
||||
};
|
||||
|
||||
export { frameworkIcons };
|
||||
|
||||
export const renderMarkdown: (
|
||||
documentContent: string,
|
||||
options: { filePath: string }
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ChevronRightIcon } from '@heroicons/react/24/outline';
|
||||
import { frameworkIcons } from '../icons';
|
||||
import { frameworkIcons } from '@nx/graph/ui-icons';
|
||||
|
||||
export function CallToAction({
|
||||
url,
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
DocumentIcon,
|
||||
PlayCircleIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { frameworkIcons } from '../icons';
|
||||
import { Framework, frameworkIcons } from '@nx/graph/ui-icons';
|
||||
|
||||
import { cx } from '@nx/nx-dev/ui-primitives';
|
||||
import { ReactNode } from 'react';
|
||||
@ -127,7 +127,7 @@ export function LinkCard({
|
||||
}
|
||||
)}
|
||||
>
|
||||
{icon && frameworkIcons[icon]?.image}
|
||||
{icon && frameworkIcons[icon as Framework]?.image}
|
||||
</div>
|
||||
)}
|
||||
<div className={cx('pt-4', { 'pt-2': appearance === 'small' })}>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useTheme } from '@nx/nx-dev/ui-theme';
|
||||
import { JSX, ReactElement, useEffect, useState } from 'react';
|
||||
import { Provider as StoreProvider } from 'react-redux';
|
||||
import { rootStore } from '@nx/graph/state';
|
||||
import { ProjectDetails as ProjectDetailsUi } from '@nx/graph/ui-project-details';
|
||||
|
||||
export function Loading() {
|
||||
@ -26,7 +27,6 @@ export function ProjectDetails({
|
||||
jsonFile?: string;
|
||||
children: ReactElement;
|
||||
}): JSX.Element {
|
||||
const [theme] = useTheme();
|
||||
const [parsedProps, setParsedProps] = useState<any>();
|
||||
const getData = async (path: string) => {
|
||||
const response = await fetch('/documentation/' + path, {
|
||||
@ -73,7 +73,7 @@ export function ProjectDetails({
|
||||
return (
|
||||
<div className="w-full place-content-center overflow-hidden rounded-md ring-1 ring-slate-200 dark:ring-slate-700">
|
||||
{title && (
|
||||
<div className="relative flex justify-center p-2 border-b border-slate-200 bg-slate-100/50 dark:border-slate-700 dark:bg-slate-700/50 font-bold">
|
||||
<div className="relative flex justify-center border-b border-slate-200 bg-slate-100/50 p-2 font-bold dark:border-slate-700 dark:bg-slate-700/50">
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
@ -82,11 +82,13 @@ export function ProjectDetails({
|
||||
height ? `p-4 h-[${height}] overflow-y-auto` : 'p-4'
|
||||
}`}
|
||||
>
|
||||
<ProjectDetailsUi
|
||||
project={parsedProps.project}
|
||||
sourceMap={parsedProps.sourceMap}
|
||||
variant="compact"
|
||||
/>
|
||||
<StoreProvider store={rootStore}>
|
||||
<ProjectDetailsUi
|
||||
project={parsedProps.project}
|
||||
sourceMap={parsedProps.sourceMap}
|
||||
variant="compact"
|
||||
/>
|
||||
</StoreProvider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -69,6 +69,11 @@ describe('@nx/gradle/plugin', () => {
|
||||
expect(nodes.projects.proj).toMatchInlineSnapshot(`
|
||||
{
|
||||
"metadata": {
|
||||
"targetGroups": {
|
||||
"Test": [
|
||||
"test",
|
||||
],
|
||||
},
|
||||
"technologies": [
|
||||
"gradle",
|
||||
],
|
||||
@ -85,6 +90,11 @@ describe('@nx/gradle/plugin', () => {
|
||||
"default",
|
||||
"^production",
|
||||
],
|
||||
"metadata": {
|
||||
"technologies": [
|
||||
"gradle",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "proj",
|
||||
},
|
||||
@ -124,6 +134,11 @@ describe('@nx/gradle/plugin', () => {
|
||||
expect(nodes.projects['nested/nested/proj']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"metadata": {
|
||||
"targetGroups": {
|
||||
"Test": [
|
||||
"test",
|
||||
],
|
||||
},
|
||||
"technologies": [
|
||||
"gradle",
|
||||
],
|
||||
@ -140,6 +155,11 @@ describe('@nx/gradle/plugin', () => {
|
||||
"default",
|
||||
"^production",
|
||||
],
|
||||
"metadata": {
|
||||
"technologies": [
|
||||
"gradle",
|
||||
],
|
||||
},
|
||||
"options": {
|
||||
"cwd": "nested/nested/proj",
|
||||
},
|
||||
|
||||
@ -4,7 +4,6 @@ import {
|
||||
ProjectConfiguration,
|
||||
TargetConfiguration,
|
||||
readJsonFile,
|
||||
workspaceRoot,
|
||||
writeJsonFile,
|
||||
} from '@nx/devkit';
|
||||
import { calculateHashForCreateNodes } from '@nx/devkit/src/utils/calculate-hash-for-create-nodes';
|
||||
@ -42,7 +41,7 @@ export const calculatedTargets: Record<
|
||||
{
|
||||
name: string;
|
||||
targets: Record<string, TargetConfiguration>;
|
||||
targetGroups: Record<string, string[]>;
|
||||
metadata: ProjectConfiguration['metadata'];
|
||||
}
|
||||
> = {};
|
||||
|
||||
@ -51,7 +50,7 @@ function readTargetsCache(): Record<
|
||||
{
|
||||
name: string;
|
||||
targets: Record<string, TargetConfiguration>;
|
||||
targetGroups: Record<string, string[]>;
|
||||
metadata: ProjectConfiguration['metadata'];
|
||||
}
|
||||
> {
|
||||
return readJsonFile(cachePath);
|
||||
@ -63,7 +62,7 @@ export function writeTargetsToCache(
|
||||
{
|
||||
name: string;
|
||||
targets: Record<string, TargetConfiguration>;
|
||||
targetGroups: Record<string, string[]>;
|
||||
metadata: ProjectConfiguration['metadata'];
|
||||
}
|
||||
>
|
||||
) {
|
||||
@ -88,12 +87,7 @@ export const createNodes: CreateNodes<GradlePluginOptions> = [
|
||||
calculatedTargets[hash] = targetsCache[hash];
|
||||
return {
|
||||
projects: {
|
||||
[projectRoot]: {
|
||||
...targetsCache[hash],
|
||||
metadata: {
|
||||
technologies: ['gradle'],
|
||||
},
|
||||
},
|
||||
[projectRoot]: targetsCache[hash],
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -137,19 +131,15 @@ export const createNodes: CreateNodes<GradlePluginOptions> = [
|
||||
context,
|
||||
outputDirs
|
||||
);
|
||||
calculatedTargets[hash] = {
|
||||
name: projectName,
|
||||
targets,
|
||||
targetGroups,
|
||||
};
|
||||
|
||||
const project: Omit<ProjectConfiguration, 'root'> = {
|
||||
const project = {
|
||||
name: projectName,
|
||||
targets,
|
||||
metadata: {
|
||||
targetGroups,
|
||||
technologies: ['gradle'],
|
||||
},
|
||||
};
|
||||
calculatedTargets[hash] = project;
|
||||
|
||||
return {
|
||||
projects: {
|
||||
@ -194,6 +184,9 @@ function createGradleTargets(
|
||||
inputs: inputsMap[task.name],
|
||||
outputs: outputs ? [outputs] : undefined,
|
||||
dependsOn: dependsOnMap[task.name],
|
||||
metadata: {
|
||||
technologies: ['gradle'],
|
||||
},
|
||||
};
|
||||
if (!targetGroups[task.type]) {
|
||||
targetGroups[task.type] = [];
|
||||
|
||||
@ -39,9 +39,11 @@
|
||||
"@nx/gradle/*": ["packages/gradle/*"],
|
||||
"@nx/graph/project-details": ["graph/project-details/src/index.ts"],
|
||||
"@nx/graph/shared": ["graph/shared/src/index.ts"],
|
||||
"@nx/graph/state": ["graph/state/src/index.ts"],
|
||||
"@nx/graph/ui-code-block": ["graph/ui-code-block/src/index.ts"],
|
||||
"@nx/graph/ui-components": ["graph/ui-components/src/index.ts"],
|
||||
"@nx/graph/ui-graph": ["graph/ui-graph/src/index.ts"],
|
||||
"@nx/graph/ui-icons": ["graph/ui-icons/src/index.ts"],
|
||||
"@nx/graph/ui-project-details": ["graph/ui-project-details/src/index.ts"],
|
||||
"@nx/graph/ui-theme": ["graph/ui-theme/src/index.ts"],
|
||||
"@nx/graph/ui-tooltips": ["graph/ui-tooltips/src/index.ts"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user