feat(docs): add {% project-details %} as a tag in markdown docs (#21288)
Co-authored-by: Colum Ferry <cferry09@gmail.com> Co-authored-by: Isaac Mann <isaacplmann@gmail.com>
This commit is contained in:
parent
292d407536
commit
7b680ec68c
@ -286,6 +286,49 @@ Have a more decent button-like widget that you can place below sections of a tut
|
||||
{% video-link link="https://youtu.be/OQ-Zc5tcxJE?t=64" /%}
|
||||
```
|
||||
|
||||
#### Project Details View
|
||||
|
||||
Embed a Project Details View that is identical what is shown in Nx Console or `nx show project myproject --web`
|
||||
|
||||
````markdown
|
||||
{% project-details title="Test" height="100px" %}
|
||||
|
||||
```json
|
||||
{
|
||||
"project": {
|
||||
"name": "demo",
|
||||
"data": {
|
||||
"root": " packages/demo",
|
||||
"projectType": "application",
|
||||
"targets": {
|
||||
"dev": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "vite dev"
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"executor": "nx:run-commands",
|
||||
"inputs": ["production", "^production"],
|
||||
"outputs": ["{projectRoot}/dist"],
|
||||
"options": {
|
||||
"command": "vite build"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sourceMap": {
|
||||
"targets": ["packages/demo/vite.config.ts", "@nx/vite"],
|
||||
"targets.dev": ["packages/demo/vite.config.ts", "@nx/vite"],
|
||||
"targets.build": ["packages/demo/vite.config.ts", "@nx/vite"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /project-details %}
|
||||
````
|
||||
|
||||
#### Graph
|
||||
|
||||
Embed an Nx Graph visualization that can be panned by the user.
|
||||
|
||||
@ -5,13 +5,12 @@ import { Shell } from './shell';
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
||||
/* eslint-enable @nx/enforce-module-boundaries */
|
||||
import { ProjectDetailsPage } from '@nx/graph/project-details';
|
||||
import {
|
||||
getEnvironmentConfig,
|
||||
getProjectGraphDataService,
|
||||
} from '@nx/graph/shared';
|
||||
import { TasksSidebarErrorBoundary } from './feature-tasks/tasks-sidebar-error-boundary';
|
||||
import { ProjectDetailsPage } from '@nx/graph/project-details';
|
||||
|
||||
const { appConfig } = getEnvironmentConfig();
|
||||
const projectGraphDataService = getProjectGraphDataService();
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// nx-ignore-next-line
|
||||
import { useFloating } from '@floating-ui/react';
|
||||
import { XMarkIcon } from '@heroicons/react/24/outline';
|
||||
import { ProjectDetails } from '@nx/graph/project-details';
|
||||
import { ProjectDetailsWrapper } from '@nx/graph/project-details';
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
||||
@ -46,6 +46,7 @@ export function ProjectDetailsModal() {
|
||||
setSearchParams(searchParams);
|
||||
setIsOpen(false);
|
||||
}
|
||||
|
||||
return (
|
||||
isOpen && (
|
||||
<div
|
||||
@ -56,7 +57,7 @@ export function ProjectDetailsModal() {
|
||||
ref={refs.setFloating}
|
||||
>
|
||||
<div className="rounded-md h-full border border-slate-500">
|
||||
<ProjectDetails project={project} sourceMap={sourceMap} />
|
||||
<ProjectDetailsWrapper project={project} sourceMap={sourceMap} />
|
||||
<div className="top-2 right-2 absolute" onClick={onClose}>
|
||||
<XMarkIcon className="h-4 w-4" />
|
||||
</div>
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
export * from './lib/project-details';
|
||||
export * from './lib/project-details-wrapper';
|
||||
export * from './lib/project-details-page';
|
||||
|
||||
@ -4,12 +4,10 @@ import { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
import {
|
||||
Link,
|
||||
ScrollRestoration,
|
||||
useLocation,
|
||||
useNavigate,
|
||||
useParams,
|
||||
useRouteLoaderData,
|
||||
} from 'react-router-dom';
|
||||
import ProjectDetails from './project-details';
|
||||
import { ProjectDetailsWrapper } from './project-details-wrapper';
|
||||
import {
|
||||
fetchProjectGraph,
|
||||
getProjectGraphDataService,
|
||||
@ -54,7 +52,7 @@ export function ProjectDetailsPage() {
|
||||
<div className="flex flex-col justify-center w-full text-slate-700 dark:text-slate-400">
|
||||
<ScrollRestoration />
|
||||
{environment !== 'nx-console' ? (
|
||||
<header className="flex w-full justify-center items-center px-4 py-2 border-b-2 border-slate-900/10 mb-16 dark:border-slate-300/10">
|
||||
<header className="flex w-full justify-center items-center px-4 py-2 border-b-2 border-slate-900/10 mb-8 dark:border-slate-300/10">
|
||||
<div className="flex flex-grow items-center justify-between max-w-6xl">
|
||||
<svg
|
||||
className="h-10 w-auto text-slate-900 dark:text-white"
|
||||
@ -85,10 +83,10 @@ export function ProjectDetailsPage() {
|
||||
</header>
|
||||
) : null}
|
||||
<div className="flex-grow mx-auto w-full max-w-6xl px-8 mb-8">
|
||||
<ProjectDetails
|
||||
<ProjectDetailsWrapper
|
||||
project={project}
|
||||
sourceMap={sourceMap}
|
||||
></ProjectDetails>
|
||||
></ProjectDetailsWrapper>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
163
graph/project-details/src/lib/project-details-wrapper.tsx
Normal file
163
graph/project-details/src/lib/project-details-wrapper.tsx
Normal file
@ -0,0 +1,163 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
import {
|
||||
getExternalApiService,
|
||||
useEnvironmentConfig,
|
||||
useRouteConstructor,
|
||||
} from '@nx/graph/shared';
|
||||
import {
|
||||
ProjectDetails,
|
||||
ProjectDetailsImperativeHandle,
|
||||
} from '@nx/graph/ui-project-details';
|
||||
import { useCallback, useLayoutEffect, useRef } from 'react';
|
||||
|
||||
export interface ProjectDetailsProps {
|
||||
project: ProjectGraphProjectNode;
|
||||
sourceMap: Record<string, string[]>;
|
||||
}
|
||||
|
||||
export function ProjectDetailsWrapper(props: ProjectDetailsProps) {
|
||||
const projectDetailsRef = useRef<ProjectDetailsImperativeHandle>(null);
|
||||
const environment = useEnvironmentConfig()?.environment;
|
||||
const externalApiService = getExternalApiService();
|
||||
const navigate = useNavigate();
|
||||
const routeConstructor = useRouteConstructor();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const handleViewInProjectGraph = useCallback(
|
||||
(data: { projectName: string }) => {
|
||||
if (environment === 'nx-console') {
|
||||
externalApiService.postEvent({
|
||||
type: 'open-project-graph',
|
||||
payload: {
|
||||
projectName: data.projectName,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
navigate(
|
||||
routeConstructor(
|
||||
`/projects/${encodeURIComponent(data.projectName)}`,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
[externalApiService, routeConstructor, navigate, environment]
|
||||
);
|
||||
|
||||
const handleViewInTaskGraph = useCallback(
|
||||
(data: { projectName: string; targetName: string }) => {
|
||||
if (environment === 'nx-console') {
|
||||
externalApiService.postEvent({
|
||||
type: 'open-task-graph',
|
||||
payload: {
|
||||
projectName: data.projectName,
|
||||
targetName: data.targetName,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
navigate(
|
||||
routeConstructor(
|
||||
{
|
||||
pathname: `/tasks/${encodeURIComponent(data.targetName)}`,
|
||||
search: `?projects=${encodeURIComponent(data.projectName)}`,
|
||||
},
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
[externalApiService, routeConstructor, navigate, environment]
|
||||
);
|
||||
|
||||
const handleRunTarget = useCallback(
|
||||
(data: { projectName: string; targetName: string }) => {
|
||||
externalApiService.postEvent({
|
||||
type: 'run-task',
|
||||
payload: { taskId: `${data.projectName}:${data.targetName}` },
|
||||
});
|
||||
},
|
||||
[externalApiService]
|
||||
);
|
||||
|
||||
const updateSearchParams = (params: URLSearchParams, sections: string[]) => {
|
||||
if (sections.length === 0) {
|
||||
params.delete('expanded');
|
||||
} else {
|
||||
params.set('expanded', sections.join(','));
|
||||
}
|
||||
};
|
||||
|
||||
const handleTargetCollapse = useCallback(
|
||||
(targetName: string) => {
|
||||
setSearchParams(
|
||||
(currentSearchParams) => {
|
||||
const expandedSections =
|
||||
currentSearchParams.get('expanded')?.split(',') || [];
|
||||
const newExpandedSections = expandedSections.filter(
|
||||
(section) => section !== targetName
|
||||
);
|
||||
updateSearchParams(currentSearchParams, newExpandedSections);
|
||||
return currentSearchParams;
|
||||
},
|
||||
{
|
||||
replace: true,
|
||||
preventScrollReset: true,
|
||||
}
|
||||
);
|
||||
},
|
||||
[setSearchParams]
|
||||
);
|
||||
|
||||
const handleTargetExpand = useCallback(
|
||||
(targetName: string) => {
|
||||
setSearchParams(
|
||||
(currentSearchParams) => {
|
||||
const expandedSections =
|
||||
currentSearchParams.get('expanded')?.split(',') || [];
|
||||
if (!expandedSections.includes(targetName)) {
|
||||
expandedSections.push(targetName);
|
||||
updateSearchParams(currentSearchParams, expandedSections);
|
||||
}
|
||||
return currentSearchParams;
|
||||
},
|
||||
{ replace: true, preventScrollReset: true }
|
||||
);
|
||||
},
|
||||
[setSearchParams]
|
||||
);
|
||||
|
||||
// On initial render, expand the sections that are included in the URL search params.
|
||||
const isExpandedHandled = useRef(false);
|
||||
useLayoutEffect(() => {
|
||||
if (!props.project.data.targets) return;
|
||||
if (isExpandedHandled.current) return;
|
||||
isExpandedHandled.current = true;
|
||||
|
||||
const expandedSections = searchParams.get('expanded')?.split(',') || [];
|
||||
for (const targetName of Object.keys(props.project.data.targets)) {
|
||||
if (expandedSections.includes(targetName)) {
|
||||
projectDetailsRef.current?.expandTarget(targetName);
|
||||
}
|
||||
}
|
||||
}, [searchParams, props.project.data.targets, projectDetailsRef]);
|
||||
|
||||
return (
|
||||
<ProjectDetails
|
||||
ref={projectDetailsRef}
|
||||
{...props}
|
||||
onTargetCollapse={handleTargetCollapse}
|
||||
onTargetExpand={handleTargetExpand}
|
||||
onViewInProjectGraph={handleViewInProjectGraph}
|
||||
onViewInTaskGraph={handleViewInTaskGraph}
|
||||
onRunTarget={environment === 'nx-console' ? handleRunTarget : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProjectDetailsWrapper;
|
||||
@ -1,114 +0,0 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import { ProjectGraphProjectNode } from '@nx/devkit';
|
||||
|
||||
import { EyeIcon } from '@heroicons/react/24/outline';
|
||||
import {
|
||||
getExternalApiService,
|
||||
useEnvironmentConfig,
|
||||
useRouteConstructor,
|
||||
} from '@nx/graph/shared';
|
||||
import { TargetConfigurationDetails } from './target/target-configuration-details';
|
||||
import { PropertyInfoTooltip, Tooltip } from '@nx/graph/ui-tooltips';
|
||||
import { TooltipTriggerText } from './target/ui/tooltip-trigger-text';
|
||||
|
||||
export interface ProjectDetailsProps {
|
||||
project: ProjectGraphProjectNode;
|
||||
sourceMap: Record<string, string[]>;
|
||||
}
|
||||
|
||||
export function ProjectDetails({
|
||||
project: {
|
||||
name,
|
||||
data: { root, ...projectData },
|
||||
},
|
||||
sourceMap,
|
||||
}: ProjectDetailsProps) {
|
||||
const environment = useEnvironmentConfig()?.environment;
|
||||
const externalApiService = getExternalApiService();
|
||||
const navigate = useNavigate();
|
||||
const routeConstructor = useRouteConstructor();
|
||||
|
||||
const displayType =
|
||||
projectData.projectType &&
|
||||
projectData.projectType?.charAt(0)?.toUpperCase() +
|
||||
projectData.projectType?.slice(1);
|
||||
|
||||
const viewInProjectGraph = () => {
|
||||
if (environment === 'nx-console') {
|
||||
externalApiService.postEvent({
|
||||
type: 'open-project-graph',
|
||||
payload: {
|
||||
projectName: name,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
navigate(routeConstructor(`/projects/${encodeURIComponent(name)}`, true));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="border-b border-slate-900/10 mb-4 dark:border-slate-300/10">
|
||||
<h1 className="text-6xl flex items-center mb-4 gap-2">
|
||||
{name}{' '}
|
||||
{environment === 'nx-console' ? (
|
||||
<EyeIcon className="h-5 w-5" onClick={viewInProjectGraph}></EyeIcon>
|
||||
) : null}{' '}
|
||||
</h1>
|
||||
<div className="p-4">
|
||||
{projectData.tags ? (
|
||||
<p>
|
||||
{projectData.tags?.map((tag) => (
|
||||
<span className="bg-slate-300 rounded-md p-1 mr-2">{tag}</span>
|
||||
))}
|
||||
</p>
|
||||
) : null}
|
||||
<p>
|
||||
<span className="font-bold">Root:</span> {root}
|
||||
</p>
|
||||
{displayType ? (
|
||||
<p>
|
||||
<span className="font-bold">Type:</span> {displayType}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<h2 className="text-3xl mb-4">
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={(<PropertyInfoTooltip type="targets" />) as any}
|
||||
>
|
||||
<span>
|
||||
<TooltipTriggerText>Targets</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</h2>
|
||||
<ul>
|
||||
{Object.entries(projectData.targets ?? {}).map(
|
||||
([targetName, target]) => {
|
||||
const props = {
|
||||
projectName: name,
|
||||
targetName: targetName,
|
||||
targetConfiguration: target,
|
||||
sourceMap,
|
||||
};
|
||||
return (
|
||||
<li className="mb-4">
|
||||
<TargetConfigurationDetails {...props} />
|
||||
</li>
|
||||
);
|
||||
}
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProjectDetails;
|
||||
@ -1,520 +0,0 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
EyeIcon,
|
||||
PlayIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
|
||||
// nx-ignore-next-line
|
||||
import { TargetConfiguration } from '@nx/devkit';
|
||||
import {
|
||||
getExternalApiService,
|
||||
useEnvironmentConfig,
|
||||
useRouteConstructor,
|
||||
} from '@nx/graph/shared';
|
||||
import { JsonCodeBlock } from '@nx/graph/ui-code-block';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { SourceInfo } from './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 {
|
||||
PropertyInfoTooltip,
|
||||
SourcemapInfoToolTip,
|
||||
Tooltip,
|
||||
} from '@nx/graph/ui-tooltips';
|
||||
import { TooltipTriggerText } from './ui/tooltip-trigger-text';
|
||||
import { ExternalLink } from '@nx/graph/ui-tooltips';
|
||||
|
||||
/* eslint-disable-next-line */
|
||||
export interface TargetProps {
|
||||
projectName: string;
|
||||
targetName: string;
|
||||
targetConfiguration: TargetConfiguration;
|
||||
sourceMap: Record<string, string[]>;
|
||||
}
|
||||
|
||||
export function TargetConfigurationDetails({
|
||||
projectName,
|
||||
targetName,
|
||||
targetConfiguration,
|
||||
sourceMap,
|
||||
}: TargetProps) {
|
||||
const environment = useEnvironmentConfig()?.environment;
|
||||
const externalApiService = getExternalApiService();
|
||||
const navigate = useNavigate();
|
||||
const routeConstructor = useRouteConstructor();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
|
||||
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(() => {
|
||||
const expandedSections = searchParams.get('expanded')?.split(',') || [];
|
||||
setCollapsed(!expandedSections.includes(targetName));
|
||||
}, [searchParams, targetName]);
|
||||
|
||||
const handleCopyClick = (copyText: string) => {
|
||||
navigator.clipboard.writeText(copyText);
|
||||
};
|
||||
|
||||
function toggleCollapsed() {
|
||||
setCollapsed((prevState) => {
|
||||
const newState = !prevState;
|
||||
setSearchParams((currentSearchParams) => {
|
||||
const expandedSections =
|
||||
currentSearchParams.get('expanded')?.split(',') || [];
|
||||
if (newState) {
|
||||
const newExpandedSections = expandedSections.filter(
|
||||
(section) => section !== targetName
|
||||
);
|
||||
updateSearchParams(currentSearchParams, newExpandedSections);
|
||||
} else {
|
||||
if (!expandedSections.includes(targetName)) {
|
||||
expandedSections.push(targetName);
|
||||
updateSearchParams(currentSearchParams, expandedSections);
|
||||
}
|
||||
}
|
||||
return currentSearchParams;
|
||||
});
|
||||
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
|
||||
function updateSearchParams(params: URLSearchParams, sections: string[]) {
|
||||
if (sections.length === 0) {
|
||||
params.delete('expanded');
|
||||
} else {
|
||||
params.set('expanded', sections.join(','));
|
||||
}
|
||||
}
|
||||
|
||||
const runTarget = () => {
|
||||
externalApiService.postEvent({
|
||||
type: 'run-task',
|
||||
payload: { taskId: `${projectName}:${targetName}` },
|
||||
});
|
||||
};
|
||||
|
||||
const viewInTaskGraph = () => {
|
||||
if (environment === 'nx-console') {
|
||||
externalApiService.postEvent({
|
||||
type: 'open-task-graph',
|
||||
payload: {
|
||||
projectName: projectName,
|
||||
targetName: targetName,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
navigate(
|
||||
routeConstructor(
|
||||
{
|
||||
pathname: `/tasks/${encodeURIComponent(targetName)}`,
|
||||
search: `?projects=${encodeURIComponent(projectName)}`,
|
||||
},
|
||||
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 configurations = targetConfiguration.configurations;
|
||||
|
||||
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="rounded-md border border-slate-500 relative overflow-hidden">
|
||||
<header
|
||||
className={`group hover:bg-slate-200 dark:hover:bg-slate-800 p-2 cursor-pointer ${
|
||||
!collapsed
|
||||
? 'bg-slate-200 dark:bg-slate-800 border-b-2 border-slate-900/10 dark:border-slate-300/10 '
|
||||
: ''
|
||||
}`}
|
||||
onClick={toggleCollapsed}
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center">
|
||||
<h3 className="font-bold mr-2">{targetName}</h3>
|
||||
{collapsed && (
|
||||
<p className="text-slate-600 mr-2">
|
||||
{singleCommand ? singleCommand : targetConfiguration.executor}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<EyeIcon
|
||||
className={`h-4 w-4 mr-2 ${
|
||||
collapsed ? 'hidden group-hover:inline-block' : 'inline-block'
|
||||
}`}
|
||||
title="View in Task Graph"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
viewInTaskGraph();
|
||||
}}
|
||||
/>
|
||||
{targetConfiguration.cache && (
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
strategy="fixed"
|
||||
content={(<PropertyInfoTooltip type="cacheable" />) as any}
|
||||
>
|
||||
<span className="rounded-full inline-block text-xs bg-sky-500 dark:bg-sky-800 px-2 text-slate-50 mr-2">
|
||||
Cacheable
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
{environment === 'nx-console' && (
|
||||
<PlayIcon
|
||||
className="h-5 w-5 mr-2"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
runTarget();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{collapsed ? (
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
) : (
|
||||
<ChevronUpIcon className="h-3 w-3" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!collapsed && (
|
||||
<div className="flex items-center text-sm mt-2">
|
||||
<span className="flex-1 flex items-center">
|
||||
<SourceInfo
|
||||
data={sourceMap[`targets.${targetName}`]}
|
||||
propertyKey={`targets.${targetName}`}
|
||||
/>
|
||||
</span>
|
||||
<code className="ml-4 bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300 font-mono px-2 py-1 rounded">
|
||||
nx run {projectName}:{targetName}
|
||||
</code>
|
||||
<span className="ml-2">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(`nx run ${projectName}:${targetName}`)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
{/* body */}
|
||||
{!collapsed && (
|
||||
<div className="p-4 text-base">
|
||||
<div className="mb-4 group">
|
||||
<h4 className="mb-4">
|
||||
{singleCommand ? (
|
||||
<span className="font-bold">
|
||||
Command
|
||||
<span className="hidden group-hover:inline ml-2 mb-1">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(`"command": "${singleCommand}"`)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
) : (
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={(<PropertyInfoTooltip type="executors" />) as any}
|
||||
>
|
||||
<span className="font-bold">
|
||||
<TooltipTriggerText>Executor</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</h4>
|
||||
<p className="pl-5">
|
||||
{executorLink ? (
|
||||
<ExternalLink
|
||||
href={executorLink ?? 'https://nx.dev/nx-api'}
|
||||
text={
|
||||
singleCommand ? singleCommand : targetConfiguration.executor
|
||||
}
|
||||
title="View Documentation"
|
||||
/>
|
||||
) : 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-bold">
|
||||
<TooltipTriggerText>Inputs</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="hidden group-hover:inline ml-2 mb-1">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"inputs": ${JSON.stringify(
|
||||
targetConfiguration.inputs
|
||||
)}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
<ul className="list-disc pl-5 mb-4">
|
||||
{targetConfiguration.inputs.map((input) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.inputs`
|
||||
);
|
||||
return (
|
||||
<li className="group/line overflow-hidden whitespace-nowrap">
|
||||
<TargetConfigurationProperty data={input}>
|
||||
{sourceInfo && (
|
||||
<span className="hidden group-hover/line:inline pl-4">
|
||||
<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-bold">
|
||||
<TooltipTriggerText>Outputs</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="hidden group-hover:inline ml-2 mb-1">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"outputs": ${JSON.stringify(
|
||||
targetConfiguration.outputs
|
||||
)}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
<ul className="list-disc pl-5 mb-4">
|
||||
{targetConfiguration.outputs?.map((output) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.outputs`
|
||||
);
|
||||
return (
|
||||
<li className="group/line overflow-hidden whitespace-nowrap">
|
||||
<TargetConfigurationProperty data={output}>
|
||||
{sourceInfo && (
|
||||
<span className="hidden group-hover/line:inline pl-4">
|
||||
<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-bold">
|
||||
<TooltipTriggerText>Depends On</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="hidden group-hover:inline ml-2 mb-1">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"dependsOn": ${JSON.stringify(
|
||||
targetConfiguration.dependsOn
|
||||
)}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
<ul className="list-disc pl-5 mb-4">
|
||||
{targetConfiguration.dependsOn.map((dep) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.dependsOn`
|
||||
);
|
||||
|
||||
return (
|
||||
<li className="group/line overflow-hidden whitespace-nowrap">
|
||||
<TargetConfigurationProperty data={dep}>
|
||||
<span className="hidden group-hover/line:inline pl-4 h-6">
|
||||
{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-bold">
|
||||
<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="pl-4">
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.options.${propertyName}`}
|
||||
/>
|
||||
</span>
|
||||
) : null;
|
||||
}}
|
||||
/>
|
||||
</FadingCollapsible>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
|
||||
{shouldRenderConfigurations ? (
|
||||
<>
|
||||
<h4 className="py-2 mb-4">
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={
|
||||
(<PropertyInfoTooltip type="configurations" />) as any
|
||||
}
|
||||
>
|
||||
<span className="font-bold">
|
||||
<TooltipTriggerText>Configurations</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>{' '}
|
||||
{targetConfiguration.defaultConfiguration && (
|
||||
<span
|
||||
className="ml-3 font-bold rounded-full inline-block text-xs bg-sky-500 px-2 text-slate-50 mr-6"
|
||||
title="Default Configuration"
|
||||
>
|
||||
{targetConfiguration.defaultConfiguration}
|
||||
</span>
|
||||
)}
|
||||
</h4>
|
||||
<FadingCollapsible>
|
||||
<JsonCodeBlock
|
||||
data={targetConfiguration.configurations}
|
||||
renderSource={(propertyName: string) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.configurations.${propertyName}`
|
||||
);
|
||||
return sourceInfo ? (
|
||||
<span className="pl-4">
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.configurations.${propertyName}`}
|
||||
/>{' '}
|
||||
</span>
|
||||
) : null;
|
||||
}}
|
||||
/>
|
||||
</FadingCollapsible>
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TargetConfigurationDetails;
|
||||
12
graph/ui-project-details/.babelrc
Normal file
12
graph/ui-project-details/.babelrc
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nx/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
18
graph/ui-project-details/.eslintrc.json
Normal file
18
graph/ui-project-details/.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": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
26
graph/ui-project-details/.storybook/main.ts
Normal file
26
graph/ui-project-details/.storybook/main.ts
Normal file
@ -0,0 +1,26 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
|
||||
// nx-ignore-next-line
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
import { mergeConfig } from 'vite';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/lib/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
|
||||
addons: ['@storybook/addon-essentials'],
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {},
|
||||
},
|
||||
|
||||
viteFinal: async (config) =>
|
||||
mergeConfig(config, {
|
||||
plugins: [nxViteTsPaths()],
|
||||
}),
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
// To customize your Vite configuration you can use the viteFinal field.
|
||||
// Check https://storybook.js.org/docs/react/builders/vite#configuration
|
||||
// and https://nx.dev/recipes/storybook/custom-builder-configs
|
||||
1
graph/ui-project-details/.storybook/preview.ts
Normal file
1
graph/ui-project-details/.storybook/preview.ts
Normal file
@ -0,0 +1 @@
|
||||
import './tailwind.css';
|
||||
3
graph/ui-project-details/.storybook/tailwind.css
Normal file
3
graph/ui-project-details/.storybook/tailwind.css
Normal file
@ -0,0 +1,3 @@
|
||||
@tailwind components;
|
||||
@tailwind base;
|
||||
@tailwind utilities;
|
||||
7
graph/ui-project-details/README.md
Normal file
7
graph/ui-project-details/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# graph-ui-project-details
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test graph-ui-project-details` to execute the unit tests via [Jest](https://jestjs.io).
|
||||
9
graph/ui-project-details/postcss.config.js
Normal file
9
graph/ui-project-details/postcss.config.js
Normal file
@ -0,0 +1,9 @@
|
||||
const { join } = require('path');
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
config: join(__dirname, 'tailwind.config.js'),
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
49
graph/ui-project-details/project.json
Normal file
49
graph/ui-project-details/project.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "graph-ui-project-details",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "graph/ui-project-details/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint"
|
||||
},
|
||||
"storybook": {
|
||||
"executor": "@nx/storybook:storybook",
|
||||
"options": {
|
||||
"port": 4400,
|
||||
"configDir": "graph/ui-project-details/.storybook"
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"quiet": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"build-storybook": {
|
||||
"executor": "@nx/storybook:build",
|
||||
"outputs": ["{options.outputDir}"],
|
||||
"options": {
|
||||
"outputDir": "dist/storybook/graph-ui-project-details",
|
||||
"configDir": "graph/ui-project-details/.storybook"
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"quiet": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"static-storybook": {
|
||||
"executor": "@nx/web:file-server",
|
||||
"options": {
|
||||
"buildTarget": "graph-ui-project-details:build-storybook",
|
||||
"staticFilePath": "dist/storybook/graph-ui-project-details"
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"buildTarget": "graph-ui-project-details:build-storybook:ci"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
graph/ui-project-details/src/index.ts
Normal file
1
graph/ui-project-details/src/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './lib/project-details/project-details';
|
||||
@ -0,0 +1,210 @@
|
||||
import type { Meta } from '@storybook/react';
|
||||
import { ProjectDetails } from './project-details';
|
||||
|
||||
const meta: Meta<typeof ProjectDetails> = {
|
||||
component: ProjectDetails,
|
||||
title: 'ProjectDetails',
|
||||
};
|
||||
export default meta;
|
||||
|
||||
export const Primary = {
|
||||
args: {
|
||||
project: {
|
||||
name: 'jest',
|
||||
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: [],
|
||||
},
|
||||
},
|
||||
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',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,163 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import { 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';
|
||||
|
||||
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;
|
||||
targetName: string;
|
||||
}) => void;
|
||||
onRunTarget?: (data: { projectName: string; targetName: string }) => void;
|
||||
}
|
||||
|
||||
export interface ProjectDetailsImperativeHandle {
|
||||
collapseTarget: (targetName: string) => void;
|
||||
expandTarget: (targetName: string) => void;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
collapseTarget: (targetName: string) => {
|
||||
targetRefs.current[targetName]?.current?.collapse();
|
||||
},
|
||||
expandTarget: (targetName: string) => {
|
||||
targetRefs.current[targetName]?.current?.expand();
|
||||
},
|
||||
}));
|
||||
|
||||
return (
|
||||
<>
|
||||
<header
|
||||
className={twMerge(
|
||||
`border-b border-slate-900/10 dark:border-slate-300/10`,
|
||||
isCompact ? 'mb-2' : 'mb-4'
|
||||
)}
|
||||
>
|
||||
<h1
|
||||
className={twMerge(
|
||||
`flex items-center`,
|
||||
isCompact ? `text-2xl gap-1` : `text-4xl mb-4 gap-2`
|
||||
)}
|
||||
>
|
||||
{name}{' '}
|
||||
{onViewInProjectGraph ? (
|
||||
<EyeIcon
|
||||
className="h-5 w-5 cursor-pointer"
|
||||
onClick={() => onViewInProjectGraph({ projectName: name })}
|
||||
></EyeIcon>
|
||||
) : null}{' '}
|
||||
</h1>
|
||||
<div className={isCompact ? `px-4 py-2` : `p-4`}>
|
||||
{projectData.tags ? (
|
||||
<p>
|
||||
{projectData.tags?.map((tag) => (
|
||||
<span className="bg-slate-300 rounded-md p-1 mr-2">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
) : null}
|
||||
<p>
|
||||
<span className="font-bold">Root:</span> {root}
|
||||
</p>
|
||||
{displayType ? (
|
||||
<p>
|
||||
<span className="font-bold">Type:</span> {displayType}
|
||||
</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}
|
||||
>
|
||||
<span>
|
||||
<TooltipTriggerText>Targets</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</h2>
|
||||
<ul>
|
||||
{projectTargets.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>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default ProjectDetails;
|
||||
@ -19,8 +19,8 @@ export function SourceInfo(props: {
|
||||
(
|
||||
<SourcemapInfoToolTip
|
||||
propertyKey={props.propertyKey}
|
||||
plugin={props.data[1]}
|
||||
file={props.data[0]}
|
||||
plugin={props.data?.[1]}
|
||||
file={props.data?.[0]}
|
||||
/>
|
||||
) as any
|
||||
}
|
||||
@ -29,7 +29,8 @@ export function SourceInfo(props: {
|
||||
{/* <InformationCircleIcon className="w-3 h-3" />*/}
|
||||
{/*</span>*/}
|
||||
<span className="italic text-gray-500">
|
||||
{isTarget ? 'Created' : 'Set'} by {props.data[1]} from {props.data[0]}
|
||||
{isTarget ? 'Created' : 'Set'} by {props.data?.[1]} from{' '}
|
||||
{props.data?.[0]}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
@ -0,0 +1,514 @@
|
||||
/* eslint-disable @nx/enforce-module-boundaries */
|
||||
// nx-ignore-next-line
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
EyeIcon,
|
||||
PlayIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
|
||||
// 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 { FadingCollapsible } from './fading-collapsible';
|
||||
import { TargetConfigurationProperty } from './target-configuration-property';
|
||||
import { selectSourceInfo } from './target-configuration-details.util';
|
||||
import { CopyToClipboard } from './copy-to-clipboard';
|
||||
import {
|
||||
ExternalLink,
|
||||
PropertyInfoTooltip,
|
||||
Tooltip,
|
||||
} from '@nx/graph/ui-tooltips';
|
||||
import { TooltipTriggerText } from './tooltip-trigger-text';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
/* 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: {
|
||||
projectName: string;
|
||||
targetName: string;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
export interface TargetConfigurationDetailsHandle {
|
||||
collapse: () => void;
|
||||
expand: () => void;
|
||||
}
|
||||
|
||||
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 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`;
|
||||
}
|
||||
|
||||
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 configurations = targetConfiguration.configurations;
|
||||
|
||||
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="rounded-md border border-slate-500 relative overflow-hidden">
|
||||
<header
|
||||
className={twMerge(
|
||||
`group hover:bg-slate-200 dark:hover:bg-slate-800 cursor-pointer`,
|
||||
isCompact ? 'px-2 py-1' : 'p-2',
|
||||
!collapsed
|
||||
? 'bg-slate-200 dark:bg-slate-800 border-b-2 border-slate-900/10 dark:border-slate-300/10 '
|
||||
: ''
|
||||
)}
|
||||
onClick={handleCollapseToggle}
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center">
|
||||
<h3 className="font-bold mr-2">{targetName}</h3>
|
||||
{collapsed && (
|
||||
<p className="text-slate-600 mr-2">
|
||||
{singleCommand ? singleCommand : targetConfiguration.executor}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
{onViewInTaskGraph && (
|
||||
<EyeIcon
|
||||
className={`h-4 w-4 mr-2 ${
|
||||
collapsed
|
||||
? 'hidden group-hover:inline-block'
|
||||
: 'inline-block'
|
||||
}`}
|
||||
title="View in Task Graph"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onViewInTaskGraph({ projectName, targetName });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{targetConfiguration.cache && (
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
strategy="fixed"
|
||||
content={(<PropertyInfoTooltip type="cacheable" />) as any}
|
||||
>
|
||||
<span className="rounded-full inline-block text-xs bg-sky-500 dark:bg-sky-800 px-2 text-slate-50 mr-2">
|
||||
Cacheable
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
{onRunTarget && (
|
||||
<PlayIcon
|
||||
className="h-5 w-5 mr-2"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onRunTarget({ projectName, targetName });
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{collapsed ? (
|
||||
<ChevronDownIcon className="h-3 w-3" />
|
||||
) : (
|
||||
<ChevronUpIcon className="h-3 w-3" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!collapsed && (
|
||||
<div className="flex items-center text-sm mt-2">
|
||||
<span className="flex-1 flex items-center">
|
||||
<SourceInfo
|
||||
data={sourceMap[`targets.${targetName}`]}
|
||||
propertyKey={`targets.${targetName}`}
|
||||
/>
|
||||
</span>
|
||||
<code className="ml-4 bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-300 font-mono px-2 py-1 rounded">
|
||||
nx run {projectName}:{targetName}
|
||||
</code>
|
||||
<span className="ml-2">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(`nx run ${projectName}:${targetName}`)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
{/* body */}
|
||||
{!collapsed && (
|
||||
<div className="p-4 text-base">
|
||||
<div className="mb-4 group">
|
||||
<h4 className="mb-4">
|
||||
{singleCommand ? (
|
||||
<span className="font-bold">
|
||||
Command
|
||||
<span className="hidden group-hover:inline ml-2 mb-1">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(`"command": "${singleCommand}"`)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
) : (
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={(<PropertyInfoTooltip type="executors" />) as any}
|
||||
>
|
||||
<span className="font-bold">
|
||||
<TooltipTriggerText>Executor</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</h4>
|
||||
<p className="pl-5">
|
||||
{executorLink ? (
|
||||
<ExternalLink
|
||||
href={executorLink ?? 'https://nx.dev/nx-api'}
|
||||
text={
|
||||
singleCommand
|
||||
? singleCommand
|
||||
: targetConfiguration.executor
|
||||
}
|
||||
title="View Documentation"
|
||||
/>
|
||||
) : 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-bold">
|
||||
<TooltipTriggerText>Inputs</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="hidden group-hover:inline ml-2 mb-1">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"inputs": ${JSON.stringify(
|
||||
targetConfiguration.inputs
|
||||
)}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
<ul className="list-disc pl-5 mb-4">
|
||||
{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="hidden group-hover/line:inline pl-4">
|
||||
<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-bold">
|
||||
<TooltipTriggerText>Outputs</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="hidden group-hover:inline ml-2 mb-1">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"outputs": ${JSON.stringify(
|
||||
targetConfiguration.outputs
|
||||
)}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
<ul className="list-disc pl-5 mb-4">
|
||||
{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="hidden group-hover/line:inline pl-4">
|
||||
<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-bold">
|
||||
<TooltipTriggerText>Depends On</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<span className="hidden group-hover:inline ml-2 mb-1">
|
||||
<CopyToClipboard
|
||||
onCopy={() =>
|
||||
handleCopyClick(
|
||||
`"dependsOn": ${JSON.stringify(
|
||||
targetConfiguration.dependsOn
|
||||
)}`
|
||||
)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</h4>
|
||||
<ul className="list-disc pl-5 mb-4">
|
||||
{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="hidden group-hover/line:inline pl-4 h-6">
|
||||
{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-bold">
|
||||
<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="pl-4">
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.options.${propertyName}`}
|
||||
/>
|
||||
</span>
|
||||
) : null;
|
||||
}}
|
||||
/>
|
||||
</FadingCollapsible>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
|
||||
{shouldRenderConfigurations ? (
|
||||
<>
|
||||
<h4 className="py-2 mb-4">
|
||||
<Tooltip
|
||||
openAction="hover"
|
||||
content={
|
||||
(<PropertyInfoTooltip type="configurations" />) as any
|
||||
}
|
||||
>
|
||||
<span className="font-bold">
|
||||
<TooltipTriggerText>Configurations</TooltipTriggerText>
|
||||
</span>
|
||||
</Tooltip>{' '}
|
||||
{targetConfiguration.defaultConfiguration && (
|
||||
<span
|
||||
className="ml-3 font-bold rounded-full inline-block text-xs bg-sky-500 px-2 text-slate-50 mr-6"
|
||||
title="Default Configuration"
|
||||
>
|
||||
{targetConfiguration.defaultConfiguration}
|
||||
</span>
|
||||
)}
|
||||
</h4>
|
||||
<FadingCollapsible>
|
||||
<JsonCodeBlock
|
||||
data={targetConfiguration.configurations}
|
||||
renderSource={(propertyName: string) => {
|
||||
const sourceInfo = selectSourceInfo(
|
||||
sourceMap,
|
||||
`targets.${targetName}.configurations.${propertyName}`
|
||||
);
|
||||
return sourceInfo ? (
|
||||
<span className="pl-4">
|
||||
<SourceInfo
|
||||
data={sourceInfo}
|
||||
propertyKey={`targets.${targetName}.configurations.${propertyName}`}
|
||||
/>{' '}
|
||||
</span>
|
||||
) : null;
|
||||
}}
|
||||
/>
|
||||
</FadingCollapsible>
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default TargetConfigurationDetails;
|
||||
45
graph/ui-project-details/tailwind.config.js
Normal file
45
graph/ui-project-details/tailwind.config.js
Normal file
@ -0,0 +1,45 @@
|
||||
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'),
|
||||
require('@tailwindcss/forms')({
|
||||
strategy: 'class',
|
||||
}),
|
||||
],
|
||||
};
|
||||
21
graph/ui-project-details/tsconfig.json
Normal file
21
graph/ui-project-details/tsconfig.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"lib": ["ES2022", "dom"]
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.storybook.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../tsconfig.base.json"
|
||||
}
|
||||
27
graph/ui-project-details/tsconfig.lib.json
Normal file
27
graph/ui-project-details/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-project-details/tsconfig.storybook.json
Normal file
31
graph/ui-project-details/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"
|
||||
]
|
||||
}
|
||||
@ -33,6 +33,8 @@ import { InstallNxConsole } from './lib/tags/install-nx-console.component';
|
||||
import { installNxConsole } from './lib/tags/install-nx-console.schema';
|
||||
import { Persona, Personas } from './lib/tags/personas.component';
|
||||
import { persona, personas } from './lib/tags/personas.schema';
|
||||
import { ProjectDetails } from './lib/tags/project-details.component';
|
||||
import { projectDetails } from './lib/tags/project-details.schema';
|
||||
import {
|
||||
ShortEmbeds,
|
||||
shortEmbeds,
|
||||
@ -83,6 +85,7 @@ export const getMarkdocCustomConfig = (
|
||||
'install-nx-console': installNxConsole,
|
||||
persona,
|
||||
personas,
|
||||
'project-details': projectDetails,
|
||||
pill,
|
||||
'short-embeds': shortEmbeds,
|
||||
'short-video': shortVideo,
|
||||
@ -112,6 +115,7 @@ export const getMarkdocCustomConfig = (
|
||||
InstallNxConsole,
|
||||
Persona,
|
||||
Personas,
|
||||
ProjectDetails,
|
||||
Pill,
|
||||
ShortEmbeds,
|
||||
ShortVideo,
|
||||
|
||||
@ -91,7 +91,7 @@ export function Graph({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="my-6 w-full place-content-center overflow-hidden rounded-md ring-1 ring-slate-100 dark:ring-slate-700">
|
||||
<div className="my-6 w-full place-content-center overflow-hidden rounded-md ring-1 ring-slate-200 dark:ring-slate-700">
|
||||
<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">
|
||||
{title}
|
||||
</div>
|
||||
|
||||
93
nx-dev/ui-markdoc/src/lib/tags/project-details.component.tsx
Normal file
93
nx-dev/ui-markdoc/src/lib/tags/project-details.component.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
import { useTheme } from '@nx/nx-dev/ui-theme';
|
||||
import { JSX, ReactElement, useEffect, useState } from 'react';
|
||||
import { ProjectDetails as ProjectDetailsUi } from '@nx/graph/ui-project-details';
|
||||
|
||||
export function Loading() {
|
||||
return (
|
||||
<div className="flex h-[450px] w-full items-center justify-center">
|
||||
<div
|
||||
className="spinner-border inline-block h-8 w-8 animate-spin rounded-full border-4 border-slate-200 border-r-slate-400 dark:border-slate-700 dark:border-r-slate-500"
|
||||
role="status"
|
||||
>
|
||||
<span className="sr-only">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProjectDetails({
|
||||
height,
|
||||
title,
|
||||
jsonFile,
|
||||
children,
|
||||
}: {
|
||||
height: string;
|
||||
title: string;
|
||||
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, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
setParsedProps(await response.json());
|
||||
};
|
||||
useEffect(() => {
|
||||
if (jsonFile) {
|
||||
getData(jsonFile);
|
||||
}
|
||||
}, [jsonFile, setParsedProps]);
|
||||
|
||||
if (!jsonFile && !parsedProps) {
|
||||
if (!children || !children.hasOwnProperty('props')) {
|
||||
return (
|
||||
<div className="no-prose my-6 block rounded-md bg-red-50 p-4 text-red-700 ring-1 ring-red-100 dark:bg-red-900/30 dark:text-red-600 dark:ring-red-900">
|
||||
<p className="mb-4">
|
||||
No JSON provided for graph, use JSON code fence to embed data for
|
||||
the graph.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
setParsedProps(JSON.parse(children?.props.children as any));
|
||||
} catch {
|
||||
return (
|
||||
<div className="not-prose my-6 block rounded-md bg-red-50 p-4 text-red-700 ring-1 ring-red-100 dark:bg-red-900/30 dark:text-red-600 dark:ring-red-900">
|
||||
<p className="mb-4">Could not parse JSON for graph:</p>
|
||||
<pre className="p-4 text-sm">{children?.props.children as any}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!parsedProps) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
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">
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`not-prose ${
|
||||
height ? `p-4 h-[${height}] overflow-y-auto` : 'p-4'
|
||||
}`}
|
||||
>
|
||||
<ProjectDetailsUi
|
||||
project={parsedProps.project}
|
||||
sourceMap={parsedProps.sourceMap}
|
||||
variant="compact"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
nx-dev/ui-markdoc/src/lib/tags/project-details.schema.ts
Normal file
18
nx-dev/ui-markdoc/src/lib/tags/project-details.schema.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Schema } from '@markdoc/markdoc';
|
||||
|
||||
export const projectDetails: Schema = {
|
||||
render: 'ProjectDetails',
|
||||
children: [],
|
||||
|
||||
attributes: {
|
||||
jsonFile: {
|
||||
type: 'String',
|
||||
},
|
||||
title: {
|
||||
type: 'String',
|
||||
},
|
||||
height: {
|
||||
type: 'String',
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -40,6 +40,7 @@
|
||||
"@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-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"],
|
||||
"@nx/jest": ["packages/jest"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user