feat(graph): add tooltips to docs graph (#13832)
This commit is contained in:
parent
f4802ae579
commit
578ecb6785
@ -126,7 +126,8 @@ Try playing around with a [fully interactive graph on a sample repo](https://nrw
|
||||
"affectedProjectIds": [],
|
||||
"focus": null,
|
||||
"groupByFolder": false,
|
||||
"exclude": []
|
||||
"exclude": [],
|
||||
"enableTooltips": true
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
11
graph/.storybook/main.js
Normal file
11
graph/.storybook/main.js
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
stories: [],
|
||||
addons: ['@storybook/addon-essentials'],
|
||||
// uncomment the property below if you want to apply some webpack config globally
|
||||
// webpackFinal: async (config, { configType }) => {
|
||||
// // Make whatever fine-grained changes you need that should apply to all storybook configs
|
||||
|
||||
// // Return the altered config
|
||||
// return config;
|
||||
// },
|
||||
};
|
||||
0
graph/.storybook/preview.js
Normal file
0
graph/.storybook/preview.js
Normal file
@ -1,5 +1,5 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"exclude": [
|
||||
"../**/*.spec.js",
|
||||
"../**/*.test.js",
|
||||
@ -1,4 +1,4 @@
|
||||
const rootMain = require('../../../.storybook/main');
|
||||
const rootMain = require('../../.storybook/main');
|
||||
|
||||
module.exports = {
|
||||
...rootMain,
|
||||
|
||||
@ -3,16 +3,10 @@ import '../src/styles.scss';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import { themes } from '@storybook/theming';
|
||||
import { rootParameters } from '../../.storybook/preview';
|
||||
|
||||
export const parameters = {
|
||||
darkMode: {
|
||||
// Override the default dark theme
|
||||
dark: { ...themes.dark, appContentBg: 'rgb(15, 23, 42, 1)' },
|
||||
// Override the default light theme
|
||||
light: themes.normal,
|
||||
stylePreview: true,
|
||||
},
|
||||
...rootParameters,
|
||||
};
|
||||
export const decorators = [
|
||||
(Story, context) => {
|
||||
|
||||
3
graph/client/.storybook/tailwind-imports.css
Normal file
3
graph/client/.storybook/tailwind-imports.css
Normal file
@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@ -1,10 +1,6 @@
|
||||
import { themeInit } from './theme-resolver';
|
||||
import { rankDirInit } from './rankdir-resolver';
|
||||
import {
|
||||
createBrowserRouter,
|
||||
createHashRouter,
|
||||
RouterProvider,
|
||||
} from 'react-router-dom';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
import { getRouter } from './get-router';
|
||||
|
||||
themeInit();
|
||||
@ -13,5 +9,3 @@ rankDirInit();
|
||||
export function App() {
|
||||
return <RouterProvider router={getRouter()} />;
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@ -37,5 +37,3 @@ export const CollapseEdgesPanel = memo(
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default CollapseEdgesPanel;
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { memo } from 'react';
|
||||
import CheckboxPanel from '../../ui-components/checkbox-panel';
|
||||
import { CheckboxPanel } from '../../ui-components/checkbox-panel';
|
||||
|
||||
export interface DisplayOptionsPanelProps {
|
||||
groupByFolder: boolean;
|
||||
@ -20,5 +19,3 @@ export const GroupByFolderPanel = ({
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default GroupByFolderPanel;
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
rankDirResolver,
|
||||
} from '../../rankdir-resolver';
|
||||
|
||||
export default function RankdirPanel(): JSX.Element {
|
||||
export function RankdirPanel(): JSX.Element {
|
||||
const [rankDir, setRankDir] = useState(
|
||||
(localStorage.getItem(localStorageRankDirKey) as RankDir) || 'TB'
|
||||
);
|
||||
|
||||
@ -76,5 +76,3 @@ export const SearchDepth = memo(
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default SearchDepth;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import DebouncedTextInput from '../../ui-components/debounced-text-input';
|
||||
import { DebouncedTextInput } from '@nrwl/graph/ui-components';
|
||||
|
||||
export interface TextFilterPanelProps {
|
||||
textFilter: string;
|
||||
@ -56,5 +56,3 @@ export function TextFilterPanel({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TextFilterPanel;
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
themeResolver,
|
||||
} from '../../theme-resolver';
|
||||
|
||||
export default function ThemePanel(): JSX.Element {
|
||||
export function ThemePanel(): JSX.Element {
|
||||
const [theme, setTheme] = useState(
|
||||
(localStorage.getItem(localStorageThemeKey) as Theme) || 'system'
|
||||
);
|
||||
|
||||
@ -111,5 +111,3 @@ export const TracingPanel = memo(
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default TracingPanel;
|
||||
|
||||
@ -18,7 +18,7 @@ import {
|
||||
parseParentDirectoriesFromFilePath,
|
||||
useRouteConstructor,
|
||||
} from '../util';
|
||||
import ExperimentalFeature from '../ui-components/experimental-feature';
|
||||
import { ExperimentalFeature } from '../ui-components/experimental-feature';
|
||||
import { TracingAlgorithmType } from './machines/interfaces';
|
||||
import { getProjectGraphService } from '../machines/get-services';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
@ -317,5 +317,3 @@ export function ProjectList() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProjectList;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import ExperimentalFeature from '../ui-components/experimental-feature';
|
||||
import { ExperimentalFeature } from '../ui-components/experimental-feature';
|
||||
import { useProjectGraphSelector } from './hooks/use-project-graph-selector';
|
||||
import {
|
||||
collapseEdgesSelector,
|
||||
@ -11,14 +11,14 @@ import {
|
||||
searchDepthSelector,
|
||||
textFilterSelector,
|
||||
} from './machines/selectors';
|
||||
import CollapseEdgesPanel from './panels/collapse-edges-panel';
|
||||
import FocusedPanel from '../ui-components/focused-panel';
|
||||
import GroupByFolderPanel from './panels/group-by-folder-panel';
|
||||
import ProjectList from './project-list';
|
||||
import SearchDepth from './panels/search-depth';
|
||||
import ShowHideProjects from '../ui-components/show-hide-all';
|
||||
import TextFilterPanel from './panels/text-filter-panel';
|
||||
import TracingPanel from './panels/tracing-panel';
|
||||
import { CollapseEdgesPanel } from './panels/collapse-edges-panel';
|
||||
import { FocusedPanel } from '../ui-components/focused-panel';
|
||||
import { GroupByFolderPanel } from './panels/group-by-folder-panel';
|
||||
import { ProjectList } from './project-list';
|
||||
import { SearchDepth } from './panels/search-depth';
|
||||
import { ShowHideAll } from '../ui-components/show-hide-all';
|
||||
import { TextFilterPanel } from './panels/text-filter-panel';
|
||||
import { TracingPanel } from './panels/tracing-panel';
|
||||
import { useEnvironmentConfig } from '../hooks/use-environment-config';
|
||||
import { TracingAlgorithmType } from './machines/interfaces';
|
||||
import { getProjectGraphService } from '../machines/get-services';
|
||||
@ -352,13 +352,13 @@ export function ProjectsSidebar(): JSX.Element {
|
||||
></TextFilterPanel>
|
||||
|
||||
<div>
|
||||
<ShowHideProjects
|
||||
<ShowHideAll
|
||||
hideAll={hideAllProjects}
|
||||
showAll={showAllProjects}
|
||||
showAffected={showAffectedProjects}
|
||||
hasAffected={hasAffectedProjects}
|
||||
label="projects"
|
||||
></ShowHideProjects>
|
||||
></ShowHideAll>
|
||||
|
||||
<GroupByFolderPanel
|
||||
groupByFolder={groupByFolder}
|
||||
@ -392,5 +392,3 @@ export function ProjectsSidebar(): JSX.Element {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProjectsSidebar;
|
||||
|
||||
@ -187,5 +187,3 @@ export function TaskList({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TaskList;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import TaskList from './task-list';
|
||||
import { TaskList } from './task-list';
|
||||
import {
|
||||
useNavigate,
|
||||
useParams,
|
||||
@ -12,11 +12,10 @@ import type {
|
||||
} from 'nx/src/command-line/dep-graph';
|
||||
import { getGraphService } from '../machines/graph.service';
|
||||
import { useEffect, useState } from 'react';
|
||||
import CheckboxPanel from '../ui-components/checkbox-panel';
|
||||
import { CheckboxPanel } from '../ui-components/checkbox-panel';
|
||||
|
||||
// nx-ignore-next-line
|
||||
import Dropdown from '../ui-components/dropdown';
|
||||
import ShowHideAll from '../ui-components/show-hide-all';
|
||||
import { Dropdown } from '@nrwl/graph/ui-components';
|
||||
import { ShowHideAll } from '../ui-components/show-hide-all';
|
||||
|
||||
function createTaskName(
|
||||
project: string,
|
||||
@ -217,5 +216,3 @@ export function TasksSidebar() {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default TasksSidebar;
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { interpret, InterpreterStatus } from 'xstate';
|
||||
import { projectGraphMachine } from '../feature-projects/machines/project-graph.machine';
|
||||
import { getGraphService } from './graph.service';
|
||||
import { GraphTooltipService } from '@nrwl/graph/ui-graph';
|
||||
|
||||
let projectGraphService = interpret(projectGraphMachine, {
|
||||
devTools: !!window.useXstateInspect,
|
||||
@ -12,3 +14,14 @@ export function getProjectGraphService() {
|
||||
|
||||
return projectGraphService;
|
||||
}
|
||||
|
||||
let tooltipService: GraphTooltipService;
|
||||
|
||||
export function getTooltipService(): GraphTooltipService {
|
||||
if (!tooltipService) {
|
||||
const graph = getGraphService();
|
||||
tooltipService = new GraphTooltipService(graph);
|
||||
}
|
||||
|
||||
return tooltipService;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Shell } from './shell';
|
||||
import { redirect, RouteObject } from 'react-router-dom';
|
||||
import ProjectsSidebar from './feature-projects/projects-sidebar';
|
||||
import TasksSidebar from './feature-tasks/tasks-sidebar';
|
||||
import { ProjectsSidebar } from './feature-projects/projects-sidebar';
|
||||
import { TasksSidebar } from './feature-tasks/tasks-sidebar';
|
||||
import { getEnvironmentConfig } from './hooks/use-environment-config';
|
||||
// nx-ignore-next-line
|
||||
import { ProjectGraphClientResponse } from 'nx/src/command-line/dep-graph';
|
||||
|
||||
@ -4,22 +4,19 @@ import {
|
||||
InformationCircleIcon,
|
||||
} from '@heroicons/react/24/outline';
|
||||
import classNames from 'classnames';
|
||||
// nx-ignore-next-line
|
||||
|
||||
import DebuggerPanel from './ui-components/debugger-panel';
|
||||
import { DebuggerPanel } from './ui-components/debugger-panel';
|
||||
import { useEnvironmentConfig } from './hooks/use-environment-config';
|
||||
import { getGraphService } from './machines/graph.service';
|
||||
import { selectValueByThemeStatic } from './theme-resolver';
|
||||
import { Outlet, useNavigate, useParams } from 'react-router-dom';
|
||||
import ThemePanel from './feature-projects/panels/theme-panel';
|
||||
import Dropdown from './ui-components/dropdown';
|
||||
import { ThemePanel } from './feature-projects/panels/theme-panel';
|
||||
import { Dropdown } from '@nrwl/graph/ui-components';
|
||||
import { useCurrentPath } from './hooks/use-current-path';
|
||||
import ExperimentalFeature from './ui-components/experimental-feature';
|
||||
import RankdirPanel from './feature-projects/panels/rankdir-panel';
|
||||
import { ExperimentalFeature } from './ui-components/experimental-feature';
|
||||
import { RankdirPanel } from './feature-projects/panels/rankdir-panel';
|
||||
import { getProjectGraphService } from './machines/get-services';
|
||||
import TooltipDisplay from './ui-tooltips/graph-tooltip-display';
|
||||
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
||||
import { Tooltip } from './ui-tooltips/tooltip';
|
||||
import { Tooltip } from '@nrwl/graph/ui-tooltips';
|
||||
import { TooltipDisplay } from './ui-tooltips/graph-tooltip-display';
|
||||
|
||||
export function Shell(): JSX.Element {
|
||||
const projectGraphService = getProjectGraphService();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import CheckboxPanel from './checkbox-panel';
|
||||
import { CheckboxPanel } from './checkbox-panel';
|
||||
|
||||
export default {
|
||||
component: CheckboxPanel,
|
||||
|
||||
@ -38,5 +38,3 @@ export const CheckboxPanel = memo(
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default CheckboxPanel;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { memo } from 'react';
|
||||
import { WorkspaceData, GraphPerfReport } from '../interfaces';
|
||||
import Dropdown from './dropdown';
|
||||
import { Dropdown } from '@nrwl/graph/ui-components';
|
||||
|
||||
export interface DebuggerPanelProps {
|
||||
projects: WorkspaceData[];
|
||||
@ -49,5 +49,3 @@ export const DebuggerPanel = memo(function ({
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default DebuggerPanel;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { useEnvironmentConfig } from '../hooks/use-environment-config';
|
||||
import { Children, cloneElement } from 'react';
|
||||
|
||||
function ExperimentalFeature(props) {
|
||||
export function ExperimentalFeature(props) {
|
||||
const environment = useEnvironmentConfig();
|
||||
const showExperimentalFeatures =
|
||||
environment.appConfig.showExperimentalFeatures;
|
||||
@ -11,5 +11,3 @@ function ExperimentalFeature(props) {
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
export default ExperimentalFeature;
|
||||
|
||||
@ -30,5 +30,3 @@ export const FocusedPanel = memo(
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default FocusedPanel;
|
||||
|
||||
@ -54,5 +54,3 @@ export const ShowHideAll = memo(
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default ShowHideAll;
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import ProjectNodeToolTip from './project-node-tooltip';
|
||||
import ProjectEdgeNodeTooltip from './project-edge-tooltip';
|
||||
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
||||
import { getTooltipService } from './tooltip-service';
|
||||
import TaskNodeTooltip from './task-node-tooltip';
|
||||
import { Tooltip } from './tooltip';
|
||||
import { getTooltipService } from '../machines/get-services';
|
||||
import {
|
||||
ProjectEdgeNodeTooltip,
|
||||
ProjectNodeToolTip,
|
||||
TaskNodeTooltip,
|
||||
Tooltip,
|
||||
} from '@nrwl/graph/ui-tooltips';
|
||||
import { ProjectNodeActions } from './project-node-actions';
|
||||
|
||||
const tooltipService = getTooltipService();
|
||||
|
||||
@ -16,7 +19,11 @@ export function TooltipDisplay() {
|
||||
if (currentTooltip) {
|
||||
switch (currentTooltip.type) {
|
||||
case 'projectNode':
|
||||
tooltipToRender = <ProjectNodeToolTip {...currentTooltip.props} />;
|
||||
tooltipToRender = (
|
||||
<ProjectNodeToolTip {...currentTooltip.props}>
|
||||
<ProjectNodeActions {...currentTooltip.props} />
|
||||
</ProjectNodeToolTip>
|
||||
);
|
||||
break;
|
||||
case 'projectEdge':
|
||||
tooltipToRender = <ProjectEdgeNodeTooltip {...currentTooltip.props} />;
|
||||
@ -37,5 +44,3 @@ export function TooltipDisplay() {
|
||||
></Tooltip>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export default TooltipDisplay;
|
||||
|
||||
65
graph/client/src/app/ui-tooltips/project-node-actions.tsx
Normal file
65
graph/client/src/app/ui-tooltips/project-node-actions.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import { ProjectNodeToolTipProps } from '@nrwl/graph/ui-tooltips';
|
||||
import { getProjectGraphService } from '../machines/get-services';
|
||||
import { useRouteConstructor } from '../util';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { TooltipButton, TooltipLinkButton } from '@nrwl/graph/ui-tooltips';
|
||||
import { FlagIcon, MapPinIcon } from '@heroicons/react/24/solid';
|
||||
|
||||
export function ProjectNodeActions({ id }: ProjectNodeToolTipProps) {
|
||||
const projectGraphService = getProjectGraphService();
|
||||
const { start, end, algorithm } =
|
||||
projectGraphService.getSnapshot().context.tracing;
|
||||
const routeConstructor = useRouteConstructor();
|
||||
const navigate = useNavigate();
|
||||
|
||||
function onExclude() {
|
||||
projectGraphService.send({
|
||||
type: 'deselectProject',
|
||||
projectName: id,
|
||||
});
|
||||
navigate(routeConstructor('/projects', true));
|
||||
}
|
||||
|
||||
function onStartTrace() {
|
||||
navigate(
|
||||
routeConstructor(`/projects/trace/${encodeURIComponent(id)}`, true)
|
||||
);
|
||||
}
|
||||
|
||||
function onEndTrace() {
|
||||
navigate(
|
||||
routeConstructor(
|
||||
`/projects/trace/${encodeURIComponent(start)}/${encodeURIComponent(
|
||||
id
|
||||
)}`,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<TooltipLinkButton to={routeConstructor(`/projects/${id}`, true)}>
|
||||
Focus
|
||||
</TooltipLinkButton>
|
||||
<TooltipButton onClick={onExclude}>Exclude</TooltipButton>
|
||||
{!start ? (
|
||||
<TooltipButton
|
||||
className="flex flex-row items-center"
|
||||
onClick={onStartTrace}
|
||||
>
|
||||
<MapPinIcon className="mr-2 h-5 w-5 text-slate-500"></MapPinIcon>
|
||||
Start
|
||||
</TooltipButton>
|
||||
) : (
|
||||
<TooltipButton
|
||||
className="flex flex-row items-center"
|
||||
onClick={onEndTrace}
|
||||
>
|
||||
<FlagIcon className="mr-2 h-5 w-5 text-slate-500"></FlagIcon>
|
||||
End
|
||||
</TooltipButton>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
import { getProjectGraphService } from '../machines/get-services';
|
||||
import { FlagIcon, MapPinIcon } from '@heroicons/react/24/solid';
|
||||
import Tag from '../ui-components/tag';
|
||||
import { TooltipButton, TooltipLinkButton } from './tooltip-button';
|
||||
import { useRouteConstructor } from '../util';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export interface ProjectNodeToolTipProps {
|
||||
type: 'app' | 'lib' | 'e2e';
|
||||
id: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export function ProjectNodeToolTip({
|
||||
type,
|
||||
id,
|
||||
tags,
|
||||
}: ProjectNodeToolTipProps) {
|
||||
const projectGraphService = getProjectGraphService();
|
||||
const { start, end, algorithm } =
|
||||
projectGraphService.getSnapshot().context.tracing;
|
||||
const routeConstructor = useRouteConstructor();
|
||||
const navigate = useNavigate();
|
||||
|
||||
function onExclude() {
|
||||
projectGraphService.send({
|
||||
type: 'deselectProject',
|
||||
projectName: id,
|
||||
});
|
||||
navigate(routeConstructor('/projects', true));
|
||||
}
|
||||
|
||||
function onStartTrace() {
|
||||
navigate(
|
||||
routeConstructor(`/projects/trace/${encodeURIComponent(id)}`, true)
|
||||
);
|
||||
}
|
||||
|
||||
function onEndTrace() {
|
||||
navigate(
|
||||
routeConstructor(
|
||||
`/projects/trace/${encodeURIComponent(start)}/${encodeURIComponent(
|
||||
id
|
||||
)}`,
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-sm text-slate-700 dark:text-slate-400">
|
||||
<h4>
|
||||
<Tag className="mr-3">{type}</Tag>
|
||||
<span className="font-mono">{id}</span>
|
||||
</h4>
|
||||
{tags.length > 0 ? (
|
||||
<p className="my-2">
|
||||
<strong>tags</strong>
|
||||
<br></br>
|
||||
{tags.join(', ')}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<TooltipLinkButton to={routeConstructor(`/projects/${id}`, true)}>
|
||||
Focus
|
||||
</TooltipLinkButton>
|
||||
<TooltipButton onClick={onExclude}>Exclude</TooltipButton>
|
||||
{!start ? (
|
||||
<TooltipButton
|
||||
className="flex flex-row items-center"
|
||||
onClick={onStartTrace}
|
||||
>
|
||||
<MapPinIcon className="mr-2 h-5 w-5 text-slate-500"></MapPinIcon>
|
||||
Start
|
||||
</TooltipButton>
|
||||
) : (
|
||||
<TooltipButton
|
||||
className="flex flex-row items-center"
|
||||
onClick={onEndTrace}
|
||||
>
|
||||
<FlagIcon className="mr-2 h-5 w-5 text-slate-500"></FlagIcon>
|
||||
End
|
||||
</TooltipButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProjectNodeToolTip;
|
||||
@ -1,26 +0,0 @@
|
||||
import Tag from '../ui-components/tag';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
export interface TaskNodeTooltipProps {
|
||||
id: string;
|
||||
executor: string;
|
||||
}
|
||||
|
||||
export function TaskNodeTooltip({ id, executor }: TaskNodeTooltipProps) {
|
||||
const params = useParams();
|
||||
const selectedWorkspaceId = params['selectedWorkspaceId'];
|
||||
|
||||
const to = selectedWorkspaceId
|
||||
? `/${selectedWorkspaceId}/tasks/${id}`
|
||||
: `/tasks/${id}`;
|
||||
return (
|
||||
<div className="text-sm text-slate-700 dark:text-slate-400">
|
||||
<h4>
|
||||
<Tag className="mr-3">{executor}</Tag>
|
||||
<span className="font-mono">{id}</span>
|
||||
</h4>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TaskNodeTooltip;
|
||||
@ -1,7 +1,7 @@
|
||||
import { StrictMode } from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { render } from 'react-dom';
|
||||
import { inspect } from '@xstate/inspect';
|
||||
import App from './app/app';
|
||||
import { App } from './app/app';
|
||||
import { ExternalApi } from './app/external-api';
|
||||
|
||||
if (window.useXstateInspect === true) {
|
||||
@ -14,7 +14,7 @@ if (window.useXstateInspect === true) {
|
||||
window.externalApi = new ExternalApi();
|
||||
|
||||
if (!window.appConfig) {
|
||||
ReactDOM.render(
|
||||
render(
|
||||
<p>
|
||||
No environment could be found. Please run{' '}
|
||||
<pre>npx nx run graph-client:generate-dev-environment-js</pre>.
|
||||
@ -22,7 +22,7 @@ if (!window.appConfig) {
|
||||
document.getElementById('app')
|
||||
);
|
||||
} else {
|
||||
ReactDOM.render(
|
||||
render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
const path = require('path');
|
||||
|
||||
// nx-ignore-next-line
|
||||
const { createGlobPatternsForDependencies } = require('@nrwl/react/tailwind');
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
path.join(__dirname, 'src/**/*.{js,ts,jsx,tsx,html}'),
|
||||
// ...createGlobPatternsForDependencies(__dirname),
|
||||
...createGlobPatternsForDependencies(__dirname),
|
||||
],
|
||||
darkMode: 'class', // or 'media' or 'class'
|
||||
theme: {
|
||||
|
||||
12
graph/ui-components/.babelrc
Normal file
12
graph/ui-components/.babelrc
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nrwl/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
18
graph/ui-components/.eslintrc.json
Normal file
18
graph/ui-components/.eslintrc.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
24
graph/ui-components/.storybook/main.js
Normal file
24
graph/ui-components/.storybook/main.js
Normal file
@ -0,0 +1,24 @@
|
||||
const rootMain = require('../../.storybook/main');
|
||||
|
||||
module.exports = {
|
||||
...rootMain,
|
||||
|
||||
core: { ...rootMain.core, builder: 'webpack5' },
|
||||
|
||||
stories: [
|
||||
...rootMain.stories,
|
||||
'../src/lib/**/*.stories.mdx',
|
||||
'../src/lib/**/*.stories.@(js|jsx|ts|tsx)',
|
||||
],
|
||||
addons: [...rootMain.addons, '@nrwl/react/plugins/storybook'],
|
||||
webpackFinal: async (config, { configType }) => {
|
||||
// apply any global webpack configs that might have been specified in .storybook/main.js
|
||||
if (rootMain.webpackFinal) {
|
||||
config = await rootMain.webpackFinal(config, { configType });
|
||||
}
|
||||
|
||||
// add your own webpack tweaks if needed
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
8
graph/ui-components/.storybook/preview.js
Normal file
8
graph/ui-components/.storybook/preview.js
Normal file
@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import '../../client/.storybook/tailwind-imports.css';
|
||||
|
||||
import { rootParameters } from '../../.storybook/preview';
|
||||
|
||||
export const parameters = {
|
||||
...rootParameters,
|
||||
};
|
||||
3
graph/ui-components/.storybook/tailwind-imports.css
Normal file
3
graph/ui-components/.storybook/tailwind-imports.css
Normal file
@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
26
graph/ui-components/.storybook/tsconfig.json
Normal file
26
graph/ui-components/.storybook/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": ""
|
||||
},
|
||||
"files": [
|
||||
"../../../node_modules/@nrwl/react/typings/styled-jsx.d.ts",
|
||||
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||
"../../../node_modules/@nrwl/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"../**/*.spec.ts",
|
||||
"../**/*.spec.js",
|
||||
"../**/*.spec.tsx",
|
||||
"../**/*.spec.jsx"
|
||||
],
|
||||
"include": [
|
||||
"../src/**/*.stories.ts",
|
||||
"../src/**/*.stories.js",
|
||||
"../src/**/*.stories.jsx",
|
||||
"../src/**/*.stories.tsx",
|
||||
"../src/**/*.stories.mdx",
|
||||
"*.js"
|
||||
]
|
||||
}
|
||||
7
graph/ui-components/README.md
Normal file
7
graph/ui-components/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# graph-ui-components
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test graph-ui-components` to execute the unit tests via [Jest](https://jestjs.io).
|
||||
10
graph/ui-components/jest.config.ts
Normal file
10
graph/ui-components/jest.config.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'graph-ui-components',
|
||||
preset: '../../jest.preset.js',
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/graph/ui-graph',
|
||||
};
|
||||
8
graph/ui-components/postcss.config.js
Normal file
8
graph/ui-components/postcss.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
config: './graph/ui-components/tailwind.config.js',
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
55
graph/ui-components/project.json
Normal file
55
graph/ui-components/project.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "graph-ui-components",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "graph/ui-components/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["graph/ui-components/**/*.{ts,tsx,js,jsx}"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "graph/ui-components/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
},
|
||||
"storybook": {
|
||||
"executor": "@nrwl/storybook:storybook",
|
||||
"options": {
|
||||
"uiFramework": "@storybook/react",
|
||||
"port": 4400,
|
||||
"config": {
|
||||
"configFolder": "graph/ui-components/.storybook"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"quiet": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"build-storybook": {
|
||||
"executor": "@nrwl/storybook:build",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"uiFramework": "@storybook/react",
|
||||
"outputPath": "dist/storybook/graph-ui-components",
|
||||
"config": {
|
||||
"configFolder": "graph/ui-components/.storybook"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"quiet": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
graph/ui-components/src/index.ts
Normal file
3
graph/ui-components/src/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './lib/debounced-text-input';
|
||||
export * from './lib/tag';
|
||||
export * from './lib/dropdown';
|
||||
@ -21,6 +21,6 @@ const Template: ComponentStory<typeof DebouncedTextInput> = (args) => (
|
||||
|
||||
export const Primary = Template.bind({});
|
||||
Primary.args = {
|
||||
currentText: '',
|
||||
initialText: '',
|
||||
placeholderText: '',
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
import { KeyboardEvent, useEffect, useState } from 'react';
|
||||
import { useDebounce } from '../hooks/use-debounce';
|
||||
import { useDebounce } from './use-debounce';
|
||||
import { BackspaceIcon, FunnelIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
export interface DebouncedTextInputProps {
|
||||
@ -83,5 +83,3 @@ export function DebouncedTextInput({
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export default DebouncedTextInput;
|
||||
@ -16,5 +16,3 @@ export function Dropdown(props: DropdownProps) {
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
export default Dropdown;
|
||||
@ -17,5 +17,3 @@ export function Tag({ className, children, ...rest }: TagProps) {
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export default Tag;
|
||||
40
graph/ui-components/tailwind.config.js
Normal file
40
graph/ui-components/tailwind.config.js
Normal file
@ -0,0 +1,40 @@
|
||||
const path = require('path');
|
||||
|
||||
// nx-ignore-next-line
|
||||
const { createGlobPatternsForDependencies } = require('@nrwl/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')],
|
||||
};
|
||||
23
graph/ui-components/tsconfig.json
Normal file
23
graph/ui-components/tsconfig.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"path": "./.storybook/tsconfig.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../tsconfig.base.json"
|
||||
}
|
||||
28
graph/ui-components/tsconfig.lib.json
Normal file
28
graph/ui-components/tsconfig.lib.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["node"],
|
||||
"lib": ["dom"]
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nrwl/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"]
|
||||
}
|
||||
20
graph/ui-components/tsconfig.spec.json
Normal file
20
graph/ui-components/tsconfig.spec.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
const rootMain = require('../../../.storybook/main');
|
||||
const rootMain = require('../../.storybook/main');
|
||||
|
||||
module.exports = {
|
||||
...rootMain,
|
||||
|
||||
26
graph/ui-graph/.storybook/preview-head.html
Normal file
26
graph/ui-graph/.storybook/preview-head.html
Normal file
@ -0,0 +1,26 @@
|
||||
<script>
|
||||
window.exclude = [];
|
||||
window.watch = false;
|
||||
window.environment = 'dev';
|
||||
window.useXstateInspect = false;
|
||||
|
||||
window.appConfig = {
|
||||
showDebugger: true,
|
||||
showExperimentalFeatures: true,
|
||||
workspaces: [
|
||||
{
|
||||
id: 'e2e',
|
||||
label: 'e2e',
|
||||
projectGraphUrl: 'assets/project-graphs/e2e.json',
|
||||
taskGraphUrl: 'assets/task-graphs/e2e.json',
|
||||
},
|
||||
{
|
||||
id: 'affected',
|
||||
label: 'affected',
|
||||
projectGraphUrl: 'assets/project-graphs/affected.json',
|
||||
taskGraphUrl: 'assets/task-graphs/affected.json',
|
||||
},
|
||||
],
|
||||
defaultWorkspaceId: 'e2e',
|
||||
};
|
||||
</script>
|
||||
@ -0,0 +1,5 @@
|
||||
import './tailwind-imports.css';
|
||||
|
||||
import { rootParameters } from '../../.storybook/preview';
|
||||
|
||||
export const parameters = { ...rootParameters };
|
||||
3
graph/ui-graph/.storybook/tailwind-imports.css
Normal file
3
graph/ui-graph/.storybook/tailwind-imports.css
Normal file
@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
10
graph/ui-graph/postcss.config.js
Normal file
10
graph/ui-graph/postcss.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
config: path.join(__dirname, 'tailwind.config.js'),
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
@ -1,4 +1,5 @@
|
||||
export * from './lib/nx-project-graph-viz';
|
||||
export * from './lib/nx-task-graph-viz';
|
||||
export * from './lib/graph';
|
||||
export * from './lib/tooltip-service';
|
||||
export * from './lib/graph-interaction-events';
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { VirtualElement } from '@floating-ui/react-dom';
|
||||
import { ProjectNodeDataDefinition } from './util-cytoscape/project-node';
|
||||
import { TaskNodeDataDefinition } from './util-cytoscape/task-node';
|
||||
import { ProjectEdgeDataDefinition } from './util-cytoscape';
|
||||
|
||||
interface ProjectNodeClickEvent {
|
||||
type: 'ProjectNodeClick';
|
||||
@ -20,20 +21,20 @@ interface EdgeClickEvent {
|
||||
type: 'EdgeClick';
|
||||
ref: VirtualElement;
|
||||
id: string;
|
||||
data: {
|
||||
type: string;
|
||||
source: string;
|
||||
target: string;
|
||||
fileDependencies: { fileName: string; target: string }[];
|
||||
};
|
||||
data: ProjectEdgeDataDefinition;
|
||||
}
|
||||
|
||||
interface GraphRegeneratedEvent {
|
||||
type: 'GraphRegenerated';
|
||||
}
|
||||
|
||||
interface BackgroundClickEvent {
|
||||
type: 'BackgroundClick';
|
||||
}
|
||||
|
||||
export type GraphInteractionEvents =
|
||||
| ProjectNodeClickEvent
|
||||
| EdgeClickEvent
|
||||
| GraphRegeneratedEvent
|
||||
| TaskNodeClickEvent;
|
||||
| TaskNodeClickEvent
|
||||
| BackgroundClickEvent;
|
||||
|
||||
@ -62,6 +62,7 @@ export class GraphService {
|
||||
}
|
||||
|
||||
broadcast(event: GraphInteractionEvents) {
|
||||
console.log(event);
|
||||
this.listeners.forEach((callback) => callback(event));
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,12 @@ import type {
|
||||
ProjectGraphProjectNode,
|
||||
TaskGraph,
|
||||
} from '@nrwl/devkit';
|
||||
import { VirtualElement } from '@floating-ui/react-dom';
|
||||
import {
|
||||
ProjectEdgeNodeTooltipProps,
|
||||
ProjectNodeToolTipProps,
|
||||
TaskNodeTooltipProps,
|
||||
} from '@nrwl/graph/ui-tooltips';
|
||||
|
||||
export interface GraphPerfReport {
|
||||
renderTime: number;
|
||||
@ -95,3 +101,16 @@ export type TaskGraphRenderEvents =
|
||||
type: 'setGroupByProject';
|
||||
groupByProject: boolean;
|
||||
};
|
||||
|
||||
export type TooltipEvent =
|
||||
| {
|
||||
ref: VirtualElement;
|
||||
type: 'projectNode';
|
||||
props: ProjectNodeToolTipProps;
|
||||
}
|
||||
| { ref: VirtualElement; type: 'taskNode'; props: TaskNodeTooltipProps }
|
||||
| {
|
||||
ref: VirtualElement;
|
||||
type: 'projectEdge';
|
||||
props: ProjectEdgeNodeTooltipProps;
|
||||
};
|
||||
|
||||
@ -3,7 +3,7 @@ import { NxProjectGraphViz } from './nx-project-graph-viz';
|
||||
|
||||
const Story: ComponentMeta<typeof NxProjectGraphViz> = {
|
||||
component: NxProjectGraphViz,
|
||||
title: 'NxGraphViz',
|
||||
title: 'NxProjectGraphViz',
|
||||
};
|
||||
export default Story;
|
||||
|
||||
@ -19,6 +19,13 @@ Primary.args = {
|
||||
name: 'app',
|
||||
data: {
|
||||
tags: ['scope:cart'],
|
||||
description: 'This is your top-level app',
|
||||
files: [
|
||||
{
|
||||
file: 'whatever.ts',
|
||||
deps: ['lib'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -26,6 +33,7 @@ Primary.args = {
|
||||
name: 'lib',
|
||||
data: {
|
||||
tags: ['scope:cart'],
|
||||
description: 'This lib implements some type of feature for your app.',
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -59,4 +67,5 @@ Primary.args = {
|
||||
affectedProjectIds: [],
|
||||
theme: 'light',
|
||||
height: '450px',
|
||||
enableTooltips: true,
|
||||
};
|
||||
@ -5,8 +5,18 @@ import type {
|
||||
} from 'nx/src/config/project-graph';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { GraphService } from './graph';
|
||||
import { useSyncExternalStore } from 'use-sync-external-store/shim';
|
||||
import {
|
||||
ProjectEdgeNodeTooltip,
|
||||
ProjectNodeToolTip,
|
||||
TaskNodeTooltip,
|
||||
Tooltip,
|
||||
} from '@nrwl/graph/ui-tooltips';
|
||||
import { GraphTooltipService } from './tooltip-service';
|
||||
import { TooltipEvent } from './interfaces';
|
||||
|
||||
type Theme = 'light' | 'dark' | 'system';
|
||||
|
||||
export interface GraphUiGraphProps {
|
||||
projects: ProjectGraphProjectNode[];
|
||||
groupByFolder: boolean;
|
||||
@ -15,6 +25,7 @@ export interface GraphUiGraphProps {
|
||||
affectedProjectIds: string[];
|
||||
theme: Theme;
|
||||
height: string;
|
||||
enableTooltips: boolean;
|
||||
}
|
||||
|
||||
function resolveTheme(theme: Theme): 'dark' | 'light' {
|
||||
@ -25,6 +36,7 @@ function resolveTheme(theme: Theme): 'dark' | 'light' {
|
||||
return darkMedia.matches ? 'dark' : 'light';
|
||||
}
|
||||
}
|
||||
|
||||
export function NxProjectGraphViz({
|
||||
projects,
|
||||
groupByFolder,
|
||||
@ -33,9 +45,12 @@ export function NxProjectGraphViz({
|
||||
affectedProjectIds,
|
||||
theme,
|
||||
height,
|
||||
enableTooltips,
|
||||
}: GraphUiGraphProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [graph, setGraph] = useState<GraphService>(null);
|
||||
const [currentTooltip, setCurrenTooltip] = useState<TooltipEvent>(null);
|
||||
|
||||
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>();
|
||||
|
||||
const newlyResolvedTheme = resolveTheme(theme);
|
||||
@ -47,6 +62,7 @@ export function NxProjectGraphViz({
|
||||
graph.theme = newlyResolvedTheme;
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (containerRef.current !== null) {
|
||||
import('./graph')
|
||||
@ -68,18 +84,48 @@ export function NxProjectGraphViz({
|
||||
collapseEdges: false,
|
||||
});
|
||||
graph.handleProjectEvent({ type: 'notifyGraphShowAllProjects' });
|
||||
setGraph(graph);
|
||||
|
||||
if (enableTooltips) {
|
||||
const tooltipService = new GraphTooltipService(graph);
|
||||
tooltipService.subscribe((tooltip) => {
|
||||
setCurrenTooltip(tooltip);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
let tooltipToRender;
|
||||
if (currentTooltip) {
|
||||
switch (currentTooltip.type) {
|
||||
case 'projectNode':
|
||||
tooltipToRender = <ProjectNodeToolTip {...currentTooltip.props} />;
|
||||
break;
|
||||
case 'projectEdge':
|
||||
tooltipToRender = <ProjectEdgeNodeTooltip {...currentTooltip.props} />;
|
||||
break;
|
||||
case 'taskNode':
|
||||
tooltipToRender = <TaskNodeTooltip {...currentTooltip.props} />;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="w-full"
|
||||
style={{ width: '100%', height }}
|
||||
></div>
|
||||
<div className="not-prose">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="w-full"
|
||||
style={{ width: '100%', height }}
|
||||
></div>
|
||||
{tooltipToRender ? (
|
||||
<Tooltip
|
||||
content={tooltipToRender}
|
||||
open={true}
|
||||
reference={currentTooltip.ref}
|
||||
placement="top"
|
||||
openAction="manual"
|
||||
></Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NxProjectGraphViz;
|
||||
|
||||
@ -24,6 +24,7 @@ Primary.args = {
|
||||
executor: '@nrwl/js:tsc',
|
||||
},
|
||||
},
|
||||
description: 'The app uses this task to build itself.',
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -36,6 +37,7 @@ Primary.args = {
|
||||
executor: '@nrwl/js:tsc',
|
||||
},
|
||||
},
|
||||
description: 'The lib uses this task to build itself.',
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -107,4 +109,5 @@ Primary.args = {
|
||||
},
|
||||
taskId: 'app:build',
|
||||
height: '450px',
|
||||
enableTooltips: true,
|
||||
};
|
||||
|
||||
@ -2,7 +2,14 @@
|
||||
import type { ProjectGraphProjectNode } from 'nx/src/config/project-graph';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { GraphService } from './graph';
|
||||
import { TaskGraphRecord } from './interfaces';
|
||||
import { TaskGraphRecord, TooltipEvent } from './interfaces';
|
||||
import {
|
||||
ProjectEdgeNodeTooltip,
|
||||
ProjectNodeToolTip,
|
||||
TaskNodeTooltip,
|
||||
Tooltip,
|
||||
} from '@nrwl/graph/ui-tooltips';
|
||||
import { GraphTooltipService } from './tooltip-service';
|
||||
|
||||
type Theme = 'light' | 'dark' | 'system';
|
||||
|
||||
@ -12,6 +19,7 @@ export interface TaskGraphUiGraphProps {
|
||||
taskId: string;
|
||||
theme: Theme;
|
||||
height: string;
|
||||
enableTooltips: boolean;
|
||||
}
|
||||
|
||||
function resolveTheme(theme: Theme): 'dark' | 'light' {
|
||||
@ -29,9 +37,12 @@ export function NxTaskGraphViz({
|
||||
taskGraphs,
|
||||
theme,
|
||||
height,
|
||||
enableTooltips,
|
||||
}: TaskGraphUiGraphProps) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [graph, setGraph] = useState<GraphService>(null);
|
||||
const [currentTooltip, setCurrenTooltip] = useState<TooltipEvent>(null);
|
||||
|
||||
const [resolvedTheme, setResolvedTheme] = useState<'light' | 'dark'>();
|
||||
|
||||
const newlyResolvedTheme = resolveTheme(theme);
|
||||
@ -64,17 +75,42 @@ export function NxTaskGraphViz({
|
||||
taskIds: [taskId],
|
||||
});
|
||||
setGraph(graph);
|
||||
|
||||
if (enableTooltips) {
|
||||
const tooltipService = new GraphTooltipService(graph);
|
||||
tooltipService.subscribe((tooltip) => {
|
||||
setCurrenTooltip(tooltip);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
let tooltipToRender;
|
||||
if (currentTooltip) {
|
||||
switch (currentTooltip.type) {
|
||||
case 'taskNode':
|
||||
tooltipToRender = <TaskNodeTooltip {...currentTooltip.props} />;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="w-full"
|
||||
style={{ width: '100%', height }}
|
||||
></div>
|
||||
<div className="not-prose">
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="w-full"
|
||||
style={{ width: '100%', height }}
|
||||
></div>
|
||||
{tooltipToRender ? (
|
||||
<Tooltip
|
||||
content={tooltipToRender}
|
||||
open={true}
|
||||
reference={currentTooltip.ref}
|
||||
placement="top"
|
||||
openAction="manual"
|
||||
></Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NxTaskGraphViz;
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
import { getGraphService } from '../machines/graph.service';
|
||||
|
||||
import { VirtualElement } from '@floating-ui/react-dom';
|
||||
import { ProjectNodeToolTipProps } from './project-node-tooltip';
|
||||
import { ProjectEdgeNodeTooltipProps } from './project-edge-tooltip';
|
||||
import { GraphService } from '@nrwl/graph/ui-graph';
|
||||
import { TaskNodeTooltipProps } from './task-node-tooltip';
|
||||
import { GraphService } from './graph';
|
||||
import {
|
||||
TaskNodeTooltipProps,
|
||||
ProjectNodeToolTipProps,
|
||||
ProjectEdgeNodeTooltipProps,
|
||||
} from '@nrwl/graph/ui-tooltips';
|
||||
import { TooltipEvent } from './interfaces';
|
||||
|
||||
export class GraphTooltipService {
|
||||
private subscribers: Set<Function> = new Set();
|
||||
@ -15,11 +16,15 @@ export class GraphTooltipService {
|
||||
case 'GraphRegenerated':
|
||||
this.hideAll();
|
||||
break;
|
||||
case 'BackgroundClick':
|
||||
this.hideAll();
|
||||
break;
|
||||
case 'ProjectNodeClick':
|
||||
this.openProjectNodeToolTip(event.ref, {
|
||||
id: event.data.id,
|
||||
tags: event.data.tags,
|
||||
type: event.data.type,
|
||||
description: event.data.description,
|
||||
});
|
||||
break;
|
||||
case 'TaskNodeClick':
|
||||
@ -39,18 +44,7 @@ export class GraphTooltipService {
|
||||
});
|
||||
}
|
||||
|
||||
currentTooltip:
|
||||
| {
|
||||
ref: VirtualElement;
|
||||
type: 'projectNode';
|
||||
props: ProjectNodeToolTipProps;
|
||||
}
|
||||
| { ref: VirtualElement; type: 'taskNode'; props: TaskNodeTooltipProps }
|
||||
| {
|
||||
ref: VirtualElement;
|
||||
type: 'projectEdge';
|
||||
props: ProjectEdgeNodeTooltipProps;
|
||||
};
|
||||
currentTooltip: TooltipEvent;
|
||||
|
||||
openProjectNodeToolTip(ref: VirtualElement, props: ProjectNodeToolTipProps) {
|
||||
this.currentTooltip = { type: 'projectNode', ref, props };
|
||||
@ -84,14 +78,3 @@ export class GraphTooltipService {
|
||||
this.broadcastChange();
|
||||
}
|
||||
}
|
||||
|
||||
let tooltipService: GraphTooltipService;
|
||||
|
||||
export function getTooltipService(): GraphTooltipService {
|
||||
if (!tooltipService) {
|
||||
const graph = getGraphService();
|
||||
tooltipService = new GraphTooltipService(graph);
|
||||
}
|
||||
|
||||
return tooltipService;
|
||||
}
|
||||
@ -5,7 +5,7 @@ export class ParentNode {
|
||||
private config: { id: string; parentId: string; label: string }
|
||||
) {}
|
||||
|
||||
getCytoscapeNodeDef(): cy.NodeDefinition {
|
||||
getCytoscapeNodeDef(): cy.NodeDefinition & { pannable?: boolean } {
|
||||
return {
|
||||
group: 'nodes',
|
||||
classes: 'parentNode',
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
import type { ProjectGraphDependency } from '@nrwl/devkit';
|
||||
import * as cy from 'cytoscape';
|
||||
|
||||
export interface EdgeDataDefinition extends cy.NodeDataDefinition {
|
||||
export interface ProjectEdgeDataDefinition extends cy.NodeDataDefinition {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
@ -14,7 +14,7 @@ export class ProjectEdge {
|
||||
|
||||
constructor(private dep: ProjectGraphDependency) {}
|
||||
|
||||
getCytosacpeNodeDef(): cy.NodeDefinition {
|
||||
getCytoscapeNodeDef(): cy.EdgeDefinition {
|
||||
let edge: cy.EdgeDefinition;
|
||||
edge = {
|
||||
group: 'edges',
|
||||
|
||||
@ -7,6 +7,8 @@ export interface ProjectNodeDataDefinition extends cy.NodeDataDefinition {
|
||||
id: string;
|
||||
type: 'app' | 'lib' | 'e2e';
|
||||
tags: string[];
|
||||
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface Ancestor {
|
||||
@ -22,9 +24,13 @@ export class ProjectNode {
|
||||
constructor(
|
||||
private project: ProjectGraphProjectNode,
|
||||
private workspaceRoot: string
|
||||
) {}
|
||||
) {
|
||||
console.log(this.project);
|
||||
}
|
||||
|
||||
getCytoscapeNodeDef(groupByFolder: boolean): cy.NodeDefinition {
|
||||
getCytoscapeNodeDef(
|
||||
groupByFolder: boolean
|
||||
): cy.NodeDefinition & { pannable: boolean } {
|
||||
return {
|
||||
group: 'nodes',
|
||||
data: this.getData(groupByFolder),
|
||||
@ -46,6 +52,7 @@ export class ProjectNode {
|
||||
: null,
|
||||
files: this.project.data.files,
|
||||
root: this.project.data.root,
|
||||
description: this.project.data.description,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -317,12 +317,13 @@ export class ProjectTraversalGraph {
|
||||
}
|
||||
});
|
||||
|
||||
const projectElements = projectNodes.map((projectNode) =>
|
||||
projectNode.getCytoscapeNodeDef(groupByFolder)
|
||||
);
|
||||
const projectElements: (ElementDefinition & { pannable?: boolean })[] =
|
||||
projectNodes.map((projectNode) =>
|
||||
projectNode.getCytoscapeNodeDef(groupByFolder)
|
||||
);
|
||||
|
||||
const edgeElements = edgeNodes.map((edgeNode) =>
|
||||
edgeNode.getCytosacpeNodeDef()
|
||||
edgeNode.getCytoscapeNodeDef()
|
||||
);
|
||||
|
||||
elements = projectElements.concat(edgeElements);
|
||||
|
||||
@ -104,6 +104,7 @@ export class RenderGraph {
|
||||
this.listenForEdgeNodeClicks();
|
||||
this.listenForProjectNodeHovers();
|
||||
this.listenForTaskNodeClicks();
|
||||
this.listenForEmptyClicks();
|
||||
}
|
||||
|
||||
render(): { numEdges: number; numNodes: number } {
|
||||
@ -232,6 +233,7 @@ export class RenderGraph {
|
||||
id: node.id(),
|
||||
type: node.data('type'),
|
||||
tags: node.data('tags'),
|
||||
description: node.data('description'),
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -252,6 +254,7 @@ export class RenderGraph {
|
||||
id: node.id(),
|
||||
label: node.data('label'),
|
||||
executor: node.data('executor'),
|
||||
description: node.data('description'),
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -259,7 +262,8 @@ export class RenderGraph {
|
||||
|
||||
private listenForEdgeNodeClicks() {
|
||||
this.cy.$('edge.projectEdge').on('click', (event) => {
|
||||
const edge: EdgeSingular = event.target;
|
||||
const edge: EdgeSingular & { popperRef: () => VirtualElement } =
|
||||
event.target;
|
||||
let ref: VirtualElement = edge.popperRef(); // used only for positioning
|
||||
|
||||
this.broadcast({
|
||||
@ -268,24 +272,26 @@ export class RenderGraph {
|
||||
id: edge.id(),
|
||||
|
||||
data: {
|
||||
id: edge.id(),
|
||||
type: edge.data('type'),
|
||||
source: edge.source().id(),
|
||||
target: edge.target().id(),
|
||||
fileDependencies: edge
|
||||
.source()
|
||||
.data('files')
|
||||
.filter(
|
||||
(file) => file.deps && file.deps.includes(edge.target().id())
|
||||
)
|
||||
.map((file) => {
|
||||
return {
|
||||
fileName: file.file.replace(
|
||||
`${edge.source().data('root')}/`,
|
||||
''
|
||||
),
|
||||
target: edge.target().id(),
|
||||
};
|
||||
}),
|
||||
fileDependencies:
|
||||
edge
|
||||
.source()
|
||||
.data('files')
|
||||
?.filter(
|
||||
(file) => file.deps && file.deps.includes(edge.target().id())
|
||||
)
|
||||
.map((file) => {
|
||||
return {
|
||||
fileName: file.file.replace(
|
||||
`${edge.source().data('root')}/`,
|
||||
''
|
||||
),
|
||||
target: edge.target().id(),
|
||||
};
|
||||
}) || [],
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -321,6 +327,14 @@ export class RenderGraph {
|
||||
});
|
||||
}
|
||||
|
||||
private listenForEmptyClicks(): void {
|
||||
this.cy.on('click', (event) => {
|
||||
if (event.target === this.cy) {
|
||||
this.broadcast({ type: 'BackgroundClick' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getImage() {
|
||||
const bg = switchValueByDarkMode(this.cy, '#0F172A', '#FFFFFF');
|
||||
return this.cy.png({ bg, full: true });
|
||||
|
||||
@ -6,12 +6,15 @@ export interface TaskNodeDataDefinition extends cy.NodeDataDefinition {
|
||||
id: string;
|
||||
label: string;
|
||||
executor: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export class TaskNode {
|
||||
constructor(private task: Task, private project: ProjectGraphProjectNode) {}
|
||||
|
||||
getCytoscapeNodeDef(groupByProject: boolean): cy.NodeDefinition {
|
||||
getCytoscapeNodeDef(
|
||||
groupByProject: boolean
|
||||
): cy.NodeDefinition & { pannable: boolean } {
|
||||
return {
|
||||
group: 'nodes',
|
||||
classes: 'taskNode',
|
||||
@ -31,6 +34,7 @@ export class TaskNode {
|
||||
label,
|
||||
executor: this.project.data.targets[this.task.target.target].executor,
|
||||
parent: groupByProject ? this.task.target.project : null,
|
||||
description: this.project.data.description,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
40
graph/ui-graph/tailwind.config.js
Normal file
40
graph/ui-graph/tailwind.config.js
Normal file
@ -0,0 +1,40 @@
|
||||
const path = require('path');
|
||||
|
||||
// nx-ignore-next-line
|
||||
const { createGlobPatternsForDependencies } = require('@nrwl/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')],
|
||||
};
|
||||
12
graph/ui-tooltips/.babelrc
Normal file
12
graph/ui-tooltips/.babelrc
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nrwl/react/babel",
|
||||
{
|
||||
"runtime": "automatic",
|
||||
"useBuiltIns": "usage"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
||||
18
graph/ui-tooltips/.eslintrc.json
Normal file
18
graph/ui-tooltips/.eslintrc.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
22
graph/ui-tooltips/.storybook/main.js
Normal file
22
graph/ui-tooltips/.storybook/main.js
Normal file
@ -0,0 +1,22 @@
|
||||
const rootMain = require('../../.storybook/main');
|
||||
|
||||
module.exports = {
|
||||
...rootMain,
|
||||
core: { ...rootMain.core, builder: 'webpack5' },
|
||||
stories: [
|
||||
...rootMain.stories,
|
||||
'../src/lib/**/*.stories.mdx',
|
||||
'../src/lib/**/*.stories.@(js|jsx|ts|tsx)',
|
||||
],
|
||||
addons: [...rootMain.addons, '@nrwl/react/plugins/storybook'],
|
||||
webpackFinal: async (config, { configType }) => {
|
||||
// apply any global webpack configs that might have been specified in .storybook/main.js
|
||||
if (rootMain.webpackFinal) {
|
||||
config = await rootMain.webpackFinal(config, { configType });
|
||||
}
|
||||
|
||||
// add your own webpack tweaks if needed
|
||||
|
||||
return config;
|
||||
},
|
||||
};
|
||||
1
graph/ui-tooltips/.storybook/preview.js
Normal file
1
graph/ui-tooltips/.storybook/preview.js
Normal file
@ -0,0 +1 @@
|
||||
import './tailwind-imports.css';
|
||||
3
graph/ui-tooltips/.storybook/tailwind-imports.css
Normal file
3
graph/ui-tooltips/.storybook/tailwind-imports.css
Normal file
@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
26
graph/ui-tooltips/.storybook/tsconfig.json
Normal file
26
graph/ui-tooltips/.storybook/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"emitDecoratorMetadata": true,
|
||||
"outDir": ""
|
||||
},
|
||||
"files": [
|
||||
"../../../node_modules/@nrwl/react/typings/styled-jsx.d.ts",
|
||||
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||
"../../../node_modules/@nrwl/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"../**/*.spec.ts",
|
||||
"../**/*.spec.js",
|
||||
"../**/*.spec.tsx",
|
||||
"../**/*.spec.jsx"
|
||||
],
|
||||
"include": [
|
||||
"../src/**/*.stories.ts",
|
||||
"../src/**/*.stories.js",
|
||||
"../src/**/*.stories.jsx",
|
||||
"../src/**/*.stories.tsx",
|
||||
"../src/**/*.stories.mdx",
|
||||
"*.js"
|
||||
]
|
||||
}
|
||||
7
graph/ui-tooltips/README.md
Normal file
7
graph/ui-tooltips/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# graph-ui-tooltips
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test graph-ui-tooltips` to execute the unit tests via [Jest](https://jestjs.io).
|
||||
10
graph/ui-tooltips/jest.config.ts
Normal file
10
graph/ui-tooltips/jest.config.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'graph-ui-tooltips',
|
||||
preset: '../../jest.preset.js',
|
||||
transform: {
|
||||
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/graph/ui-graph',
|
||||
};
|
||||
10
graph/ui-tooltips/postcss.config.js
Normal file
10
graph/ui-tooltips/postcss.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
config: path.join(__dirname, 'tailwind.config.js'),
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
55
graph/ui-tooltips/project.json
Normal file
55
graph/ui-tooltips/project.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"name": "graph-ui-tooltips",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "graph/ui-tooltips/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["graph/ui-tooltips/**/*.{ts,tsx,js,jsx}"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "graph/ui-tooltips/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
},
|
||||
"storybook": {
|
||||
"executor": "@nrwl/storybook:storybook",
|
||||
"options": {
|
||||
"uiFramework": "@storybook/react",
|
||||
"port": 4400,
|
||||
"config": {
|
||||
"configFolder": "graph/ui-tooltips/.storybook"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"quiet": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"build-storybook": {
|
||||
"executor": "@nrwl/storybook:build",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"uiFramework": "@storybook/react",
|
||||
"outputPath": "dist/storybook/graph-ui-tooltips",
|
||||
"config": {
|
||||
"configFolder": "graph/ui-tooltips/.storybook"
|
||||
}
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"quiet": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
graph/ui-tooltips/src/index.ts
Normal file
5
graph/ui-tooltips/src/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './lib/tooltip';
|
||||
export * from './lib/project-edge-tooltip';
|
||||
export * from './lib/project-node-tooltip';
|
||||
export * from './lib/task-node-tooltip';
|
||||
export * from './lib/tooltip-button';
|
||||
@ -1,10 +1,11 @@
|
||||
import Tag from '../ui-components/tag';
|
||||
import { Tag } from '@nrwl/graph/ui-components';
|
||||
|
||||
export interface ProjectEdgeNodeTooltipProps {
|
||||
type: string;
|
||||
source: string;
|
||||
target: string;
|
||||
fileDependencies: Array<{ fileName: string }>;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export function ProjectEdgeNodeTooltip({
|
||||
@ -12,6 +13,7 @@ export function ProjectEdgeNodeTooltip({
|
||||
source,
|
||||
target,
|
||||
fileDependencies,
|
||||
description,
|
||||
}: ProjectEdgeNodeTooltipProps) {
|
||||
return (
|
||||
<div className="text-sm text-slate-700 dark:text-slate-400">
|
||||
@ -21,7 +23,8 @@ export function ProjectEdgeNodeTooltip({
|
||||
{source} → {target}
|
||||
</span>
|
||||
</h4>
|
||||
{type !== 'implicit' ? (
|
||||
{description ? <p>{description}</p> : null}
|
||||
{type !== 'implicit' && fileDependencies?.length > 0 ? (
|
||||
<div className="overflow-hidden rounded-md border border-slate-200 dark:border-slate-800">
|
||||
<div className="bg-slate-50 px-4 py-2 text-xs font-medium uppercase text-slate-500 dark:bg-slate-800 dark:text-slate-400">
|
||||
<span>Files</span>
|
||||
@ -43,5 +46,3 @@ export function ProjectEdgeNodeTooltip({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProjectEdgeNodeTooltip;
|
||||
37
graph/ui-tooltips/src/lib/project-node-tooltip.tsx
Normal file
37
graph/ui-tooltips/src/lib/project-node-tooltip.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import { Tag } from '@nrwl/graph/ui-components';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export interface ProjectNodeToolTipProps {
|
||||
type: 'app' | 'lib' | 'e2e';
|
||||
id: string;
|
||||
tags: string[];
|
||||
description?: string;
|
||||
|
||||
children?: ReactNode | ReactNode[];
|
||||
}
|
||||
|
||||
export function ProjectNodeToolTip({
|
||||
type,
|
||||
id,
|
||||
tags,
|
||||
children,
|
||||
description,
|
||||
}: ProjectNodeToolTipProps) {
|
||||
return (
|
||||
<div className="text-sm text-slate-700 dark:text-slate-400">
|
||||
<h4>
|
||||
<Tag className="mr-3">{type}</Tag>
|
||||
<span className="font-mono">{id}</span>
|
||||
</h4>
|
||||
{tags.length > 0 ? (
|
||||
<p className="my-2">
|
||||
<strong>tags</strong>
|
||||
<br></br>
|
||||
{tags.join(', ')}
|
||||
</p>
|
||||
) : null}
|
||||
{description ? <p className="mt-4">{description}</p> : null}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
23
graph/ui-tooltips/src/lib/task-node-tooltip.tsx
Normal file
23
graph/ui-tooltips/src/lib/task-node-tooltip.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { Tag } from '@nrwl/graph/ui-components';
|
||||
|
||||
export interface TaskNodeTooltipProps {
|
||||
id: string;
|
||||
executor: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export function TaskNodeTooltip({
|
||||
id,
|
||||
executor,
|
||||
description,
|
||||
}: TaskNodeTooltipProps) {
|
||||
return (
|
||||
<div className="text-sm text-slate-700 dark:text-slate-400">
|
||||
<h4>
|
||||
<Tag className="mr-3">{executor}</Tag>
|
||||
<span className="font-mono">{id}</span>
|
||||
</h4>
|
||||
{description ? <p className="mt-4">{description}</p> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -55,12 +55,13 @@ export function Tooltip({
|
||||
],
|
||||
});
|
||||
|
||||
const staticSide = {
|
||||
top: 'bottom',
|
||||
right: 'left',
|
||||
bottom: 'top',
|
||||
left: 'right',
|
||||
}[finalPlacement.split('-')[0]];
|
||||
const staticSide: string =
|
||||
{
|
||||
top: 'bottom',
|
||||
right: 'left',
|
||||
bottom: 'top',
|
||||
left: 'right',
|
||||
}[finalPlacement.split('-')[0]] || 'bottom';
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!!externalReference) {
|
||||
40
graph/ui-tooltips/tailwind.config.js
Normal file
40
graph/ui-tooltips/tailwind.config.js
Normal file
@ -0,0 +1,40 @@
|
||||
const path = require('path');
|
||||
|
||||
// nx-ignore-next-line
|
||||
const { createGlobPatternsForDependencies } = require('@nrwl/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')],
|
||||
};
|
||||
23
graph/ui-tooltips/tsconfig.json
Normal file
23
graph/ui-tooltips/tsconfig.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"path": "./.storybook/tsconfig.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../tsconfig.base.json"
|
||||
}
|
||||
27
graph/ui-tooltips/tsconfig.lib.json
Normal file
27
graph/ui-tooltips/tsconfig.lib.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["node"]
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nrwl/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"]
|
||||
}
|
||||
20
graph/ui-tooltips/tsconfig.spec.json
Normal file
20
graph/ui-tooltips/tsconfig.spec.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user