Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/graph-editor/src/components/panels/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export * from './nodeSettings/index.js';
export * from './output/index.js';
export * from './play/index.js';
export * from './settings/index.js';
export * from './navigation/index.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.listWrapper {
display: flex;
flex-direction: column;
font-size: var(--fontSizes-small);
gap: var(--component-spacing-xs);
list-style-type: none;
margin: 0;
padding: 0;
user-select: none;
}

.listItem {
cursor: pointer;
display: inline-flex;
gap: 0.5ch;
padding-left: 0;
padding-left: calc(var(--tree-depth, 0) * var(--component-spacing-xs, 1));
}

.listItemCount {
opacity: 0.5;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the opacity here, to make it subtle?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes
image

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, this should be solved with using var(--fg-subtle)

Copy link
Author

@floscr floscr Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed, but looks almost indistinguishable imo (also I think --component-spacing-xs is too large)
image

font-weight: normal;
}
109 changes: 109 additions & 0 deletions packages/graph-editor/src/components/panels/navigation/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import React from 'react';

import { MAIN_GRAPH_ID } from '@/constants.js';
import { Stack } from '@tokens-studio/ui';
import { TreeNode, graphNodesSelector } from '@/redux/selectors/graph.js';
import { currentPanelIdSelector } from '@/redux/selectors/graph.js';
import { dockerSelector } from '@/redux/selectors/refs.js';
import { flow, get, size } from 'lodash-es';
import { useSelector } from 'react-redux';
import { useSubgraphExplorerCallback } from '@/hooks/useSubgraphExplorerCallback.js';
import styles from './index.module.css';

type ListItemProps = {
label: string;
count?: number;
isSelected?: boolean;
onClick?: () => void;
depth?: number;
};

const ListItem = function ({
label,
count,
isSelected,
onClick,
depth = 0,
}: ListItemProps) {
const style = {
'--tree-depth': depth,
fontWeight: isSelected && 'bold',
} as React.CSSProperties;

return (
<li className={styles.listItem} onClick={onClick} style={style}>
<span>{label}</span>
{count && <span className={styles.listItemCount}>({count})</span>}
</li>
);
};

const SubgraphNodeItem = function ({ node, isSelected, depth }) {
const nodeType = node.factory.title || node.nodeType();
const onNodeClick = useSubgraphExplorerCallback(node);
const childNodesCount = flow(
(x) => get(x, ['_innerGraph', 'nodes'], []),
size,
)(node);

return (
<ListItem
label={nodeType}
onClick={onNodeClick}
isSelected={isSelected}
depth={depth}
count={childNodesCount > 0 && childNodesCount}
/>
);
};

const RootGraphNodeItem = function () {
const dockerRef = useSelector(dockerSelector);
const activeGraphId = useSelector(currentPanelIdSelector);

return (
<ListItem
label={'Root'}
isSelected={activeGraphId === MAIN_GRAPH_ID}
onClick={() => dockerRef.current.updateTab(MAIN_GRAPH_ID, null, true)}
/>
);
};

export const NavigationPanel = () => {
const nodes = useSelector(graphNodesSelector);
const activeGraphId = useSelector(currentPanelIdSelector);

return (
<Stack
direction="column"
gap={4}
style={{
height: '100%',
flex: 1,
padding: 'var(--component-spacing-md)',
overflow: 'auto',
}}
>
<div style={{ padding: 'var(--component-spacing-md)' }}>
<ul className={styles.listWrapper}>
<RootGraphNodeItem />
{Object.values(nodes || {}).map(({ node, depth }: TreeNode) => {
const innerGraph = node['_innerGraph'];
if (!innerGraph) return null;
Comment on lines +89 to +90
Copy link
Author

@floscr floscr Jan 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how we can get this type out of packages/graph-engine/src/nodes/generic/subgraph.ts so we can do a proper typescript type check.

I would have liked to do a check like

if (node instanceof SubgraphNode) {
  const innerGraph = node._innerGraph;
  if (!innerGraph) return null;
}

but I'm not sure how importing for types/classes works for such packages

return (
<SubgraphNodeItem
key={node.id}
isSelected={
activeGraphId === innerGraph?.annotations['engine.id']
}
node={node}
depth={depth}
/>
);
})}
</ul>
</div>
</Stack>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ export const LayoutDropdown = () => {
<DropdownMenu.Item onSelect={() => onClick('dropPanel')}>
Nodes
</DropdownMenu.Item>
<DropdownMenu.Item onSelect={() => onClick('navigationPanel')}>
Navigator
</DropdownMenu.Item>

<DropdownMenu.Separator />
<DropdownMenu.Item onSelect={saveLayout}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { GraphPanel } from '../panels/graph/index.js';
import { Inputsheet } from '../panels/inputs/index.js';
import { Legend } from '../panels/legend/index.js';
import { LogsPanel } from '../panels/logs/index.js';
import { NavigationPanel } from '../panels/navigation/index.js';
import { NodeSettingsPanel } from '../panels/nodeSettings/index.js';
import { OutputSheet } from '../panels/output/index.js';
import { Settings } from '../panels/settings/index.js';
Expand Down Expand Up @@ -55,6 +56,11 @@ export const layoutButtons = {
title: 'Nodes',
content: <DropPanel />,
},
navigationPanel: {
id: 'navigationPanel',
title: 'Navigation',
content: <NavigationPanel />,
},
};

export type LayoutButtons = keyof typeof layoutButtons;
2 changes: 1 addition & 1 deletion packages/graph-editor/src/data/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const version = '4.3.9';
export const version = '4.3.6';
14 changes: 14 additions & 0 deletions packages/graph-editor/src/editor/graphEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { EditorApp } from './graph.js';
import { ErrorBoundary } from 'react-error-boundary';
import { ErrorBoundaryContent } from '@/components/ErrorBoundaryContent.js';
import { GraphEditorProps, ImperativeEditorRef } from './editorTypes.js';
import { ReactFlowProvider } from 'reactflow';
import React from 'react';
Expand All @@ -16,3 +18,15 @@ export const GraphEditor = React.forwardRef<
</ReactFlowProvider>
);
});

