feat(graph): enable composite graph functionality (#27789)
This PR enables composite graph functionality:
- Experimental feature to enable Composite Graph
- In Composite Graph mode:
- Nodes are shown by default.
- Show/Hide All Projects function similarly to regular mode
- Focus a Composite Node renders the inner nodes with up-to 3 additional
containers: Green area contains external nodes that depend on the inner
nodes; Orange area contains external nodes that the inner nodes depend
depend on; Purple area contains external nodes with circular
dependencies with the inner nodes.
- Focused node can be unfocus/reset.
- Only one node can be focused at one given time. - Show All projects
while having a focused node will unfocus the node.
- Expand a Composite Node renders the inner nodes of the composite node
in-place (i.e: still keep the context of the current graph). Expanded
node can be collapsed to go back.
This commit is contained in:
parent
7e1cf531ca
commit
3c95965e7c
@ -25,15 +25,65 @@ export const compositeGraphStateConfig: ProjectGraphStateNodeConfig = {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
exit: [
|
exit: [
|
||||||
send(() => ({ type: 'notifyGraphDisableCompositeGraph' }), {
|
|
||||||
to: (ctx) => ctx.graphActor,
|
|
||||||
}),
|
|
||||||
assign((ctx) => {
|
assign((ctx) => {
|
||||||
ctx.compositeGraph.enabled = false;
|
ctx.compositeGraph.enabled = false;
|
||||||
ctx.compositeGraph.context = undefined;
|
ctx.compositeGraph.context = undefined;
|
||||||
}),
|
}),
|
||||||
|
send(
|
||||||
|
(ctx) => ({
|
||||||
|
type: 'notifyGraphUpdateGraph',
|
||||||
|
projects: ctx.projects,
|
||||||
|
dependencies: ctx.dependencies,
|
||||||
|
fileMap: ctx.fileMap,
|
||||||
|
affectedProjects: ctx.affectedProjects,
|
||||||
|
workspaceLayout: ctx.workspaceLayout,
|
||||||
|
groupByFolder: ctx.groupByFolder,
|
||||||
|
selectedProjects: ctx.selectedProjects,
|
||||||
|
composite: ctx.compositeGraph,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
to: (ctx) => ctx.graphActor,
|
||||||
|
}
|
||||||
|
),
|
||||||
],
|
],
|
||||||
on: {
|
on: {
|
||||||
|
selectAll: {
|
||||||
|
actions: [
|
||||||
|
assign((ctx, event) => {
|
||||||
|
if (event.type !== 'selectAll') return;
|
||||||
|
ctx.compositeGraph.enabled = true;
|
||||||
|
ctx.compositeGraph.context = null;
|
||||||
|
}),
|
||||||
|
send((ctx) => ({
|
||||||
|
type: 'enableCompositeGraph',
|
||||||
|
context: ctx.compositeGraph.context,
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
deselectAll: {
|
||||||
|
actions: [
|
||||||
|
assign((ctx, event) => {
|
||||||
|
if (event.type !== 'deselectAll') return;
|
||||||
|
ctx.compositeGraph.enabled = true;
|
||||||
|
}),
|
||||||
|
send(
|
||||||
|
() => ({
|
||||||
|
type: 'notifyGraphHideAllProjects',
|
||||||
|
}),
|
||||||
|
{ to: (context) => context.graphActor }
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
selectAffected: {
|
||||||
|
actions: [
|
||||||
|
send(
|
||||||
|
() => ({
|
||||||
|
type: 'notifyGraphShowAffectedProjects',
|
||||||
|
}),
|
||||||
|
{ to: (context) => context.graphActor }
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
focusProject: {
|
focusProject: {
|
||||||
actions: [
|
actions: [
|
||||||
assign((ctx, event) => {
|
assign((ctx, event) => {
|
||||||
@ -112,6 +162,7 @@ export const compositeGraphStateConfig: ProjectGraphStateNodeConfig = {
|
|||||||
if (event.type !== 'enableCompositeGraph') return;
|
if (event.type !== 'enableCompositeGraph') return;
|
||||||
ctx.compositeGraph.enabled = true;
|
ctx.compositeGraph.enabled = true;
|
||||||
ctx.compositeGraph.context = event.context || undefined;
|
ctx.compositeGraph.context = event.context || undefined;
|
||||||
|
ctx.focusedProject = null;
|
||||||
}),
|
}),
|
||||||
send(
|
send(
|
||||||
(ctx, event) => ({
|
(ctx, event) => ({
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export interface CompositeGraphPanelProps {
|
|||||||
export const CompositeGraphPanel = memo(
|
export const CompositeGraphPanel = memo(
|
||||||
({ compositeEnabled, compositeEnabledChanged }: CompositeGraphPanelProps) => {
|
({ compositeEnabled, compositeEnabledChanged }: CompositeGraphPanelProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="px-4">
|
<div className="mt-4 px-4">
|
||||||
<div className="flex items-start">
|
<div className="flex items-start">
|
||||||
<div className="flex h-5 items-center">
|
<div className="flex h-5 items-center">
|
||||||
<input
|
<input
|
||||||
@ -16,7 +16,7 @@ export const CompositeGraphPanel = memo(
|
|||||||
name="composite"
|
name="composite"
|
||||||
value="composite"
|
value="composite"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="h-4 w-4 accent-purple-500"
|
className="h-4 w-4 accent-blue-500 dark:accent-sky-500"
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
compositeEnabledChanged(event.target.checked)
|
compositeEnabledChanged(event.target.checked)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,15 @@ import { CheckboxPanel } from '../../ui-components/checkbox-panel';
|
|||||||
export interface DisplayOptionsPanelProps {
|
export interface DisplayOptionsPanelProps {
|
||||||
groupByFolder: boolean;
|
groupByFolder: boolean;
|
||||||
groupByFolderChanged: (checked: boolean) => void;
|
groupByFolderChanged: (checked: boolean) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
disabledDescription?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GroupByFolderPanel = ({
|
export const GroupByFolderPanel = ({
|
||||||
groupByFolder,
|
groupByFolder,
|
||||||
groupByFolderChanged,
|
groupByFolderChanged,
|
||||||
|
disabled,
|
||||||
|
disabledDescription,
|
||||||
}: DisplayOptionsPanelProps) => {
|
}: DisplayOptionsPanelProps) => {
|
||||||
return (
|
return (
|
||||||
<CheckboxPanel
|
<CheckboxPanel
|
||||||
@ -16,6 +20,8 @@ export const GroupByFolderPanel = ({
|
|||||||
name={'groupByFolder'}
|
name={'groupByFolder'}
|
||||||
label={'Group by folder'}
|
label={'Group by folder'}
|
||||||
description={'Visually arrange libraries by folders.'}
|
description={'Visually arrange libraries by folders.'}
|
||||||
|
disabled={disabled}
|
||||||
|
disabledDescription={disabledDescription}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import { getProjectGraphService } from '../machines/get-services';
|
|||||||
import { Link, useNavigate, useNavigation } from 'react-router-dom';
|
import { Link, useNavigate, useNavigation } from 'react-router-dom';
|
||||||
import { useRouteConstructor } from '@nx/graph/shared';
|
import { useRouteConstructor } from '@nx/graph/shared';
|
||||||
import { CompositeNode } from '../interfaces';
|
import { CompositeNode } from '../interfaces';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
interface SidebarProject {
|
interface SidebarProject {
|
||||||
projectGraphNode: ProjectGraphProjectNode;
|
projectGraphNode: ProjectGraphProjectNode;
|
||||||
@ -249,6 +250,10 @@ function CompositeNodeListItem({
|
|||||||
const routeConstructor = useRouteConstructor();
|
const routeConstructor = useRouteConstructor();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const label = compositeNode.parent
|
||||||
|
? `${compositeNode.parent}/${compositeNode.label}`
|
||||||
|
: compositeNode.label;
|
||||||
|
|
||||||
function toggleProject() {
|
function toggleProject() {
|
||||||
if (compositeNode.state !== 'hidden') {
|
if (compositeNode.state !== 'hidden') {
|
||||||
projectGraphService.send({
|
projectGraphService.send({
|
||||||
@ -283,13 +288,8 @@ function CompositeNodeListItem({
|
|||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Link
|
<Link
|
||||||
to={routeConstructor(
|
to={routeConstructor(
|
||||||
{
|
{ pathname: `/projects`, search: `?composite=${compositeNode.id}` },
|
||||||
pathname: `/projects`,
|
true
|
||||||
search: `?composite=true&compositeContext=${encodeURIComponent(
|
|
||||||
compositeNode.id
|
|
||||||
)}`,
|
|
||||||
},
|
|
||||||
false
|
|
||||||
)}
|
)}
|
||||||
className="mr-1 flex items-center rounded-md border-slate-300 bg-white p-1 font-medium text-slate-500 shadow-sm ring-1 ring-slate-200 transition hover:bg-slate-50 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-400 dark:ring-slate-600 hover:dark:bg-slate-700"
|
className="mr-1 flex items-center rounded-md border-slate-300 bg-white p-1 font-medium text-slate-500 shadow-sm ring-1 ring-slate-200 transition hover:bg-slate-50 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-400 dark:ring-slate-600 hover:dark:bg-slate-700"
|
||||||
title="Focus on this node"
|
title="Focus on this node"
|
||||||
@ -313,11 +313,11 @@ function CompositeNodeListItem({
|
|||||||
<label
|
<label
|
||||||
className="ml-2 block w-full cursor-pointer truncate rounded-md p-2 font-mono font-normal transition hover:bg-slate-50 hover:dark:bg-slate-700"
|
className="ml-2 block w-full cursor-pointer truncate rounded-md p-2 font-mono font-normal transition hover:bg-slate-50 hover:dark:bg-slate-700"
|
||||||
data-project={compositeNode.id}
|
data-project={compositeNode.id}
|
||||||
title={compositeNode.label}
|
title={label}
|
||||||
data-active={compositeNode.state !== 'hidden'}
|
data-active={compositeNode.state !== 'hidden'}
|
||||||
onClick={toggleProject}
|
onClick={toggleProject}
|
||||||
>
|
>
|
||||||
{compositeNode.label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -339,8 +339,6 @@ function CompositeNodeList({
|
|||||||
}: {
|
}: {
|
||||||
compositeNodes: CompositeNode[];
|
compositeNodes: CompositeNode[];
|
||||||
}) {
|
}) {
|
||||||
const projectGraphService = getProjectGraphService();
|
|
||||||
|
|
||||||
if (compositeNodes.length === 0) {
|
if (compositeNodes.length === 0) {
|
||||||
return <p>No composite nodes</p>;
|
return <p>No composite nodes</p>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import { useProjectGraphSelector } from './hooks/use-project-graph-selector';
|
|||||||
import { TracingAlgorithmType } from './machines/interfaces';
|
import { TracingAlgorithmType } from './machines/interfaces';
|
||||||
import {
|
import {
|
||||||
collapseEdgesSelector,
|
collapseEdgesSelector,
|
||||||
|
compositeContextSelector,
|
||||||
|
compositeGraphEnabledSelector,
|
||||||
focusedProjectNameSelector,
|
focusedProjectNameSelector,
|
||||||
getTracingInfo,
|
getTracingInfo,
|
||||||
groupByFolderSelector,
|
groupByFolderSelector,
|
||||||
@ -40,9 +42,13 @@ import {
|
|||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import { useCurrentPath } from '../hooks/use-current-path';
|
import { useCurrentPath } from '../hooks/use-current-path';
|
||||||
import { ProjectDetailsModal } from '../ui-components/project-details-modal';
|
import { ProjectDetailsModal } from '../ui-components/project-details-modal';
|
||||||
|
import { CompositeGraphPanel } from './panels/composite-graph-panel';
|
||||||
|
import { CompositeContextPanel } from '../ui-components/composite-context-panel';
|
||||||
|
import { getGraphService } from '../machines/graph.service';
|
||||||
|
|
||||||
export function ProjectsSidebar(): JSX.Element {
|
export function ProjectsSidebar(): JSX.Element {
|
||||||
const environmentConfig = useEnvironmentConfig();
|
const environmentConfig = useEnvironmentConfig();
|
||||||
|
const graphService = getGraphService();
|
||||||
const projectGraphService = getProjectGraphService();
|
const projectGraphService = getProjectGraphService();
|
||||||
const focusedProject = useProjectGraphSelector(focusedProjectNameSelector);
|
const focusedProject = useProjectGraphSelector(focusedProjectNameSelector);
|
||||||
const searchDepthInfo = useProjectGraphSelector(searchDepthSelector);
|
const searchDepthInfo = useProjectGraphSelector(searchDepthSelector);
|
||||||
@ -53,6 +59,11 @@ export function ProjectsSidebar(): JSX.Element {
|
|||||||
);
|
);
|
||||||
const groupByFolder = useProjectGraphSelector(groupByFolderSelector);
|
const groupByFolder = useProjectGraphSelector(groupByFolderSelector);
|
||||||
const collapseEdges = useProjectGraphSelector(collapseEdgesSelector);
|
const collapseEdges = useProjectGraphSelector(collapseEdgesSelector);
|
||||||
|
const compositeEnabled = useProjectGraphSelector(
|
||||||
|
compositeGraphEnabledSelector
|
||||||
|
);
|
||||||
|
|
||||||
|
const compositeContext = useProjectGraphSelector(compositeContextSelector);
|
||||||
|
|
||||||
const isTracing = projectGraphService.getSnapshot().matches('tracing');
|
const isTracing = projectGraphService.getSnapshot().matches('tracing');
|
||||||
const tracingInfo = useProjectGraphSelector(getTracingInfo);
|
const tracingInfo = useProjectGraphSelector(getTracingInfo);
|
||||||
@ -75,17 +86,48 @@ export function ProjectsSidebar(): JSX.Element {
|
|||||||
navigate(routeConstructor('/projects', true));
|
navigate(routeConstructor('/projects', true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetCompositeContext() {
|
||||||
|
projectGraphService.send({ type: 'enableCompositeGraph', context: null });
|
||||||
|
navigate(
|
||||||
|
routeConstructor(
|
||||||
|
{ pathname: '/projects', search: '?composite=true' },
|
||||||
|
true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function showAllProjects() {
|
function showAllProjects() {
|
||||||
navigate(routeConstructor('/projects/all', true));
|
navigate(
|
||||||
|
routeConstructor('/projects/all', (searchParams) => {
|
||||||
|
if (searchParams.has('composite')) {
|
||||||
|
searchParams.set('composite', 'true');
|
||||||
|
}
|
||||||
|
return searchParams;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideAllProjects() {
|
function hideAllProjects() {
|
||||||
projectGraphService.send({ type: 'deselectAll' });
|
projectGraphService.send({ type: 'deselectAll' });
|
||||||
navigate(routeConstructor('/projects', true));
|
navigate(
|
||||||
|
routeConstructor('/projects', (searchParams) => {
|
||||||
|
if (searchParams.has('composite')) {
|
||||||
|
searchParams.set('composite', 'true');
|
||||||
|
}
|
||||||
|
return searchParams;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showAffectedProjects() {
|
function showAffectedProjects() {
|
||||||
navigate(routeConstructor('/projects/affected', true));
|
navigate(
|
||||||
|
routeConstructor('/projects/affected', (searchParams) => {
|
||||||
|
if (searchParams.has('composite')) {
|
||||||
|
searchParams.set('composite', 'true');
|
||||||
|
}
|
||||||
|
return searchParams;
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchDepthFilterEnabledChange(checked: boolean) {
|
function searchDepthFilterEnabledChange(checked: boolean) {
|
||||||
@ -126,6 +168,19 @@ export function ProjectsSidebar(): JSX.Element {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compositeEnabledChanged(checked: boolean) {
|
||||||
|
navigate(
|
||||||
|
routeConstructor('/projects', (searchParams) => {
|
||||||
|
if (checked) {
|
||||||
|
searchParams.set('composite', 'true');
|
||||||
|
} else {
|
||||||
|
searchParams.delete('composite');
|
||||||
|
}
|
||||||
|
return searchParams;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function incrementDepthFilter() {
|
function incrementDepthFilter() {
|
||||||
const newSearchDepth = searchDepthInfo.searchDepth + 1;
|
const newSearchDepth = searchDepthInfo.searchDepth + 1;
|
||||||
setSearchParams((currentSearchParams) => {
|
setSearchParams((currentSearchParams) => {
|
||||||
@ -182,6 +237,19 @@ export function ProjectsSidebar(): JSX.Element {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return graphService.listen((event) => {
|
||||||
|
if (event.type === 'CompositeNodeDblClick') {
|
||||||
|
projectGraphService.send({
|
||||||
|
type: event.data.expanded
|
||||||
|
? 'collapseCompositeNode'
|
||||||
|
: 'expandCompositeNode',
|
||||||
|
id: event.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
projectGraphService.send({
|
projectGraphService.send({
|
||||||
type: 'setProjects',
|
type: 'setProjects',
|
||||||
@ -224,7 +292,7 @@ export function ProjectsSidebar(): JSX.Element {
|
|||||||
projectName: routeParams.endTrace,
|
projectName: routeParams.endTrace,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [routeParams]);
|
}, [routeParams, compositeEnabled]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchParams.has('groupByFolder') && groupByFolder === false) {
|
if (searchParams.has('groupByFolder') && groupByFolder === false) {
|
||||||
@ -251,6 +319,17 @@ export function ProjectsSidebar(): JSX.Element {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (searchParams.has('composite')) {
|
||||||
|
const compositeParam = searchParams.get('composite');
|
||||||
|
projectGraphService.send({
|
||||||
|
type: 'enableCompositeGraph',
|
||||||
|
context: compositeParam === 'true' ? null : compositeParam,
|
||||||
|
});
|
||||||
|
} else if (!searchParams.has('composite')) {
|
||||||
|
projectGraphService.send({ type: 'disableCompositeGraph' });
|
||||||
|
navigate(routeConstructor('/projects', true));
|
||||||
|
}
|
||||||
|
|
||||||
if (searchParams.has('searchDepth')) {
|
if (searchParams.has('searchDepth')) {
|
||||||
const parsedValue = parseInt(searchParams.get('searchDepth'), 10);
|
const parsedValue = parseInt(searchParams.get('searchDepth'), 10);
|
||||||
|
|
||||||
@ -329,6 +408,13 @@ export function ProjectsSidebar(): JSX.Element {
|
|||||||
<>
|
<>
|
||||||
<ProjectDetailsModal />
|
<ProjectDetailsModal />
|
||||||
|
|
||||||
|
{compositeEnabled && compositeContext ? (
|
||||||
|
<CompositeContextPanel
|
||||||
|
compositeContext={compositeContext}
|
||||||
|
reset={resetCompositeContext}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{focusedProject ? (
|
{focusedProject ? (
|
||||||
<FocusedPanel
|
<FocusedPanel
|
||||||
focusedLabel={focusedProject}
|
focusedLabel={focusedProject}
|
||||||
@ -367,6 +453,8 @@ export function ProjectsSidebar(): JSX.Element {
|
|||||||
<GroupByFolderPanel
|
<GroupByFolderPanel
|
||||||
groupByFolder={groupByFolder}
|
groupByFolder={groupByFolder}
|
||||||
groupByFolderChanged={groupByFolderChanged}
|
groupByFolderChanged={groupByFolderChanged}
|
||||||
|
disabled={compositeEnabled}
|
||||||
|
disabledDescription="Group by folder is not available when composite graph is enabled"
|
||||||
></GroupByFolderPanel>
|
></GroupByFolderPanel>
|
||||||
|
|
||||||
<SearchDepth
|
<SearchDepth
|
||||||
@ -377,8 +465,13 @@ export function ProjectsSidebar(): JSX.Element {
|
|||||||
decrementDepthFilter={decrementDepthFilter}
|
decrementDepthFilter={decrementDepthFilter}
|
||||||
></SearchDepth>
|
></SearchDepth>
|
||||||
|
|
||||||
|
<CompositeGraphPanel
|
||||||
|
compositeEnabled={compositeEnabled}
|
||||||
|
compositeEnabledChanged={compositeEnabledChanged}
|
||||||
|
></CompositeGraphPanel>
|
||||||
|
|
||||||
<ExperimentalFeature>
|
<ExperimentalFeature>
|
||||||
<div className="mx-4 mt-8 rounded-lg border-2 border-dashed border-purple-500 p-4 shadow-lg dark:border-purple-600 dark:bg-[#0B1221]">
|
<div className="mx-4 mt-8 flex flex-col gap-4 rounded-lg border-2 border-dashed border-purple-500 p-4 shadow-lg dark:border-purple-600 dark:bg-[#0B1221]">
|
||||||
<h3 className="cursor-text px-4 py-2 text-sm font-semibold uppercase tracking-wide text-slate-800 lg:text-xs dark:text-slate-200">
|
<h3 className="cursor-text px-4 py-2 text-sm font-semibold uppercase tracking-wide text-slate-800 lg:text-xs dark:text-slate-200">
|
||||||
Experimental Features
|
Experimental Features
|
||||||
</h3>
|
</h3>
|
||||||
|
|||||||
@ -36,4 +36,5 @@ export interface CompositeNode {
|
|||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
state: 'expanded' | 'collapsed' | 'hidden';
|
state: 'expanded' | 'collapsed' | 'hidden';
|
||||||
|
parent?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import { Dropdown, Spinner } from '@nx/graph/ui-components';
|
|||||||
import { getSystemTheme, Theme, ThemePanel } from '@nx/graph-internal/ui-theme';
|
import { getSystemTheme, Theme, ThemePanel } from '@nx/graph-internal/ui-theme';
|
||||||
import { Tooltip } from '@nx/graph/ui-tooltips';
|
import { Tooltip } from '@nx/graph/ui-tooltips';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useLayoutEffect, useState } from 'react';
|
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Outlet,
|
Outlet,
|
||||||
useNavigate,
|
useNavigate,
|
||||||
@ -43,14 +43,18 @@ import { TooltipDisplay } from './ui-tooltips/graph-tooltip-display';
|
|||||||
export function Shell(): JSX.Element {
|
export function Shell(): JSX.Element {
|
||||||
const projectGraphService = getProjectGraphService();
|
const projectGraphService = getProjectGraphService();
|
||||||
const projectGraphDataService = getProjectGraphDataService();
|
const projectGraphDataService = getProjectGraphDataService();
|
||||||
|
|
||||||
const graphService = getGraphService();
|
const graphService = getGraphService();
|
||||||
|
|
||||||
const lastPerfReport = useSyncExternalStore(
|
const [lastPerfReport, setLastPerfReport] = useState(
|
||||||
(callback) => graphService.listen(callback),
|
graphService.lastPerformanceReport
|
||||||
() => graphService.lastPerformanceReport
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
graphService.listen(() => {
|
||||||
|
setLastPerfReport(graphService.lastPerformanceReport);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const nodesVisible = lastPerfReport.numNodes !== 0;
|
const nodesVisible = lastPerfReport.numNodes !== 0;
|
||||||
|
|
||||||
const environment = useEnvironmentConfig();
|
const environment = useEnvironmentConfig();
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export interface CheckboxPanelProps {
|
export interface CheckboxPanelProps {
|
||||||
checked: boolean;
|
checked: boolean;
|
||||||
@ -6,12 +7,28 @@ export interface CheckboxPanelProps {
|
|||||||
name: string;
|
name: string;
|
||||||
label: string;
|
label: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
disabledDescription?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CheckboxPanel = memo(
|
export const CheckboxPanel = memo(
|
||||||
({ checked, checkChanged, label, description, name }: CheckboxPanelProps) => {
|
({
|
||||||
|
checked,
|
||||||
|
checkChanged,
|
||||||
|
label,
|
||||||
|
description,
|
||||||
|
name,
|
||||||
|
disabled,
|
||||||
|
disabledDescription,
|
||||||
|
}: CheckboxPanelProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="mt-8 px-4">
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'mt-8 px-4',
|
||||||
|
disabled ? 'cursor-not-allowed opacity-50' : ''
|
||||||
|
)}
|
||||||
|
title={disabled ? disabledDescription : description}
|
||||||
|
>
|
||||||
<div className="flex items-start">
|
<div className="flex items-start">
|
||||||
<div className="flex h-5 items-center">
|
<div className="flex h-5 items-center">
|
||||||
<input
|
<input
|
||||||
@ -22,12 +39,16 @@ export const CheckboxPanel = memo(
|
|||||||
className="h-4 w-4 accent-blue-500 dark:accent-sky-500"
|
className="h-4 w-4 accent-blue-500 dark:accent-sky-500"
|
||||||
onChange={(event) => checkChanged(event.target.checked)}
|
onChange={(event) => checkChanged(event.target.checked)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-3 text-sm">
|
<div className="ml-3 text-sm">
|
||||||
<label
|
<label
|
||||||
htmlFor={name}
|
htmlFor={name}
|
||||||
className="cursor-pointer font-medium text-slate-600 dark:text-slate-400"
|
className={classNames(
|
||||||
|
' font-medium text-slate-600 dark:text-slate-400',
|
||||||
|
disabled ? 'cursor-not-allowed' : 'cursor-pointer'
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@ -41,17 +41,16 @@ export function TooltipDisplay() {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'focus-node': {
|
case 'focus-node': {
|
||||||
const to =
|
if (action.tooltipNodeType === 'compositeNode') {
|
||||||
action.tooltipNodeType === 'compositeNode'
|
navigate(
|
||||||
? routeConstructor(
|
routeConstructor(
|
||||||
{
|
{ pathname: `/projects`, search: `?composite=${action.id}` },
|
||||||
pathname: `/projects`,
|
true
|
||||||
search: `?composite=true&compositeContext=${action.id}`,
|
|
||||||
},
|
|
||||||
false
|
|
||||||
)
|
)
|
||||||
: routeConstructor(`/projects/${action.id}`, true);
|
);
|
||||||
navigate(to);
|
} else {
|
||||||
|
navigate(routeConstructor(`/projects/${action.id}`, true));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'collapse-node':
|
case 'collapse-node':
|
||||||
@ -81,7 +80,12 @@ export function TooltipDisplay() {
|
|||||||
navigate(
|
navigate(
|
||||||
routeConstructor(
|
routeConstructor(
|
||||||
`/projects/trace/${encodeURIComponent(start)}/${action.id}`,
|
`/projects/trace/${encodeURIComponent(start)}/${action.id}`,
|
||||||
true
|
(searchParams) => {
|
||||||
|
if (searchParams.has('composite')) {
|
||||||
|
searchParams.delete('composite');
|
||||||
|
}
|
||||||
|
return searchParams;
|
||||||
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -327,7 +327,7 @@
|
|||||||
"@markdoc/markdoc": "0.2.2",
|
"@markdoc/markdoc": "0.2.2",
|
||||||
"@monaco-editor/react": "^4.4.6",
|
"@monaco-editor/react": "^4.4.6",
|
||||||
"@napi-rs/canvas": "^0.1.52",
|
"@napi-rs/canvas": "^0.1.52",
|
||||||
"@nx/graph": "0.0.1-alpha.16",
|
"@nx/graph": "0.0.1-alpha.23",
|
||||||
"@react-spring/three": "^9.7.3",
|
"@react-spring/three": "^9.7.3",
|
||||||
"@react-three/drei": "^9.108.3",
|
"@react-three/drei": "^9.108.3",
|
||||||
"@react-three/fiber": "^8.16.8",
|
"@react-three/fiber": "^8.16.8",
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@ -31,8 +31,8 @@ importers:
|
|||||||
specifier: ^0.1.52
|
specifier: ^0.1.52
|
||||||
version: 0.1.55
|
version: 0.1.55
|
||||||
'@nx/graph':
|
'@nx/graph':
|
||||||
specifier: 0.0.1-alpha.16
|
specifier: 0.0.1-alpha.23
|
||||||
version: 0.0.1-alpha.16(@nx/devkit@19.8.0-beta.2(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react-router-dom@6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
version: 0.0.1-alpha.23(@nx/devkit@19.8.0-beta.2(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react-router-dom@6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||||
'@react-spring/three':
|
'@react-spring/three':
|
||||||
specifier: ^9.7.3
|
specifier: ^9.7.3
|
||||||
version: 9.7.4(@react-three/fiber@8.17.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.166.1))(react@18.3.1)(three@0.166.1)
|
version: 9.7.4(@react-three/fiber@8.17.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.166.1))(react@18.3.1)(three@0.166.1)
|
||||||
@ -4575,8 +4575,8 @@ packages:
|
|||||||
'@zkochan/js-yaml':
|
'@zkochan/js-yaml':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@nx/graph@0.0.1-alpha.16':
|
'@nx/graph@0.0.1-alpha.23':
|
||||||
resolution: {integrity: sha512-pJVFuTunlRfa8BuO2Vy6wxRtfC2kDT3TKDR6y1KXmMl90xTVT30zU/K9ITXPZzLfo8nLpewIfbv41gGSCY9+Dg==}
|
resolution: {integrity: sha512-vgP9CxcCLa551AWZkwfN6qkwLGdZazU+GL7KZusIH8L3enRo2GHqyPQaF93fnTenokYM0SphR85/X4QibF3mlA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@nx/devkit': '>= 19 < 20'
|
'@nx/devkit': '>= 19 < 20'
|
||||||
nx: '>= 19 < 20'
|
nx: '>= 19 < 20'
|
||||||
@ -21629,7 +21629,7 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- verdaccio
|
- verdaccio
|
||||||
|
|
||||||
'@nx/graph@0.0.1-alpha.16(@nx/devkit@19.8.0-beta.2(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react-router-dom@6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
|
'@nx/graph@0.0.1-alpha.23(@nx/devkit@19.8.0-beta.2(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11))))(nx@19.8.0-beta.2(@swc-node/register@1.9.1(@swc/core@1.5.7(@swc/helpers@0.5.11))(@swc/types@0.1.12)(typescript@5.5.4))(@swc/core@1.5.7(@swc/helpers@0.5.11)))(react-dom@18.3.1(react@18.3.1))(react-router-dom@6.26.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@floating-ui/react': 0.26.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@floating-ui/react': 0.26.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user