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" /%}
|
{% 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
|
#### Graph
|
||||||
|
|
||||||
Embed an Nx Graph visualization that can be panned by the user.
|
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 */
|
/* eslint-disable @nx/enforce-module-boundaries */
|
||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
||||||
/* eslint-enable @nx/enforce-module-boundaries */
|
|
||||||
import { ProjectDetailsPage } from '@nx/graph/project-details';
|
|
||||||
import {
|
import {
|
||||||
getEnvironmentConfig,
|
getEnvironmentConfig,
|
||||||
getProjectGraphDataService,
|
getProjectGraphDataService,
|
||||||
} from '@nx/graph/shared';
|
} from '@nx/graph/shared';
|
||||||
import { TasksSidebarErrorBoundary } from './feature-tasks/tasks-sidebar-error-boundary';
|
import { TasksSidebarErrorBoundary } from './feature-tasks/tasks-sidebar-error-boundary';
|
||||||
|
import { ProjectDetailsPage } from '@nx/graph/project-details';
|
||||||
|
|
||||||
const { appConfig } = getEnvironmentConfig();
|
const { appConfig } = getEnvironmentConfig();
|
||||||
const projectGraphDataService = getProjectGraphDataService();
|
const projectGraphDataService = getProjectGraphDataService();
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
import { useFloating } from '@floating-ui/react';
|
import { useFloating } from '@floating-ui/react';
|
||||||
import { XMarkIcon } from '@heroicons/react/24/outline';
|
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 */
|
/* eslint-disable @nx/enforce-module-boundaries */
|
||||||
// nx-ignore-next-line
|
// nx-ignore-next-line
|
||||||
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
import { ProjectGraphClientResponse } from 'nx/src/command-line/graph/graph';
|
||||||
@ -46,6 +46,7 @@ export function ProjectDetailsModal() {
|
|||||||
setSearchParams(searchParams);
|
setSearchParams(searchParams);
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isOpen && (
|
isOpen && (
|
||||||
<div
|
<div
|
||||||
@ -56,7 +57,7 @@ export function ProjectDetailsModal() {
|
|||||||
ref={refs.setFloating}
|
ref={refs.setFloating}
|
||||||
>
|
>
|
||||||
<div className="rounded-md h-full border border-slate-500">
|
<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}>
|
<div className="top-2 right-2 absolute" onClick={onClose}>
|
||||||
<XMarkIcon className="h-4 w-4" />
|
<XMarkIcon className="h-4 w-4" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
export * from './lib/project-details';
|
export * from './lib/project-details-wrapper';
|
||||||
export * from './lib/project-details-page';
|
export * from './lib/project-details-page';
|
||||||
|
|||||||
@ -4,12 +4,10 @@ import { ProjectGraphProjectNode } from '@nx/devkit';
|
|||||||
import {
|
import {
|
||||||
Link,
|
Link,
|
||||||
ScrollRestoration,
|
ScrollRestoration,
|
||||||
useLocation,
|
|
||||||
useNavigate,
|
|
||||||
useParams,
|
useParams,
|
||||||
useRouteLoaderData,
|
useRouteLoaderData,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import ProjectDetails from './project-details';
|
import { ProjectDetailsWrapper } from './project-details-wrapper';
|
||||||
import {
|
import {
|
||||||
fetchProjectGraph,
|
fetchProjectGraph,
|
||||||
getProjectGraphDataService,
|
getProjectGraphDataService,
|
||||||
@ -54,7 +52,7 @@ export function ProjectDetailsPage() {
|
|||||||
<div className="flex flex-col justify-center w-full text-slate-700 dark:text-slate-400">
|
<div className="flex flex-col justify-center w-full text-slate-700 dark:text-slate-400">
|
||||||
<ScrollRestoration />
|
<ScrollRestoration />
|
||||||
{environment !== 'nx-console' ? (
|
{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">
|
<div className="flex flex-grow items-center justify-between max-w-6xl">
|
||||||
<svg
|
<svg
|
||||||
className="h-10 w-auto text-slate-900 dark:text-white"
|
className="h-10 w-auto text-slate-900 dark:text-white"
|
||||||
@ -85,10 +83,10 @@ export function ProjectDetailsPage() {
|
|||||||
</header>
|
</header>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="flex-grow mx-auto w-full max-w-6xl px-8 mb-8">
|
<div className="flex-grow mx-auto w-full max-w-6xl px-8 mb-8">
|
||||||
<ProjectDetails
|
<ProjectDetailsWrapper
|
||||||
project={project}
|
project={project}
|
||||||
sourceMap={sourceMap}
|
sourceMap={sourceMap}
|
||||||
></ProjectDetails>
|
></ProjectDetailsWrapper>
|
||||||
</div>
|
</div>
|
||||||
</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
|
<SourcemapInfoToolTip
|
||||||
propertyKey={props.propertyKey}
|
propertyKey={props.propertyKey}
|
||||||
plugin={props.data[1]}
|
plugin={props.data?.[1]}
|
||||||
file={props.data[0]}
|
file={props.data?.[0]}
|
||||||
/>
|
/>
|
||||||
) as any
|
) as any
|
||||||
}
|
}
|
||||||
@ -29,7 +29,8 @@ export function SourceInfo(props: {
|
|||||||
{/* <InformationCircleIcon className="w-3 h-3" />*/}
|
{/* <InformationCircleIcon className="w-3 h-3" />*/}
|
||||||
{/*</span>*/}
|
{/*</span>*/}
|
||||||
<span className="italic text-gray-500">
|
<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>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</span>
|
</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 { installNxConsole } from './lib/tags/install-nx-console.schema';
|
||||||
import { Persona, Personas } from './lib/tags/personas.component';
|
import { Persona, Personas } from './lib/tags/personas.component';
|
||||||
import { persona, personas } from './lib/tags/personas.schema';
|
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 {
|
import {
|
||||||
ShortEmbeds,
|
ShortEmbeds,
|
||||||
shortEmbeds,
|
shortEmbeds,
|
||||||
@ -83,6 +85,7 @@ export const getMarkdocCustomConfig = (
|
|||||||
'install-nx-console': installNxConsole,
|
'install-nx-console': installNxConsole,
|
||||||
persona,
|
persona,
|
||||||
personas,
|
personas,
|
||||||
|
'project-details': projectDetails,
|
||||||
pill,
|
pill,
|
||||||
'short-embeds': shortEmbeds,
|
'short-embeds': shortEmbeds,
|
||||||
'short-video': shortVideo,
|
'short-video': shortVideo,
|
||||||
@ -112,6 +115,7 @@ export const getMarkdocCustomConfig = (
|
|||||||
InstallNxConsole,
|
InstallNxConsole,
|
||||||
Persona,
|
Persona,
|
||||||
Personas,
|
Personas,
|
||||||
|
ProjectDetails,
|
||||||
Pill,
|
Pill,
|
||||||
ShortEmbeds,
|
ShortEmbeds,
|
||||||
ShortVideo,
|
ShortVideo,
|
||||||
|
|||||||
@ -91,7 +91,7 @@ export function Graph({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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">
|
<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}
|
{title}
|
||||||
</div>
|
</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-code-block": ["graph/ui-code-block/src/index.ts"],
|
||||||
"@nx/graph/ui-components": ["graph/ui-components/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-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-theme": ["graph/ui-theme/src/index.ts"],
|
||||||
"@nx/graph/ui-tooltips": ["graph/ui-tooltips/src/index.ts"],
|
"@nx/graph/ui-tooltips": ["graph/ui-tooltips/src/index.ts"],
|
||||||
"@nx/jest": ["packages/jest"],
|
"@nx/jest": ["packages/jest"],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user