feat(graph): add tooltips to docs graph (#13832)

This commit is contained in:
Philip Fulcher 2022-12-15 18:54:34 -07:00 committed by GitHub
parent f4802ae579
commit 578ecb6785
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
105 changed files with 1093 additions and 335 deletions

View File

@ -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
View 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;
// },
};

View File

View File

@ -1,5 +1,5 @@
{
"extends": "../tsconfig.base.json",
"extends": "../../tsconfig.base.json",
"exclude": [
"../**/*.spec.js",
"../**/*.test.js",

View File

@ -1,4 +1,4 @@
const rootMain = require('../../../.storybook/main');
const rootMain = require('../../.storybook/main');
module.exports = {
...rootMain,

View File

@ -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) => {

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -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;

View File

@ -37,5 +37,3 @@ export const CollapseEdgesPanel = memo(
);
}
);
export default CollapseEdgesPanel;

View File

@ -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;

View File

@ -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'
);

View File

@ -76,5 +76,3 @@ export const SearchDepth = memo(
);
}
);
export default SearchDepth;

View File

@ -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;

View File

@ -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'
);

View File

@ -111,5 +111,3 @@ export const TracingPanel = memo(
);
}
);
export default TracingPanel;

View File

@ -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;

View File

@ -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;

View File

@ -187,5 +187,3 @@ export function TaskList({
</div>
);
}
export default TaskList;

View File

@ -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;

View File

@ -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;
}

View File

@ -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';

View File

@ -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();

View File

@ -1,5 +1,5 @@
import { ComponentMeta, ComponentStory } from '@storybook/react';
import CheckboxPanel from './checkbox-panel';
import { CheckboxPanel } from './checkbox-panel';
export default {
component: CheckboxPanel,

View File

@ -38,5 +38,3 @@ export const CheckboxPanel = memo(
);
}
);
export default CheckboxPanel;

View File

@ -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;

View File

@ -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;

View File

@ -30,5 +30,3 @@ export const FocusedPanel = memo(
);
}
);
export default FocusedPanel;

View File

@ -54,5 +54,3 @@ export const ShowHideAll = memo(
);
}
);
export default ShowHideAll;

View File

@ -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;

View 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>
);
}

View File

@ -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;

View File

@ -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;

View File

@ -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>,

View File

@ -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: {

View File

@ -0,0 +1,12 @@
{
"presets": [
[
"@nrwl/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}

View 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": {}
}
]
}

View 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;
},
};

View File

@ -0,0 +1,8 @@
import React from 'react';
import '../../client/.storybook/tailwind-imports.css';
import { rootParameters } from '../../.storybook/preview';
export const parameters = {
...rootParameters,
};

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View 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"
]
}

View 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).

View 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',
};

View File

@ -0,0 +1,8 @@
module.exports = {
plugins: {
tailwindcss: {
config: './graph/ui-components/tailwind.config.js',
},
autoprefixer: {},
},
};

View 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
}
}
}
}
}

View File

@ -0,0 +1,3 @@
export * from './lib/debounced-text-input';
export * from './lib/tag';
export * from './lib/dropdown';

View File

@ -21,6 +21,6 @@ const Template: ComponentStory<typeof DebouncedTextInput> = (args) => (
export const Primary = Template.bind({});
Primary.args = {
currentText: '',
initialText: '',
placeholderText: '',
};

View File

@ -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;

View File

@ -16,5 +16,3 @@ export function Dropdown(props: DropdownProps) {
</select>
);
}
export default Dropdown;

View File

@ -17,5 +17,3 @@ export function Tag({ className, children, ...rest }: TagProps) {
</span>
);
}
export default Tag;

View 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')],
};

View 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"
}

View 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"]
}

View 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"
]
}

View File

@ -1,4 +1,4 @@
const rootMain = require('../../../.storybook/main');
const rootMain = require('../../.storybook/main');
module.exports = {
...rootMain,

View 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>

View File

@ -0,0 +1,5 @@
import './tailwind-imports.css';
import { rootParameters } from '../../.storybook/preview';
export const parameters = { ...rootParameters };

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,10 @@
const path = require('path');
module.exports = {
plugins: {
tailwindcss: {
config: path.join(__dirname, 'tailwind.config.js'),
},
autoprefixer: {},
},
};

View File

@ -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';

View File

@ -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;

View File

@ -62,6 +62,7 @@ export class GraphService {
}
broadcast(event: GraphInteractionEvents) {
console.log(event);
this.listeners.forEach((callback) => callback(event));
}

View File

@ -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;
};

View File

@ -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,
};

View File

@ -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;

View File

@ -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,
};

View File

@ -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;

View File

@ -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;
}

View File

@ -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',

View File

@ -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',

View File

@ -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,
};
}

View File

@ -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);

View File

@ -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 });

View File

@ -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,
};
}
}

View 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')],
};

View File

@ -0,0 +1,12 @@
{
"presets": [
[
"@nrwl/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}

View 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": {}
}
]
}

View 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;
},
};

View File

@ -0,0 +1 @@
import './tailwind-imports.css';

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View 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"
]
}

View 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).

View 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',
};

View File

@ -0,0 +1,10 @@
const path = require('path');
module.exports = {
plugins: {
tailwindcss: {
config: path.join(__dirname, 'tailwind.config.js'),
},
autoprefixer: {},
},
};

View 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
}
}
}
}
}

View 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';

View File

@ -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} &rarr; {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;

View 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>
);
}

View 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>
);
}

View File

@ -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) {

View 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')],
};

View 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"
}

View 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"]
}

View 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