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:
Chau Tran 2024-09-25 12:20:48 -05:00 committed by GitHub
parent 7e1cf531ca
commit 3c95965e7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 225 additions and 47 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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