// HACK: Workaround for circular dependency not allowed for nextjs
// E.g.: when trying to create a new graph editor instance as a tab
if (typeof window !== 'undefined') {
window['newGraphEditor'] = function (ref, id) {
return (
<ErrorBoundary fallback={<ErrorBoundaryContent />}>
<GraphEditor ref={ref} id={id} />
</ErrorBoundary>
);
};
}
Comment on lines +24 to +32
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NextJS was erroring because there's a circular dependency.
I think this is not too bad if we have some interface for global methods.

47 changes: 47 additions & 0 deletions packages/graph-editor/src/hooks/useSubgraphExplorerCallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ImperativeEditorRef } from '../index.js';
import { title as annotatedTitle } from '@/annotations/index.js';
import { dockerSelector } from '@/redux/selectors/refs.js';
import { useCallback } from 'react';
import { useSelector } from 'react-redux';

export const useSubgraphExplorerCallback = (node) => {
const dockerRef = useSelector(dockerSelector);

const callback = useCallback(() => {
if (!dockerRef?.current) {
return;
}

let oneShot = false;
const innerGraph = node['_innerGraph'];
const graphId = innerGraph.annotations['engine.id'];
const title =
node.annotations[annotatedTitle] ||
innerGraph.annotations['engine.title'] ||
'Subgraph';
const existing = dockerRef.current.find(graphId);

if (!existing) {
const ref = (o: ImperativeEditorRef) => {
if (o && !oneShot) {
o.load(innerGraph);
oneShot = true;
}
};

const newTab = {
cached: true,
closable: true,
id: graphId,
group: 'graph',
title,
content: window && window['newGraphEditor'](ref, graphId),
};
dockerRef.current.dockMove(newTab, 'graphs', 'middle');
} else {
dockerRef.current.updateTab(graphId, null, true);
}
}, [dockerRef, node['_innerGraph'], node.annotations]);

return callback;
};
31 changes: 31 additions & 0 deletions packages/graph-editor/src/redux/selectors/graph.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Graph, Node } from '@tokens-studio/graph-engine';
import { MAIN_GRAPH_ID } from '@/constants.js';
import { createSelector } from 'reselect';
import { graph } from './roots.js';
Expand All @@ -18,6 +19,36 @@ export const mainGraphSelector = createSelector(
(state) => state.panels[MAIN_GRAPH_ID],
);

export type TreeNode = {
node: Node;
depth: number;
};

const collectNodes = function (
graph: Graph,
coll: Record<string, TreeNode> = {},
depth = 1,
): Record<string, TreeNode> {
for (const id in graph.nodes) {
const node: Node = graph.nodes[id];
const innerGraph = node['_innerGraph'];
coll[id] = { node, depth } as TreeNode;

if (innerGraph) {
collectNodes(innerGraph, coll, ++depth);
}
}
return coll;
};

export const graphNodesSelector = createSelector(graph, (state) => {
const graph = state.panels[MAIN_GRAPH_ID]?.graph;

if (!graph) return;

return collectNodes(graph);
});

export const graphEditorSelector = createSelector(
graph,
(state) => state.currentPanel?.ref,
Expand Down
33 changes: 19 additions & 14 deletions packages/graph-editor/src/registry/specifics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,31 @@ import { useSelector } from 'react-redux';
import Eye from '@tokens-studio/icons/Eye.js';
import React, { useCallback } from 'react';

const SubgraphExplorer = ({ node }) => {
export const useSubgraphExplorerCallback = (node) => {
const dockerRef = useSelector(dockerSelector);
const onToggle = useCallback(() => {

const callback = useCallback(() => {
if (!dockerRef?.current) {
return;
}

let oneShot = false;
const innerGraph = node._innerGraph;
const innerGraph = node['_innerGraph'];
const graphId = innerGraph.annotations['engine.id'];
const title =
node.annotations[annotatedTitle] ||
innerGraph.annotations['engine.title'] ||
'Subgraph';
//Find the container
const existing = dockerRef.current.find(graphId);

const ref = (o: ImperativeEditorRef) => {
if (o && !oneShot) {
o.load(innerGraph);
oneShot = true;
}
};

if (!existing) {
const ref = (o: ImperativeEditorRef) => {
if (o && !oneShot) {
o.load(innerGraph);
oneShot = true;
}
};

const newTab = {
cached: true,
closable: true,
Expand All @@ -53,15 +53,20 @@ const SubgraphExplorer = ({ node }) => {
</ErrorBoundary>
),
};

dockerRef.current.dockMove(newTab, 'graphs', 'middle');
} else {
dockerRef.current.updateTab(graphId, null, true);
}
}, [dockerRef, node._innerGraph, node.annotations]);
}, [dockerRef, node['_innerGraph'], node.annotations]);

return callback;
};

const SubgraphExplorer = ({ node }: { node: Node }) => {
const onClick = useSubgraphExplorerCallback(node);

return (
<Button emphasis="high" icon={<Eye />} onClick={onToggle}>
<Button emphasis="high" icon={<Eye />} onClick={onClick}>
Subgraph Explorer
</Button>
);
Expand Down
Loading