From 5667ba458b3b517c2945ac254e62dae7c2fad705 Mon Sep 17 00:00:00 2001 From: SorsOps <80043879+sorsOps@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:20:15 +0200 Subject: [PATCH 01/10] Change the createNode action to be async to account for any loaders or async action that might occur. Added compound nodes test --- .changeset/foo-bar.md | 5 + .changeset/light-avocados-hammer.md | 5 + packages/graph-editor/package.json | 3 +- .../src/components/commandPalette/index.tsx | 33 +- .../contextMenus/selectionContextMenu.tsx | 4 +- .../components/panels/dropPanel/DragItem.tsx | 7 +- .../components/panels/dropPanel/NodeEntry.tsx | 37 +- .../panels/dropPanel/dropPanel.module.css | 15 + .../components/panels/dropPanel/dropPanel.tsx | 27 +- .../src/components/panels/dropPanel/index.ts | 1 + .../panels/dropPanel/nodeEntry.module.css | 15 + .../src/components/toolbar/dropdowns/add.tsx | 4 +- .../src/editor/actions/copyNodes.tsx | 4 +- .../src/editor/actions/createNode.tsx | 13 +- .../src/editor/actions/duplicate.ts | 8 +- .../src/editor/actions/provider.tsx | 5 +- .../graph-editor/src/editor/editorTypes.ts | 2 + packages/graph-editor/src/editor/graph.tsx | 35 +- packages/graph-editor/src/editor/index.tsx | 5 +- .../graph-editor/src/editor/layout/data.tsx | 166 + .../graph-editor/src/editor/layout/groups.tsx | 78 + .../graph-editor/src/editor/layout/utils.ts | 45 + .../graph-editor/src/hooks/useOpenPanel.tsx | 2 +- packages/graph-editor/src/index.tsx | 1 - packages/graph-editor/src/redux/index.tsx | 1 + .../graph-editor/src/redux/models/registry.ts | 23 +- .../src/redux/selectors/registry.ts | 2 +- packages/graph-editor/vitest.config.ts | 19 + packages/graph-engine/package.json | 4 + packages/graph-engine/src/graph/graph.ts | 6 +- packages/graph-engine/src/graph/types.ts | 6 +- .../src/nodes/generic/subgraph.ts | 12 +- .../graph-engine/src/programmatic/input.ts | 2 +- .../graph-engine/src/programmatic/port.ts | 2 +- .../graph-engine/tests/suites/basic.test.ts | 9 +- .../tests/suites/graph/loader.test.ts | 71 + .../tests/suites/nodeUsage.test.ts | 4 +- .../src/ui/controls/index.tsx | 11 +- .../src/ui/controls/tokenArray.tsx | 48 + .../src/ui/controls/tokenSet.tsx | 13 +- packages/ui/package.json | 2 +- packages/ui/prisma/schema.prisma | 1 + packages/ui/src/api/contracts/marketplace.ts | 47 +- .../ui/src/api/controllers/marketplace.ts | 89 +- packages/ui/src/components/editor/index.tsx | 32 +- .../ui/src/components/editor/nodeTypes.tsx | 6 +- .../src/components/editor/styles.module.css | 5 + .../ui/src/components/editor/tabLoader.tsx | 44 + packages/ui/src/components/editor/toolbar.tsx | 8 +- .../{editor => }/panels/aiSummary.tsx | 0 .../ui/src/components/panels/marketPlace.tsx | 52 + .../{editor => }/panels/preview.tsx | 0 packages/ui/src/components/spinner/index.tsx | 8 + .../src/components/spinner/styles.module.css | 13 + packages/ui/src/data/compounds/index.tsx | 41 + .../ui/src/data/compounds/smoothShadow.json | 2801 +++++++++++++++++ packages/ui/src/data/layout/default.json | 84 + yarn.lock | 88 +- 58 files changed, 3827 insertions(+), 247 deletions(-) create mode 100644 .changeset/foo-bar.md create mode 100644 .changeset/light-avocados-hammer.md create mode 100644 packages/graph-editor/src/components/panels/dropPanel/nodeEntry.module.css create mode 100644 packages/graph-editor/src/editor/layout/data.tsx create mode 100644 packages/graph-editor/src/editor/layout/groups.tsx create mode 100644 packages/graph-editor/src/editor/layout/utils.ts create mode 100644 packages/graph-editor/vitest.config.ts create mode 100644 packages/graph-engine/tests/suites/graph/loader.test.ts create mode 100644 packages/nodes-design-tokens/src/ui/controls/tokenArray.tsx create mode 100644 packages/ui/src/components/editor/styles.module.css create mode 100644 packages/ui/src/components/editor/tabLoader.tsx rename packages/ui/src/components/{editor => }/panels/aiSummary.tsx (100%) create mode 100644 packages/ui/src/components/panels/marketPlace.tsx rename packages/ui/src/components/{editor => }/panels/preview.tsx (100%) create mode 100644 packages/ui/src/components/spinner/index.tsx create mode 100644 packages/ui/src/components/spinner/styles.module.css create mode 100644 packages/ui/src/data/compounds/index.tsx create mode 100644 packages/ui/src/data/compounds/smoothShadow.json create mode 100644 packages/ui/src/data/layout/default.json diff --git a/.changeset/foo-bar.md b/.changeset/foo-bar.md new file mode 100644 index 000000000..aac0e7355 --- /dev/null +++ b/.changeset/foo-bar.md @@ -0,0 +1,5 @@ +--- +"@tokens-studio/graph-editor": major +--- + +Editor now accepts a `nodeLoader` which handles loading nodes instead of a static lookup diff --git a/.changeset/light-avocados-hammer.md b/.changeset/light-avocados-hammer.md new file mode 100644 index 000000000..f7c33e073 --- /dev/null +++ b/.changeset/light-avocados-hammer.md @@ -0,0 +1,5 @@ +--- +"@tokens-studio/graph-engine": major +--- + +The engine now allows for async node loading during deserialization using a nodeLoader instead of a static lookup diff --git a/packages/graph-editor/package.json b/packages/graph-editor/package.json index 22fd7218f..61e9f893b 100644 --- a/packages/graph-editor/package.json +++ b/packages/graph-editor/package.json @@ -34,6 +34,7 @@ "format": "npm run format:eslint && npm run format:prettier", "format:eslint": "eslint . --fix", "format:prettier": "prettier \"**/*.{tsx,ts,js,md,json}\" --write", + "test": "vitest run", "test:e2e": "cypress run", "test:e2e:manual": "cypress open", "lint": "npm run lint:prettier && npm run lint:eslint", @@ -59,7 +60,6 @@ "@tokens-studio/tokens": "^0.3.7", "@tokens-studio/types": "^0.5.1", "@tokens-studio/ui": "^1.0.13", - "@tokens-studio/tokens": "^0.3.7", "@xzdarcy/react-timeline-editor": "^0.1.9", "array-move": "^4.0.0", "clsx": "^2.1.1", @@ -122,6 +122,7 @@ "cypress": "^13.9.0", "cypress-react-selector": "^3.0.0", "glob": "^11.0.0", + "happy-dom": "^16.3.0", "postcss": "^8.4.47", "postcss-cli": "^11.0.0", "postcss-css-variables": "^0.19.0", diff --git a/packages/graph-editor/src/components/commandPalette/index.tsx b/packages/graph-editor/src/components/commandPalette/index.tsx index d44657282..fa629029d 100644 --- a/packages/graph-editor/src/components/commandPalette/index.tsx +++ b/packages/graph-editor/src/components/commandPalette/index.tsx @@ -13,18 +13,19 @@ import { observer } from 'mobx-react-lite'; import { showNodesCmdPaletteSelector } from '@/redux/selectors/ui.js'; import { useDispatch, useSelector } from 'react-redux'; import { useSelectAddedNodes } from '@/hooks/useSelectAddedNodes.js'; -import React from 'react'; +import React, { useCallback } from 'react'; import Search from '@tokens-studio/icons/Search.js'; import styles from './index.module.css'; export interface ICommandMenu { items: DropPanelStore; - handleSelectNewNodeType: (node: NodeRequest) => + handleSelectNewNodeType: (node: NodeRequest) => Promise< | { graphNode: Node; flowNode: ReactFlowNode; } - | undefined; + | undefined + >; } const CommandItem = observer( @@ -80,7 +81,10 @@ const CommandMenuGroup = observer( }, ); -const CommandMenu = ({ items, handleSelectNewNodeType }: ICommandMenu) => { +export const CommandMenu = ({ + items, + handleSelectNewNodeType, +}: ICommandMenu) => { const showNodesCmdPalette = useSelector(showNodesCmdPaletteSelector); const dispatch = useDispatch(); const cursorPositionRef = React.useRef<{ x: number; y: number }>({ @@ -91,8 +95,8 @@ const CommandMenu = ({ items, handleSelectNewNodeType }: ICommandMenu) => { const reactflow = useReactFlow(); const selectAddedNodes = useSelectAddedNodes(); - const handleSelectItem = (item) => { - const newNode = handleSelectNewNodeType({ + const handleSelectItem = async (item) => { + const newNode = await handleSelectNewNodeType({ position: reactflow.screenToFlowPosition(cursorPositionRef.current), ...item, }); @@ -132,13 +136,16 @@ const CommandMenu = ({ items, handleSelectNewNodeType }: ICommandMenu) => { }, [dispatch.ui, showNodesCmdPalette]); // Close the menu when Escape key is pressed inside the input - const handleKeyDown = (e) => { - if (e.key === 'Escape') { - e.preventDefault(); + const handleKeyDown = useCallback( + (e) => { + if (e.key === 'Escape') { + e.preventDefault(); - dispatch.ui.setShowNodesCmdPalette(false); - } - }; + dispatch.ui.setShowNodesCmdPalette(false); + } + }, + [dispatch.ui], + ); return ( ); } - -export { CommandMenu }; diff --git a/packages/graph-editor/src/components/contextMenus/selectionContextMenu.tsx b/packages/graph-editor/src/components/contextMenus/selectionContextMenu.tsx index ee764bfa9..a1a09569e 100644 --- a/packages/graph-editor/src/components/contextMenus/selectionContextMenu.tsx +++ b/packages/graph-editor/src/components/contextMenus/selectionContextMenu.tsx @@ -74,7 +74,7 @@ export const SelectionContextMenu = ({ id, nodes }: INodeContextMenuProps) => { }); }, [nodes, reactFlowInstance, selectedNodeIds, store]); - const onCreateSubgraph = useCallback(() => { + const onCreateSubgraph = useCallback(async () => { //We need to work out which nodes do not have parents in the selection const lookup = new Set(selectedNodeIds); @@ -96,7 +96,7 @@ export const SelectionContextMenu = ({ id, nodes }: INodeContextMenuProps) => { y: position.y / selectedNodes.length, }; - const nodes = createNode({ + const nodes = await createNode({ type: 'studio.tokens.generic.subgraph', position: finalPosition, }); diff --git a/packages/graph-editor/src/components/panels/dropPanel/DragItem.tsx b/packages/graph-editor/src/components/panels/dropPanel/DragItem.tsx index e77c5896c..f61f31ae3 100644 --- a/packages/graph-editor/src/components/panels/dropPanel/DragItem.tsx +++ b/packages/graph-editor/src/components/panels/dropPanel/DragItem.tsx @@ -1,4 +1,5 @@ import { GrabberIcon } from '@/components/icons/GrabberIcon.js'; +import { NodeEntry } from './NodeEntry.js'; import { NodeHoverCard } from '@/components/NodeHoverCard.js'; import React, { useCallback } from 'react'; import styles from './DragItem.module.css'; @@ -6,8 +7,7 @@ import styles from './DragItem.module.css'; type DragItemProps = { data?: unknown; type: string; - children: React.ReactNode; - title?: string; + title: string; description?: string; docs?: string; icon?: React.ReactNode | string; @@ -20,7 +20,6 @@ export const DragItem = ({ description, icon, docs, - children, ...rest }: DragItemProps) => { const [isDragging, setIsDragging] = React.useState(false); @@ -62,7 +61,7 @@ export const DragItem = ({ - {children} + diff --git a/packages/graph-editor/src/components/panels/dropPanel/NodeEntry.tsx b/packages/graph-editor/src/components/panels/dropPanel/NodeEntry.tsx index 23118af8e..cbfdc0f5e 100644 --- a/packages/graph-editor/src/components/panels/dropPanel/NodeEntry.tsx +++ b/packages/graph-editor/src/components/panels/dropPanel/NodeEntry.tsx @@ -1,39 +1,18 @@ -import { Text } from '@tokens-studio/ui'; +import { Text } from '@tokens-studio/ui/Text.js'; import React from 'react'; +import styles from './nodeEntry.module.css'; -export const NodeEntry = ({ - icon, - text, -}: { +export interface INodeEntry { icon?: React.ReactNode | string; text: string; -}) => { +} + +export const NodeEntry = ({ icon, text }: INodeEntry) => { return ( <> - {icon && ( -
- {icon} -
- )} + {icon &&
{icon}
} - + {text} diff --git a/packages/graph-editor/src/components/panels/dropPanel/dropPanel.module.css b/packages/graph-editor/src/components/panels/dropPanel/dropPanel.module.css index 0adbdf244..f6574c606 100644 --- a/packages/graph-editor/src/components/panels/dropPanel/dropPanel.module.css +++ b/packages/graph-editor/src/components/panels/dropPanel/dropPanel.module.css @@ -7,6 +7,19 @@ flex-direction: column; } +.vertical { + padding-top: var(--component-spacing-xs); + width: 100%; + flex: 1; + overflow: auto; + box-sizing: border-box; +} + +.search { + padding: 0 var(--component-spacing-md); + padding-top: var(--component-spacing-lg) +} + .accordion { width: 100%; display: flex; @@ -17,10 +30,12 @@ border: 0; } } + .accordionItem { border: 0; } + .accordionTrigger { display: flex; align-items: center; diff --git a/packages/graph-editor/src/components/panels/dropPanel/dropPanel.tsx b/packages/graph-editor/src/components/panels/dropPanel/dropPanel.tsx index 3377567e0..f47d36f07 100644 --- a/packages/graph-editor/src/components/panels/dropPanel/dropPanel.tsx +++ b/packages/graph-editor/src/components/panels/dropPanel/dropPanel.tsx @@ -1,7 +1,6 @@ import { Accordion, Stack, TextInput } from '@tokens-studio/ui'; import { DragItem } from './DragItem.js'; import { DropPanelStore } from './data.js'; -import { NodeEntry } from './NodeEntry.js'; import { observer } from 'mobx-react-lite'; import { panelItemsSelector } from '@/redux/selectors/registry.js'; import { useSelector } from 'react-redux'; @@ -43,25 +42,8 @@ export const DropPanelInner = observer(({ data }: IDropPanel) => { return (
- - + + @@ -73,14 +55,13 @@ export const DropPanelInner = observer(({ data }: IDropPanel) => { .map((item) => ( - - + /> )); if (filteredValues.length === 0) { diff --git a/packages/graph-editor/src/components/panels/dropPanel/index.ts b/packages/graph-editor/src/components/panels/dropPanel/index.ts index c0c286a75..14952d6c2 100644 --- a/packages/graph-editor/src/components/panels/dropPanel/index.ts +++ b/packages/graph-editor/src/components/panels/dropPanel/index.ts @@ -1,2 +1,3 @@ export * from './dropPanel.js'; export * from './data.js'; +export * from './DragItem.js'; diff --git a/packages/graph-editor/src/components/panels/dropPanel/nodeEntry.module.css b/packages/graph-editor/src/components/panels/dropPanel/nodeEntry.module.css new file mode 100644 index 000000000..1a0742e93 --- /dev/null +++ b/packages/graph-editor/src/components/panels/dropPanel/nodeEntry.module.css @@ -0,0 +1,15 @@ +.icon { + color: var(--color-neutral-canvas-default-fg-subtle); + width: var(--size-100); + height: var(--size-100); + display: flex; + align-items: center; + justify-content: center; + font: var(--font-body-sm); +} + +.title { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} \ No newline at end of file diff --git a/packages/graph-editor/src/components/toolbar/dropdowns/add.tsx b/packages/graph-editor/src/components/toolbar/dropdowns/add.tsx index 8fb9b1ac9..c71c792c6 100644 --- a/packages/graph-editor/src/components/toolbar/dropdowns/add.tsx +++ b/packages/graph-editor/src/components/toolbar/dropdowns/add.tsx @@ -37,7 +37,7 @@ export const AddDropdown = () => { }, [dispatch.ui]); const addNode = useCallback( - (type: string) => { + async (type: string) => { const newNode = { type, position: reactFlowInstance.screenToFlowPosition( @@ -45,7 +45,7 @@ export const AddDropdown = () => { ), }; - const node = createNode(newNode); + const node = await createNode(newNode); if (node) { selectAddedNodes([node.flowNode]); diff --git a/packages/graph-editor/src/editor/actions/copyNodes.tsx b/packages/graph-editor/src/editor/actions/copyNodes.tsx index f8bbdc220..bd0a04ea4 100644 --- a/packages/graph-editor/src/editor/actions/copyNodes.tsx +++ b/packages/graph-editor/src/editor/actions/copyNodes.tsx @@ -5,7 +5,7 @@ import { v4 as uuidv4 } from 'uuid'; export const copyNodeAction = ( reactFlowInstance: ReactFlowInstance, graph, - nodeLookup, + nodeLoader, ) => { return async (nodes: SerializedNode[]) => { const { addNodes } = await nodes.reduce( @@ -19,7 +19,7 @@ export const copyNodeAction = ( ...node.engine, id: newID, }, - nodeLookup, + nodeLoader, ); graph.addNode(newGraphNode); diff --git a/packages/graph-editor/src/editor/actions/createNode.tsx b/packages/graph-editor/src/editor/actions/createNode.tsx index e3d762220..1ab5cb285 100644 --- a/packages/graph-editor/src/editor/actions/createNode.tsx +++ b/packages/graph-editor/src/editor/actions/createNode.tsx @@ -1,5 +1,5 @@ import { Dispatch } from '@/redux/store.js'; -import { Graph, Node, NodeFactory } from '@tokens-studio/graph-engine'; +import { Graph, Node, NodeLoader } from '@tokens-studio/graph-engine'; import { ReactFlowInstance, Node as ReactFlowNode } from 'reactflow'; export type NodeRequest = { @@ -11,7 +11,7 @@ export type NodeRequest = { export interface ICreateNode { reactFlowInstance: ReactFlowInstance; graph: Graph; - nodeLookup: Record; + nodeLoader: NodeLoader; iconLookup: Record; /** * If a customized node would be created in the editor, it would be created using this UI lookup. @@ -25,13 +25,13 @@ export interface ICreateNode { export const createNode = ({ reactFlowInstance, graph, - nodeLookup, + nodeLoader, iconLookup, customUI, dropPanelPosition, dispatch, }: ICreateNode) => { - return (nodeRequest: NodeRequest) => { + return async (nodeRequest: NodeRequest) => { const position = nodeRequest.position || { x: dropPanelPosition.x, y: dropPanelPosition.y, @@ -59,12 +59,13 @@ export const createNode = ({ } //Lookup the node type - const Factory = nodeLookup[nodeRequest.type]; + const Factory = await nodeLoader(nodeRequest.type); //Generate the new node - const node = new Factory({ + const node = await new Factory({ graph: graph, }); + graph.addNode(node); const finalPos = position || { x: 0, y: 0 }; diff --git a/packages/graph-editor/src/editor/actions/duplicate.ts b/packages/graph-editor/src/editor/actions/duplicate.ts index 6e19a0163..a1a4669d3 100644 --- a/packages/graph-editor/src/editor/actions/duplicate.ts +++ b/packages/graph-editor/src/editor/actions/duplicate.ts @@ -1,16 +1,10 @@ import { Edge, Node, ReactFlowInstance } from 'reactflow'; -import { - Graph, - NodeFactory, - Port, - annotatedSingleton, -} from '@tokens-studio/graph-engine'; +import { Graph, Port, annotatedSingleton } from '@tokens-studio/graph-engine'; import { v4 as uuidv4 } from 'uuid'; export interface IDuplicate { reactFlowInstance: ReactFlowInstance; graph: Graph; - nodeLookup: Record; } /** diff --git a/packages/graph-editor/src/editor/actions/provider.tsx b/packages/graph-editor/src/editor/actions/provider.tsx index de86a28b9..d145bf9d4 100644 --- a/packages/graph-editor/src/editor/actions/provider.tsx +++ b/packages/graph-editor/src/editor/actions/provider.tsx @@ -5,12 +5,13 @@ import React from 'react'; import type { NodeRequest } from './createNode.js'; export type Actions = { - createNode: (nodeRequest: NodeRequest) => + createNode: (nodeRequest: NodeRequest) => Promise< | undefined | { graphNode: Node; flowNode: FlowNode; - }; + } + >; deleteNode: (nodeId: string) => void; copyNodes: (nodes: SerializedNode[]) => void; duplicateNodes: (nodeIds: string[]) => void; diff --git a/packages/graph-editor/src/editor/editorTypes.ts b/packages/graph-editor/src/editor/editorTypes.ts index c613b6ae3..e1aede73e 100644 --- a/packages/graph-editor/src/editor/editorTypes.ts +++ b/packages/graph-editor/src/editor/editorTypes.ts @@ -3,6 +3,7 @@ import { ExternalLoader, Graph, Node as GraphNode, + NodeLoader, SchemaObject, SerializedGraph, } from '@tokens-studio/graph-engine'; @@ -15,6 +16,7 @@ import type { LayoutBase, TabBase, TabData } from 'rc-dock'; export interface EditorProps { id?: string; + nodeLoader?: NodeLoader; tabLoader?: (tab: TabBase) => TabData | undefined; /** diff --git a/packages/graph-editor/src/editor/graph.tsx b/packages/graph-editor/src/editor/graph.tsx index 000dc8cdc..3e17a7652 100644 --- a/packages/graph-editor/src/editor/graph.tsx +++ b/packages/graph-editor/src/editor/graph.tsx @@ -57,7 +57,7 @@ import { PassthroughNode } from '@/components/flow/nodes/passthroughNode.js'; import { SelectionContextMenu } from '@/components/contextMenus/selectionContextMenu.js'; import { capabilitiesSelector, - nodeTypesSelector, + nodeLoaderSelector, panelItemsSelector, } from '@/redux/selectors/registry.js'; import { clear } from './actions/clear.js'; @@ -113,7 +113,7 @@ export const EditorApp = React.forwardRef< GraphEditorProps >((props: GraphEditorProps, ref) => { const panelItems = useSelector(panelItemsSelector); - const fullNodeLookup = useSelector(nodeTypesSelector); + const nodeLoader = useSelector(nodeLoaderSelector); const { id, customNodeUI = {}, children } = props; const externalLoader = useExternalLoader(); @@ -397,7 +397,7 @@ export const EditorApp = React.forwardRef< createNode({ reactFlowInstance, graph, - nodeLookup: fullNodeLookup, + nodeLoader, iconLookup, customUI: customNodeMap, dropPanelPosition, @@ -406,7 +406,7 @@ export const EditorApp = React.forwardRef< [ reactFlowInstance, graph, - fullNodeLookup, + nodeLoader, iconLookup, customNodeMap, dropPanelPosition, @@ -429,7 +429,7 @@ export const EditorApp = React.forwardRef< }, loadRaw: async (serializedGraph) => { if (internalRef.current) { - await graph.deserialize(serializedGraph, fullNodeLookup); + await graph.deserialize(serializedGraph, nodeLoader); internalRef?.current.load(graph); } }, @@ -510,7 +510,7 @@ export const EditorApp = React.forwardRef< [ reactFlowInstance, graph, - fullNodeLookup, + nodeLoader, setNodes, setEdges, dispatch.graph, @@ -589,15 +589,16 @@ export const EditorApp = React.forwardRef< ); const onEdgeDblClick = useCallback( - (event, clickedEdge) => { + async (event, clickedEdge) => { event.stopPropagation(); const position = reactFlowInstance?.screenToFlowPosition({ x: event.clientX, y: event.clientY, }); + const PassthroughFactory = await nodeLoader(PASSTHROUGH); - const newNode = new fullNodeLookup[PASSTHROUGH]({ + const newNode = new PassthroughFactory({ graph, }); @@ -657,7 +658,7 @@ export const EditorApp = React.forwardRef< return [...filtered, newEdge, newEdge2]; }); }, - [fullNodeLookup, graph, reactFlowInstance, setEdges, setNodes], + [nodeLoader, graph, reactFlowInstance, setEdges, setNodes], ); const onNodeDrag = useCallback( @@ -698,10 +699,9 @@ export const EditorApp = React.forwardRef< const duplicateNodesAction = duplicateNodes({ graph, reactFlowInstance, - nodeLookup: fullNodeLookup, }); - const copyNodes = copyNodeAction(reactFlowInstance, graph, fullNodeLookup); + const copyNodes = copyNodeAction(reactFlowInstance, graph, nodeLoader); const selectAddedNodes = useSelectAddedNodes(); const onDrop = useCallback( @@ -721,12 +721,15 @@ export const EditorApp = React.forwardRef< }); //Some of the nodes might be invalid, so remember to filter them out - const newNodes = positionUpdated - .map((nodeRequest) => handleSelectNewNodeType(nodeRequest)) - .filter((x) => !!x) - .map((x) => x?.flowNode ?? ({} as Node)); + const newNodes = await Promise.all( + positionUpdated.map((nodeRequest) => + handleSelectNewNodeType(nodeRequest), + ), + ); - selectAddedNodes(newNodes); + selectAddedNodes( + newNodes.filter((x) => !!x).map((x) => x?.flowNode ?? ({} as Node)), + ); }, [handleSelectNewNodeType, reactFlowInstance], ); diff --git a/packages/graph-editor/src/editor/index.tsx b/packages/graph-editor/src/editor/index.tsx index 13275df10..80d621908 100644 --- a/packages/graph-editor/src/editor/index.tsx +++ b/packages/graph-editor/src/editor/index.tsx @@ -3,7 +3,6 @@ import { LayoutController } from './layoutController.js'; import { ReduxProvider } from '../redux/index.js'; import { ToastProvider } from '@/hooks/useToast.js'; import { defaultControls } from '@/registry/control.js'; -import { nodeLookup as defaultNodeLookup } from '@tokens-studio/graph-engine'; import { defaultPanelGroupsFactory } from '@/components/index.js'; import { defaultSpecifics } from '@/registry/specifics.js'; import React from 'react'; @@ -20,7 +19,7 @@ export const Editor = React.forwardRef( toolbarButtons, schemas, - nodeTypes = defaultNodeLookup, + nodeLoader, controls = [...defaultControls], specifics = defaultSpecifics, icons, @@ -34,7 +33,7 @@ export const Editor = React.forwardRef( schemas={schemas} controls={controls} panelItems={panelItems} - nodeTypes={nodeTypes} + nodeTypes={nodeLoader} specifics={specifics} capabilities={capabilities} toolbarButtons={toolbarButtons} diff --git a/packages/graph-editor/src/editor/layout/data.tsx b/packages/graph-editor/src/editor/layout/data.tsx new file mode 100644 index 000000000..4f9d972fd --- /dev/null +++ b/packages/graph-editor/src/editor/layout/data.tsx @@ -0,0 +1,166 @@ +import { DropPanel } from '@/components/panels/dropPanel/dropPanel.js'; +import { ErrorBoundary } from 'react-error-boundary'; +import { ErrorBoundaryContent } from '@/components/ErrorBoundaryContent.js'; +import { GraphEditor } from '../graphEditor.js'; +import { Inputsheet } from '@/components/panels/inputs/index.js'; +import { LayoutData, TabBase, TabData } from 'rc-dock'; +import { MAIN_GRAPH_ID } from '@/constants.js'; +import { OutputSheet } from '@/components/panels/output/index.js'; +import React from 'react'; + +export const layoutDataFactory = (): LayoutData => { + return { + dockbox: { + mode: 'vertical', + children: [ + { + mode: 'horizontal', + children: [ + { + size: 2, + mode: 'vertical', + children: [ + { + mode: 'horizontal', + children: [ + { + size: 3, + mode: 'vertical', + children: [ + { + tabs: [ + { + id: 'dropPanel', + title: '', + content: <>, + }, + { + id: 'previewNodesPanel', + title: '', + content: <>, + }, + ], + }, + ], + }, + { + size: 17, + mode: 'vertical', + children: [ + { + id: 'graphs', + size: 700, + group: 'graph', + panelLock: { panelStyle: 'graph' }, + tabs: [ + { + closable: true, + cached: true, + id: MAIN_GRAPH_ID, + group: 'graph', + title: 'Graph', + content: <>, + }, + ], + }, + ], + }, + + { + size: 4, + mode: 'vertical', + children: [ + { + size: 12, + tabs: [ + { + id: 'input', + title: '', + content: <>, + }, + ], + }, + { + size: 12, + tabs: [ + { + id: 'outputs', + title: '', + content: <>, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + }; +}; + +export const layoutLoader = + (props, ref) => + (tab: TabBase): TabData => { + const { id } = tab; + switch (id) { + case MAIN_GRAPH_ID: + return { + closable: true, + cached: true, + id: MAIN_GRAPH_ID, + group: 'graph', + title: 'Graph', + content: ( + }> + + + ), + }; + case 'input': + return { + closable: true, + cached: true, + group: 'popout', + id: 'input', + title: 'Inputs', + content: ( + }> + + + ), + }; + case 'outputs': + return { + closable: true, + cached: true, + group: 'popout', + id: 'outputs', + title: 'Outputs', + content: ( + }> + + + ), + }; + + case 'dropPanel': + return { + group: 'popout', + id: 'dropPanel', + title: 'Nodes', + content: ( + }> + + + ), + closable: true, + }; + default: + return tab as TabData; + } + }; diff --git a/packages/graph-editor/src/editor/layout/groups.tsx b/packages/graph-editor/src/editor/layout/groups.tsx new file mode 100644 index 000000000..378d6dee2 --- /dev/null +++ b/packages/graph-editor/src/editor/layout/groups.tsx @@ -0,0 +1,78 @@ +import { IconButton } from '@tokens-studio/ui/IconButton.js'; +import { Stack } from '@tokens-studio/ui/Stack.js'; +import { TabGroup } from 'rc-dock'; +import ArrowUpRight from '@tokens-studio/icons/ArrowUpRight.js'; +import Maximize from '@tokens-studio/icons/Maximize.js'; +import React from 'react'; +import Reduce from '@tokens-studio/icons/Reduce.js'; +import Xmark from '@tokens-studio/icons/Xmark.js'; + +const DockButton = (rest) => { + return ; +}; + +export const groups: Record = { + popout: { + animated: true, + floatable: true, + + panelExtra: (panelData, context) => { + const buttons: React.ReactElement[] = []; + if (panelData?.parent?.mode !== 'window') { + const maxxed = panelData?.parent?.mode === 'maximize'; + buttons.push( + : } + onClick={() => context.dockMove(panelData, null, 'maximize')} + >, + ); + buttons.push( + } + onClick={() => context.dockMove(panelData, null, 'new-window')} + >, + ); + } + buttons.push( + } + onClick={() => context.dockMove(panelData, null, 'remove')} + >, + ); + return {buttons}; + }, + }, + /** + * Note that the graph has a huge issue when ran in a popout window, as such we disable it for now + */ + graph: { + animated: true, + floatable: true, + panelExtra: (panelData, context) => { + const buttons: React.ReactElement[] = []; + if (panelData?.parent?.mode !== 'window') { + const maxxed = panelData?.parent?.mode === 'maximize'; + buttons.push( + : } + onClick={() => context.dockMove(panelData, null, 'maximize')} + >, + ); + } + + return {buttons}; + }, + }, +}; diff --git a/packages/graph-editor/src/editor/layout/utils.ts b/packages/graph-editor/src/editor/layout/utils.ts new file mode 100644 index 000000000..0ac3069cf --- /dev/null +++ b/packages/graph-editor/src/editor/layout/utils.ts @@ -0,0 +1,45 @@ +import { BoxBase, LayoutBase, PanelBase } from 'rc-dock'; + +export function recurseFindGraphPanel( + base: BoxBase | PanelBase, +): PanelBase | null { + if (base.id === 'graphs') { + return base as PanelBase; + } + //Check if it has children + if ((base as BoxBase).children) { + for (const child of (base as BoxBase).children) { + const found = recurseFindGraphPanel(child); + if (found) { + return found; + } + } + } + return null; +} + +export function findGraphPanel(layout: LayoutBase): PanelBase | null { + //We need to recursively search for the graph panel + // It is most likely in the dockbox + const dockbox = recurseFindGraphPanel(layout.dockbox); + if (dockbox) { + return dockbox; + } + if (layout.floatbox) { + const floatBox = recurseFindGraphPanel(layout.floatbox); + if (floatBox) { + return floatBox; + } + } else if (layout.maxbox) { + const tab = recurseFindGraphPanel(layout.maxbox); + if (tab) { + return tab; + } + } else if (layout.windowbox) { + const tab = recurseFindGraphPanel(layout.windowbox); + if (tab) { + return tab; + } + } + return null; +} diff --git a/packages/graph-editor/src/hooks/useOpenPanel.tsx b/packages/graph-editor/src/hooks/useOpenPanel.tsx index d1a9b3d53..06f6b4201 100644 --- a/packages/graph-editor/src/hooks/useOpenPanel.tsx +++ b/packages/graph-editor/src/hooks/useOpenPanel.tsx @@ -5,7 +5,7 @@ import React, { MutableRefObject, useCallback } from 'react'; import type { DockLayout } from 'rc-dock'; export type PanelFactory = { - group: string; + group: string | undefined; id: string; title: React.ReactElement | string; content: React.ReactElement; diff --git a/packages/graph-editor/src/index.tsx b/packages/graph-editor/src/index.tsx index b508c6315..1207401ef 100644 --- a/packages/graph-editor/src/index.tsx +++ b/packages/graph-editor/src/index.tsx @@ -19,4 +19,3 @@ export * from './registry/control.js'; export * from './registry/specifics.js'; export * from './registry/toolbar.js'; export * from './types/index.js'; -export { DropPanelStore } from './components/panels/dropPanel/data.js'; diff --git a/packages/graph-editor/src/redux/index.tsx b/packages/graph-editor/src/redux/index.tsx index 8a824681e..fe0d0742e 100644 --- a/packages/graph-editor/src/redux/index.tsx +++ b/packages/graph-editor/src/redux/index.tsx @@ -34,6 +34,7 @@ export const ReduxProvider = ({ }, [controls]); useEffect(() => { + console.log(nodeTypes); store.dispatch.registry.setNodeTypes(nodeTypes); }, [nodeTypes]); diff --git a/packages/graph-editor/src/redux/models/registry.ts b/packages/graph-editor/src/redux/models/registry.ts index 742830230..c2b0ed442 100644 --- a/packages/graph-editor/src/redux/models/registry.ts +++ b/packages/graph-editor/src/redux/models/registry.ts @@ -2,6 +2,7 @@ import { AllSchemas, CapabilityFactory, Node, + NodeLoader, SchemaObject, } from '@tokens-studio/graph-engine'; import { Control } from '@/types/controls.js'; @@ -24,7 +25,7 @@ export interface RegistryState { icons: Record; inputControls: Record>; controls: Control[]; - nodeTypes: Record; + nodeTypes: NodeLoader; panelItems: DropPanelStore; capabilities: CapabilityFactory[]; toolbarButtons: ReactElement[]; @@ -38,7 +39,9 @@ export const registryState = createModel()({ inputControls: { ...inputControls }, controls: [...(defaultControls as Control[])], panelItems: defaultPanelGroupsFactory(), - nodeTypes: {}, + nodeTypes: async () => { + throw new Error('Node type not found'); + }, capabilities: [], toolbarButtons: DefaultToolbarButtons(), schemas: AllSchemas, @@ -56,31 +59,31 @@ export const registryState = createModel()({ toolbarButtons, }; }, - setCapabilities(state, payload: CapabilityFactory[]) { + setCapabilities(state, capabilities: CapabilityFactory[]) { return { ...state, - capabilities: payload, + capabilities, }; }, - setNodeTypes: (state, payload: Record) => { + setNodeTypes: (state, nodeTypes: NodeLoader) => { return { ...state, - nodeTypes: payload, + nodeTypes, }; }, setSpecifics: ( state, - payload: Record>, + nodeSpecifics: Record>, ) => { return { ...state, - nodeSpecifics: payload, + nodeSpecifics, }; }, - setControls(state, payload: Control[]) { + setControls(state, controls: Control[]) { return { ...state, - controls: payload, + controls, }; }, registerIcons(state, payload: Record) { diff --git a/packages/graph-editor/src/redux/selectors/registry.ts b/packages/graph-editor/src/redux/selectors/registry.ts index ed9e71f27..478de24d5 100644 --- a/packages/graph-editor/src/redux/selectors/registry.ts +++ b/packages/graph-editor/src/redux/selectors/registry.ts @@ -23,7 +23,7 @@ export const capabilitiesSelector = createSelector( (state) => state.capabilities, ); -export const nodeTypesSelector = createSelector( +export const nodeLoaderSelector = createSelector( registry, (state) => state.nodeTypes, ); diff --git a/packages/graph-editor/vitest.config.ts b/packages/graph-editor/vitest.config.ts new file mode 100644 index 000000000..80e559d59 --- /dev/null +++ b/packages/graph-editor/vitest.config.ts @@ -0,0 +1,19 @@ +/// +import { defineConfig } from 'vitest/config'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + // Note that this needs to be present here as well as it doesn't get picked up from the root vite.config.ts + plugins: [tsconfigPaths()], + test: { + include: ['tests/**/*.test.[jt]s?(x)', 'src/**/*.test.[jt]s?(x)'], + environment: 'happy-dom', + globals: true, + server: { + deps: { + // to ensure importing CSS files is working + inline: ['@tokens-studio/ui'], + }, + }, + }, +}); diff --git a/packages/graph-engine/package.json b/packages/graph-engine/package.json index 9a2e3b068..0c60157c2 100644 --- a/packages/graph-engine/package.json +++ b/packages/graph-engine/package.json @@ -9,6 +9,10 @@ ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" + }, + "./nodes/*": { + "import": "./dist/nodes/*", + "types": "./dist/nodes/*" } }, "repository": { diff --git a/packages/graph-engine/src/graph/graph.ts b/packages/graph-engine/src/graph/graph.ts index e23cc0273..29581be36 100644 --- a/packages/graph-engine/src/graph/graph.ts +++ b/packages/graph-engine/src/graph/graph.ts @@ -17,7 +17,7 @@ import { makeObservable, observable, toJS } from 'mobx'; import { topologicalSort } from './topologicSort.js'; import { v4 as uuid } from 'uuid'; import type { ExternalLoader, NodeRun, NodeStart } from '../types.js'; -import type { NodeFactory, SerializedGraph } from './types.js'; +import type { NodeLoader, SerializedGraph } from './types.js'; export type CapabilityFactory = { name: string; @@ -456,7 +456,7 @@ export class Graph { */ async deserialize( serialized: SerializedGraph, - lookup: Record + lookup: NodeLoader ): Promise { const version = (serialized.annotations && serialized.annotations['engine.version']) || @@ -484,7 +484,7 @@ export class Graph { await Promise.all( serialized.nodes.map(async node => { - const factory = lookup[node.type]; + const factory = await lookup(node.type); return await factory.deserialize({ serialized: node, graph: this, diff --git a/packages/graph-engine/src/graph/types.ts b/packages/graph-engine/src/graph/types.ts index 41ffff903..a6be45cf0 100644 --- a/packages/graph-engine/src/graph/types.ts +++ b/packages/graph-engine/src/graph/types.ts @@ -5,7 +5,7 @@ import { Node } from '../programmatic/node.js'; export interface SerializedInput { name: string; value?: any; - visible: boolean; + visible?: boolean; variadic?: boolean; type: TypeDefinition; dynamicType?: GraphSchema; @@ -37,7 +37,9 @@ export interface SerializedGraph { export type IDeserializeOpts = { serialized: SerializedNode; graph: Graph; - lookup: Record; + lookup: NodeLoader; }; export type NodeFactory = typeof Node; + +export type NodeLoader = (type: string) => Promise | NodeFactory; diff --git a/packages/graph-engine/src/nodes/generic/subgraph.ts b/packages/graph-engine/src/nodes/generic/subgraph.ts index d9edb3df2..0f1b2c3c7 100644 --- a/packages/graph-engine/src/nodes/generic/subgraph.ts +++ b/packages/graph-engine/src/nodes/generic/subgraph.ts @@ -14,7 +14,7 @@ import InputNode from './input.js'; import OutputNode from './output.js'; export interface SerializedSubgraphNode extends SerializedNode { - innergraph: SerializedGraph; + innergraph?: SerializedGraph; } export interface ISubgraphNode extends INodeDefinition { innergraph?: Graph; @@ -124,10 +124,12 @@ export default class SubgraphNode extends Node { } static override async deserialize(opts: IDeserializeOpts) { - const innergraph = await new Graph().deserialize( - (opts.serialized as SerializedSubgraphNode).innergraph, - opts.lookup - ); + const serializedInner = (opts.serialized as SerializedSubgraphNode) + .innergraph; + + const innergraph = serializedInner + ? await new Graph().deserialize(serializedInner, opts.lookup) + : undefined; const node = (await super.deserialize({ ...opts, diff --git a/packages/graph-engine/src/programmatic/input.ts b/packages/graph-engine/src/programmatic/input.ts index ffbe67782..430036399 100644 --- a/packages/graph-engine/src/programmatic/input.ts +++ b/packages/graph-engine/src/programmatic/input.ts @@ -10,7 +10,7 @@ export interface IInputProps { name: string; type: GraphSchema; value: T; - visible: boolean; + visible?: boolean; node: Node; variadic?: boolean; annotations?: Record; diff --git a/packages/graph-engine/src/programmatic/port.ts b/packages/graph-engine/src/programmatic/port.ts index 0607c74db..613f62050 100644 --- a/packages/graph-engine/src/programmatic/port.ts +++ b/packages/graph-engine/src/programmatic/port.ts @@ -5,7 +5,7 @@ import { action, computed, makeObservable, observable } from 'mobx'; export interface IPort { name: string; - visible: boolean; + visible?: boolean; node: Node; type: GraphSchema; value: T; diff --git a/packages/graph-engine/tests/suites/basic.test.ts b/packages/graph-engine/tests/suites/basic.test.ts index 8f66b4c96..60d48fdac 100644 --- a/packages/graph-engine/tests/suites/basic.test.ts +++ b/packages/graph-engine/tests/suites/basic.test.ts @@ -1,9 +1,4 @@ -import { - Graph, - NodeFactory, - SerializedGraph, - nodeLookup -} from '../../src/index.js'; +import { Graph, SerializedGraph, nodeLookup } from '../../src/index.js'; import { describe, expect, test } from 'vitest'; import basic from '../data/processed/basic.js'; @@ -11,7 +6,7 @@ describe('basic', () => { test('performs basic passthrough calculations', async () => { const graph = await new Graph().deserialize( basic as unknown as SerializedGraph, - nodeLookup as Record + key => nodeLookup[key] ); const result = await graph.execute(); diff --git a/packages/graph-engine/tests/suites/graph/loader.test.ts b/packages/graph-engine/tests/suites/graph/loader.test.ts new file mode 100644 index 000000000..d35e4c718 --- /dev/null +++ b/packages/graph-engine/tests/suites/graph/loader.test.ts @@ -0,0 +1,71 @@ +import { Graph, SerializedGraph, nodeLookup } from '../../../src/index.js'; +import { describe, expect, test } from 'vitest'; + +describe('Loader', () => { + test('handles async loading of nodes', async () => { + const serialized = { + nodes: [ + { + id: 'x', + type: 'studio.tokens.generic.input', + inputs: [ + { + name: 'foo', + type: { + $id: 'https://schemas.tokens.studio/string.json', + title: 'String', + type: 'string' + } + } + ], + annotations: { + 'engine.singleton': true, + 'engine.dynamicInputs': true + } + }, + { + id: 'y', + type: 'studio.tokens.generic.output', + inputs: [ + { + name: 'input', + value: 'black', + type: { + $id: 'https://schemas.tokens.studio/string.json', + title: 'String', + type: 'string' + } + } + ], + annotations: { + 'engine.singleton': true, + 'engine.dynamicInputs': true + } + } + ], + edges: [], + annotations: { + 'engine.id': '5dc7b415-9e46-455c-9375-2380b7fbdfa5', + 'engine.version': '0.12.0' + } + } as SerializedGraph; + + const nodeLoader = async key => { + return Promise.resolve(nodeLookup[key]); + }; + + const newGraph = await new Graph().deserialize(serialized, nodeLoader); + const deserializedOutput = await newGraph.execute(); + + expect(deserializedOutput.output).to.eql({ + input: { + type: { + $id: 'https://schemas.tokens.studio/string.json', + title: 'String', + type: 'string' + }, + value: 'black' + } + }); + }); +}); diff --git a/packages/graph-engine/tests/suites/nodeUsage.test.ts b/packages/graph-engine/tests/suites/nodeUsage.test.ts index d11076251..ed7feb30d 100644 --- a/packages/graph-engine/tests/suites/nodeUsage.test.ts +++ b/packages/graph-engine/tests/suites/nodeUsage.test.ts @@ -4,6 +4,8 @@ import { describe, expect, test } from 'vitest'; import InputNode from '../../src/nodes/generic/input.js'; import OutputNode from '../../src/nodes/generic/output.js'; +const nodeLoader = key => nodeLookup[key]; + describe('nodeUsage', () => { test('performs basic passthrough calculations', async () => { const graph = new Graph(); @@ -113,7 +115,7 @@ describe('nodeUsage', () => { } ]); - const newGraph = await new Graph().deserialize(serialized, nodeLookup); + const newGraph = await new Graph().deserialize(serialized, nodeLoader); const deserializedOutput = await newGraph.execute(); expect(deserializedOutput.output).to.eql({ diff --git a/packages/nodes-design-tokens/src/ui/controls/index.tsx b/packages/nodes-design-tokens/src/ui/controls/index.tsx index 7497c45d8..b602ce3db 100644 --- a/packages/nodes-design-tokens/src/ui/controls/index.tsx +++ b/packages/nodes-design-tokens/src/ui/controls/index.tsx @@ -1,6 +1,7 @@ -import { TOKEN } from '../../schemas/index.js'; -import { TokenArrayField } from './tokenSet.js'; +import { TOKEN, TOKEN_SET } from '../../schemas/index.js'; +import { TokenArrayField } from './tokenArray.js'; import { TokenField } from './token.js'; +import { TokenSetField } from './tokenSet.js'; import { VariadicTokenSet } from './variadicTokenSet.js'; import { variadicMatcher } from '@tokens-studio/graph-editor'; import type { Input, Port } from '@tokens-studio/graph-engine'; @@ -17,6 +18,12 @@ export const controls = [ }, component: TokenArrayField }, + + { + matcher: (port: Port) => port.type.$id === TOKEN_SET, + component: TokenSetField + }, + { matcher: (port: Port) => port.type.$id === TOKEN, component: TokenField diff --git a/packages/nodes-design-tokens/src/ui/controls/tokenArray.tsx b/packages/nodes-design-tokens/src/ui/controls/tokenArray.tsx new file mode 100644 index 000000000..e7452d227 --- /dev/null +++ b/packages/nodes-design-tokens/src/ui/controls/tokenArray.tsx @@ -0,0 +1,48 @@ +import { Accordion, Button, Separator, Stack } from '@tokens-studio/ui'; +import { IField, flatTokensRestoreToMap } from '@tokens-studio/graph-editor'; +import { Token } from './token.js'; +import { observer } from 'mobx-react-lite'; +import React from 'react'; + +export const TokenArrayField = ({ port }: IField) => { + return ; +}; + +export const TokenArrayFieldInner = observer(({ value }) => { + const downloadTokens = () => { + const element = document.createElement('a'); + const asObj = flatTokensRestoreToMap(value); + const file = new Blob([JSON.stringify(asObj)], { + type: 'application/json' + }); + element.href = URL.createObjectURL(file); + element.download = 'tokens.json'; + document.body.appendChild(element); + element.click(); + }; + + return ( + + + + Tokens + + + + {(value || []).map(token => ( + + ))} + + + + + + + + ); +}); diff --git a/packages/nodes-design-tokens/src/ui/controls/tokenSet.tsx b/packages/nodes-design-tokens/src/ui/controls/tokenSet.tsx index 010692c20..26d0b62bc 100644 --- a/packages/nodes-design-tokens/src/ui/controls/tokenSet.tsx +++ b/packages/nodes-design-tokens/src/ui/controls/tokenSet.tsx @@ -1,13 +1,14 @@ import { Accordion, Button, Separator, Stack } from '@tokens-studio/ui'; -import { IField, flatTokensRestoreToMap } from '@tokens-studio/graph-editor'; +import { IField } from '@tokens-studio/graph-editor'; import { Token } from './token.js'; +import { flatten } from '../../utils/index.js'; import { observer } from 'mobx-react-lite'; -import React from 'react'; +import React, { useMemo } from 'react'; -export const TokenArrayField = observer(({ port }: IField) => { +export const TokenSetField = observer(({ port }: IField) => { const downloadTokens = () => { const element = document.createElement('a'); - const asObj = flatTokensRestoreToMap(port.value); + const asObj = port.value; const file = new Blob([JSON.stringify(asObj)], { type: 'application/json' }); @@ -17,6 +18,8 @@ export const TokenArrayField = observer(({ port }: IField) => { element.click(); }; + const tokens = useMemo(() => flatten(port.value || {}), []); + return ( @@ -30,7 +33,7 @@ export const TokenArrayField = observer(({ port }: IField) => { align='center' style={{ padding: 'var(--component-spacing-md)' }} > - {(port.value || []).map(token => ( + {tokens.map(token => ( ))} diff --git a/packages/ui/package.json b/packages/ui/package.json index f53a9fca3..2a9c30881 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -74,7 +74,7 @@ "prisma": "5.16.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-error-boundary": "4.0.11", + "react-error-boundary": "^4.0.13", "react-joyride": "2.8.1", "react-no-ssr": "1.1.0", "react-redux": "^8.0.5", diff --git a/packages/ui/prisma/schema.prisma b/packages/ui/prisma/schema.prisma index 4d9fadda9..7c7256ef7 100644 --- a/packages/ui/prisma/schema.prisma +++ b/packages/ui/prisma/schema.prisma @@ -3,6 +3,7 @@ generator client { provider = "prisma-client-js" + previewFeatures = ["fullTextSearch"] } datasource db { diff --git a/packages/ui/src/api/contracts/marketplace.ts b/packages/ui/src/api/contracts/marketplace.ts index 30bda508f..7995bade5 100644 --- a/packages/ui/src/api/contracts/marketplace.ts +++ b/packages/ui/src/api/contracts/marketplace.ts @@ -1,3 +1,4 @@ +import { SerializedGraph } from '../schemas/graph.ts'; import { initContract } from '@ts-rest/core'; import { z } from 'zod'; @@ -24,9 +25,9 @@ export const marketplaceContract = c.router({ } }, getGraph: { - summary: 'Retrieves a graph', + summary: 'Gets a graphs metadata', method: 'GET', - path: '/marketplace/graph/:id', + path: '/marketplace/graph/:id/meta', pathParams: z.object({ id: z.string() }), @@ -48,6 +49,21 @@ export const marketplaceContract = c.router({ }) } }, + retrieveGraph: { + summary: 'Retrieves a graph', + method: 'GET', + path: '/marketplace/graph/:id/graph', + pathParams: z.object({ + id: z.string() + }), + responses: { + 200: z.object({ + id: z.string(), + graph: SerializedGraph, + version: z.string() + }) + } + }, likeGraph: { summary: 'Likes a graph', method: 'POST', @@ -60,12 +76,38 @@ export const marketplaceContract = c.router({ 200: z.object({}) } }, + searchGraphs: { + summary: 'Searches through marketplace graphs ', + method: 'GET', + path: '/marketplace/graphs/search', + + query: z.object({ + name: z.string(), + take: z.number().lte(20).gte(1).optional().default(10), + skip: z.number().gte(0).optional().default(0), + token: z.string().optional().describe('The token to use for pagination') + }), + + responses: { + 200: z.object({ + graphs: z.array( + z.object({ + name: z.string(), + id: z.string(), + description: z.string().nullable() + }) + ), + token: z.string().optional() + }) + } + }, listGraphs: { summary: 'Lists all public graphs ', method: 'GET', path: '/marketplace/graphs', query: z.object({ + containsText: z.string().optional(), take: z.number().lte(50).gte(1).optional().default(10), skip: z.number().gte(0).optional().default(0), token: z.string().optional().describe('The token to use for pagination') @@ -93,7 +135,6 @@ export const marketplaceContract = c.router({ }) } }, - editGraph: { summary: 'Edits an existing graph', method: 'PUT', diff --git a/packages/ui/src/api/controllers/marketplace.ts b/packages/ui/src/api/controllers/marketplace.ts index 657acfb76..52ad74a6e 100644 --- a/packages/ui/src/api/controllers/marketplace.ts +++ b/packages/ui/src/api/controllers/marketplace.ts @@ -318,9 +318,96 @@ export const router = tsr.router( } }; }, - listGraphs: async ({ query }) => { + + retrieveGraph: async ({ params }) => { + const { id } = params; + const graph = await prisma.publishedGraph.findFirst({ + where: { + id + }, + include: { + versions: true + } + }); + + if (!graph) { + return { + status: 404, + body: { + message: 'Graph not found' + } + }; + } + + //Check for versions + const latestVersion = graph.versions[0]; + + if (!latestVersion) { + return { + status: 404, + body: { + message: 'Graph not found' + } + }; + } + + return { + status: 201, + body: { + id: latestVersion.id, + graph: latestVersion.graph, + verison: latestVersion.version + } + }; + }, + + searchGraphs: async ({ query }) => { const cursorAdd = withCursor(query.token); + const where: Prisma.PublishedGraphWhereInput = { + name: { + contains: query.name, + mode: 'insensitive' + } + }; + + const graphs = await prisma.publishedGraph.findMany({ + where, + take: query.take, + skip: query.skip, + orderBy: [ + // If searching, prioritize exact matches and then partial matches + { + _relevance: { + fields: ['name'], + search: query.name, + sort: 'desc' + } + }, + { updatedAt: 'desc' } + ], + select: { + name: true, + id: true, + description: true + }, + ...cursorAdd + }); + + const token = + graphs.length > 0 ? graphs?.[graphs.length - 1]?.id : undefined; + + return { + status: 200, + body: { + token, + graphs: graphs + } + }; + }, + + listGraphs: async ({ query }) => { + const cursorAdd = withCursor(query.token); const graphs = await prisma.publishedGraph.findMany({ take: query.take, skip: query.skip, diff --git a/packages/ui/src/components/editor/index.tsx b/packages/ui/src/components/editor/index.tsx index 09b77a072..6e1f23af9 100644 --- a/packages/ui/src/components/editor/index.tsx +++ b/packages/ui/src/components/editor/index.tsx @@ -4,7 +4,6 @@ import { DropPanelInner, Editor } from '@tokens-studio/graph-editor'; import { EmptyStateEditor } from '../EmptyStateEditor.tsx'; import { ErrorBoundary } from 'react-error-boundary'; import { ExamplesPicker } from '../ExamplesPicker.tsx'; -import { Spinner } from '@tokens-studio/ui/Spinner.js'; import { capabilities, controls, @@ -16,10 +15,16 @@ import { specifics } from './data.tsx'; import { defaultLayout } from './layout.ts'; +import { loadCompounds } from '@/data/compounds/index.tsx'; import { observer } from 'mobx-react-lite'; +import { tabLoader } from './tabLoader.tsx'; import { useGetEditor } from '@/hooks/useGetEditor.ts'; import React, { useCallback } from 'react'; +import Spinner from '../spinner/index.tsx'; import globalState from '@/mobx/index.tsx'; +import initialLayout from '@/data/layout/default.json'; +import styles from './styles.module.css'; +import type { LayoutBase } from 'rc-dock'; import type { ReactElement } from 'react'; import type { TabBase, TabData } from 'rc-dock'; @@ -59,16 +64,19 @@ export const EditorTab = observer( }, []); return ( -
+
} - {loading && ( -
- -
- )} + {loading && }
); }, diff --git a/packages/ui/src/components/editor/nodeTypes.tsx b/packages/ui/src/components/editor/nodeTypes.tsx index 944d7c665..5d49cf2ff 100644 --- a/packages/ui/src/components/editor/nodeTypes.tsx +++ b/packages/ui/src/components/editor/nodeTypes.tsx @@ -10,8 +10,7 @@ const flatten = nodes => return acc; }, {}); -//These are all the nodes that are available in the editor -export const nodeTypes = { +const defaultNodes = { //Default ...nodeLookup, //Audio @@ -20,3 +19,6 @@ export const nodeTypes = { ...flatten(figmaNodes), ...flatten(previewNodes) }; + +//These are all the nodes that are available in the editor +export const nodeTypes = defaultNodes; diff --git a/packages/ui/src/components/editor/styles.module.css b/packages/ui/src/components/editor/styles.module.css new file mode 100644 index 000000000..84185f097 --- /dev/null +++ b/packages/ui/src/components/editor/styles.module.css @@ -0,0 +1,5 @@ +.container { + position: relative; + width: 100%; + height: 100% +} \ No newline at end of file diff --git a/packages/ui/src/components/editor/tabLoader.tsx b/packages/ui/src/components/editor/tabLoader.tsx new file mode 100644 index 000000000..36932140c --- /dev/null +++ b/packages/ui/src/components/editor/tabLoader.tsx @@ -0,0 +1,44 @@ +import { AISummary } from '../panels/aiSummary.tsx'; +import { DropPanelInner } from '@tokens-studio/graph-editor/index.js'; +import { ErrorBoundary } from 'react-error-boundary'; +import { MarketPlace } from '../panels/marketPlace.tsx'; +import { Preview } from '../panels/preview.tsx'; +import { TabBase, TabData } from 'rc-dock'; +import { previewItems } from './previewItem.tsx'; + +export const tabLoader = (tab: TabBase): TabData | undefined => { + switch (tab.id) { + case 'ai': + return { + ...tab, + title: 'Artificial intelligence', + content: + }; + case 'preview': + return { + ...tab, + title: 'Preview', + content: + }; + case 'marketplace': + return { + ...tab, + title: 'Marketplace', + content: + }; + case 'previewNodesPanel': + return { + group: 'popout', + title: 'Preview', + content: ( + Error
}> + + + ), + closable: true + }; + + default: + return undefined; + } +}; diff --git a/packages/ui/src/components/editor/toolbar.tsx b/packages/ui/src/components/editor/toolbar.tsx index 1bd0d24ea..c05a9acf5 100644 --- a/packages/ui/src/components/editor/toolbar.tsx +++ b/packages/ui/src/components/editor/toolbar.tsx @@ -14,9 +14,9 @@ import { useOpenPanel } from '@tokens-studio/graph-editor'; -import { AISummary } from './panels/aiSummary.tsx'; +import { AISummary } from '../panels/aiSummary.tsx'; import { IconButton, Tooltip } from '@tokens-studio/ui'; -import { Preview } from './panels/preview.tsx'; +import { Preview } from '../panels/preview.tsx'; import { ShareAndroidSolid } from 'iconoir-react'; import { SharePopover } from '../share/index.tsx'; import { client } from '@/api/sdk/index.ts'; @@ -125,7 +125,7 @@ export const AiSummary = () => { onClick={() => toggle({ group: 'popout', - title: Artificial intelligence, + title: 'Artificial intelligence', id: 'ai', content: }) @@ -145,7 +145,7 @@ export const PreviewButton = () => { onClick={() => toggle({ group: 'popout', - title: Preview, + title: 'Preview', id: 'preview', content: }) diff --git a/packages/ui/src/components/editor/panels/aiSummary.tsx b/packages/ui/src/components/panels/aiSummary.tsx similarity index 100% rename from packages/ui/src/components/editor/panels/aiSummary.tsx rename to packages/ui/src/components/panels/aiSummary.tsx diff --git a/packages/ui/src/components/panels/marketPlace.tsx b/packages/ui/src/components/panels/marketPlace.tsx new file mode 100644 index 000000000..dc9ae0199 --- /dev/null +++ b/packages/ui/src/components/panels/marketPlace.tsx @@ -0,0 +1,52 @@ +import { DragItem, PanelGroup } from '@tokens-studio/graph-editor'; +import { Spinner } from '@tokens-studio/ui/Spinner.js'; +import { Stack } from '@tokens-studio/ui/Stack.js'; +import { TextInput } from '@tokens-studio/ui/TextInput.js'; +import { client } from '@/api/sdk/index.ts'; +import DatabaseScript from '@tokens-studio/icons/DatabaseScript.js'; +import React, { useCallback } from 'react'; + +export const compoundNodesDrop = new PanelGroup({ + title: 'Compounds', + key: 'compounds', + icon: , + items: [ + { + type: 'data.compounds.smoothShadow', + text: 'Smooth Shadow', + description: 'A compound node that generates a smooth shadow effect', + docs: '' + } + ] +}); + +export const MarketPlace = () => { + const [search, setSearch] = React.useState(''); + + const { isLoading, data } = client.marketplace.searchGraphs.useQuery( + ['searchGraph', search], + { + query: { + name: search as string + } + } + ); + + const onChange = useCallback(e => setSearch(e.target.value), []); + + return ( + + + + {isLoading && } + {!isLoading && + data?.body.graphs.map(graph => ( + + ))} + + ); +}; diff --git a/packages/ui/src/components/editor/panels/preview.tsx b/packages/ui/src/components/panels/preview.tsx similarity index 100% rename from packages/ui/src/components/editor/panels/preview.tsx rename to packages/ui/src/components/panels/preview.tsx diff --git a/packages/ui/src/components/spinner/index.tsx b/packages/ui/src/components/spinner/index.tsx new file mode 100644 index 000000000..8b03406b6 --- /dev/null +++ b/packages/ui/src/components/spinner/index.tsx @@ -0,0 +1,8 @@ +import { Spinner } from '@tokens-studio/ui/Spinner.js'; +import styles from './styles.module.css'; + +export default () => ( +
+ +
+); diff --git a/packages/ui/src/components/spinner/styles.module.css b/packages/ui/src/components/spinner/styles.module.css new file mode 100644 index 000000000..64ce9c645 --- /dev/null +++ b/packages/ui/src/components/spinner/styles.module.css @@ -0,0 +1,13 @@ +.center { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: var(--color-neutral-canvas-minimal-bg); + opacity: 0.5; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; +} \ No newline at end of file diff --git a/packages/ui/src/data/compounds/index.tsx b/packages/ui/src/data/compounds/index.tsx new file mode 100644 index 000000000..055ad4c84 --- /dev/null +++ b/packages/ui/src/data/compounds/index.tsx @@ -0,0 +1,41 @@ +import { Graph } from '@tokens-studio/graph-engine'; +import { client } from '@/api/sdk/index.ts'; +import { nodeTypes } from '@/components/editor/nodeTypes.tsx'; +import Subgraph from '@tokens-studio/graph-engine/nodes/generic/subgraph.js'; + +export const loadCompounds = async (type: string) => { + if (type.startsWith('graph.')) { + const raw = await client.marketplace.retrieveGraph.query({ + params: { + id: type.split('.')[1] + } + }); + + const innergraph = await new Graph().deserialize( + raw.body.graph, + loadCompounds + ); + + return class CustomNode extends Subgraph { + static type = type; + constructor(data) { + super({ + ...data, + innergraph + }); + } + + override serialize() { + const serialized = super.serialize(); + // Don't store the innergraph + serialized.innergraph = undefined; + return serialized; + } + static override async deserialize(opts) { + return await super.deserialize({ ...opts, innergraph: raw.body.graph }); + } + }; + } else { + return nodeTypes[type]; + } +}; diff --git a/packages/ui/src/data/compounds/smoothShadow.json b/packages/ui/src/data/compounds/smoothShadow.json new file mode 100644 index 000000000..5b7b6f1dc --- /dev/null +++ b/packages/ui/src/data/compounds/smoothShadow.json @@ -0,0 +1,2801 @@ +{ + "nodes": [ + { + "id": "acc83d6c-bb42-45a0-9574-944fc6a18916", + "type": "studio.tokens.generic.input", + "inputs": [ + { + "name": "layers", + "value": 5, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "finalTransparency", + "value": 0.11, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "transparencyCurve", + "value": { + "controlPoints": [ + [ + [0.14219337573716578, 0.7274187304710118], + [0.81242438670312, 0.3426880696053026] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "type": { + "$id": "https://schemas.tokens.studio/floatCurve.json", + "title": "Float curve", + "type": "object", + "default": { + "controlPoints": [ + [ + [0.25, 0.25], + [0.75, 0.75] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "properties": { + "segments": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + }, + "controlPoints": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + } + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "horizontalDistance", + "value": 8, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "verticalDistance", + "value": 8, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "distanceCurve", + "value": { + "controlPoints": [ + [ + [0.17052797122027868, 0.6089303215977628], + [0.8822099930827179, 0.3961354099429192] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "type": { + "$id": "https://schemas.tokens.studio/floatCurve.json", + "title": "Float curve", + "type": "object", + "default": { + "controlPoints": [ + [ + [0.25, 0.25], + [0.75, 0.75] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "properties": { + "segments": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + }, + "controlPoints": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + } + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "blur", + "value": 20, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "blurCurve", + "value": { + "controlPoints": [ + [ + [0.8627544900645381, 0.07411938476562502], + [1, 0.05392041015624993] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "type": { + "$id": "https://schemas.tokens.studio/floatCurve.json", + "title": "Float curve", + "type": "object", + "default": { + "controlPoints": [ + [ + [0.25, 0.25], + [0.75, 0.75] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "properties": { + "segments": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + }, + "controlPoints": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + } + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "reduceSpread", + "value": 2, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "color", + "value": { + "space": "srgb", + "channels": [0.7215686274509804, 0, 0], + "alpha": 1 + }, + "type": { + "$id": "https://schemas.tokens.studio/color.json", + "title": "Color", + "type": "object", + "properties": { + "channels": { + "type": "array", + "items": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + "space": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + }, + "alpha": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + "default": { "channels": [0, 0, 0], "space": "srgb" }, + "required": ["channels", "space"] + }, + "visible": false, + "annotations": { "ui.deletable": true } + } + ], + "annotations": { + "engine.singleton": true, + "engine.dynamicInputs": true, + "xpos": 378.648681640625, + "ypos": 427.8787865953998, + "ui.position.x": 86.64868164062489, + "ui.position.y": 501.21211992873316 + } + }, + { + "id": "b5a484f5-1e3b-4a11-9530-680f6d8e0dde", + "type": "studio.tokens.series.linear", + "inputs": [ + { + "name": "start", + "value": 0, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "stop", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 1 + } + }, + { + "name": "length", + "value": 5, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 5 + } + }, + { + "name": "precision", + "value": 2, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 2 + } + } + ], + "annotations": { + "xpos": 575.9820149739584, + "ypos": 412.6666633143571, + "ui.position.x": 493.31534830729174, + "ui.position.y": 343.9999966476904 + } + }, + { + "id": "be3cbc58-94c8-4fd9-b3c6-fdc5e2ed1020", + "type": "tokens.studio.array.map", + "inputs": [ + { + "name": "finalTransparency", + "value": 0.11, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + { + "name": "transparencyCurve", + "value": { + "controlPoints": [ + [ + [0.14219337573716578, 0.7274187304710118], + [0.81242438670312, 0.3426880696053026] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "type": { + "$id": "https://schemas.tokens.studio/floatCurve.json", + "title": "Float curve", + "type": "object", + "default": { + "controlPoints": [ + [ + [0.25, 0.25], + [0.75, 0.75] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "properties": { + "segments": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + }, + "controlPoints": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + } + } + }, + { + "name": "horizontalDistance", + "value": 8, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + { + "name": "verticalDistance", + "value": 8, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + { + "name": "distanceCurve", + "value": { + "controlPoints": [ + [ + [0.17052797122027868, 0.6089303215977628], + [0.8822099930827179, 0.3961354099429192] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "type": { + "$id": "https://schemas.tokens.studio/floatCurve.json", + "title": "Float curve", + "type": "object", + "default": { + "controlPoints": [ + [ + [0.25, 0.25], + [0.75, 0.75] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "properties": { + "segments": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + }, + "controlPoints": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + } + } + }, + { + "name": "blur", + "value": 20, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + { + "name": "blurCurve", + "value": { + "controlPoints": [ + [ + [0.8627544900645381, 0.07411938476562502], + [1, 0.05392041015624993] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "type": { + "$id": "https://schemas.tokens.studio/floatCurve.json", + "title": "Float curve", + "type": "object", + "default": { + "controlPoints": [ + [ + [0.25, 0.25], + [0.75, 0.75] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "properties": { + "segments": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + }, + "controlPoints": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + } + } + }, + { + "name": "reduceSpread", + "value": 2, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + { + "name": "color", + "value": { + "space": "srgb", + "channels": [0.7215686274509804, 0, 0], + "alpha": 1 + }, + "type": { + "$id": "https://schemas.tokens.studio/color.json", + "title": "Color", + "type": "object", + "properties": { + "channels": { + "type": "array", + "items": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + "space": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + }, + "alpha": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + "default": { "channels": [0, 0, 0], "space": "srgb" }, + "required": ["channels", "space"] + } + }, + { + "name": "array", + "value": [0, 0.25, 0.5, 0.75, 1], + "type": { + "title": "Any[]", + "type": "array", + "items": { + "$id": "https://schemas.tokens.studio/any.json", + "title": "Any" + }, + "default": [] + }, + "dynamicType": { + "type": "array", + "items": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + "annotations": { "ui.editable": false } + } + ], + "annotations": { + "xpos": 741.3153483072916, + "ypos": 459.3333299810238, + "ui.position.x": 907.3153483072914, + "ui.position.y": 421.3333299810238 + }, + "innergraph": { + "nodes": [ + { + "id": "063f5476-ca80-4609-b2db-fde8e87be84f", + "type": "studio.tokens.generic.input", + "inputs": [ + { + "name": "value", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/any.json", + "title": "Any" + }, + "dynamicType": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + }, + "visible": false, + "annotations": { + "ui.editable": false, + "ui.hidden": true, + "engine.hideFromParentSubgraph": true + } + }, + { + "name": "index", + "value": 4, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + }, + "visible": false, + "annotations": { + "ui.editable": false, + "engine.hideFromParentSubgraph": true + } + }, + { + "name": "length", + "value": 5, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + }, + "visible": false, + "annotations": { + "ui.editable": false, + "engine.hideFromParentSubgraph": true + } + }, + { + "name": "finalTransparency", + "value": 0.11, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "transparencyCurve", + "value": { + "controlPoints": [ + [ + [0.14219337573716578, 0.7274187304710118], + [0.81242438670312, 0.3426880696053026] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "type": { + "$id": "https://schemas.tokens.studio/floatCurve.json", + "title": "Float curve", + "type": "object", + "default": { + "controlPoints": [ + [ + [0.25, 0.25], + [0.75, 0.75] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "properties": { + "segments": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + }, + "controlPoints": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + } + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "horizontalDistance", + "value": 8, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "verticalDistance", + "value": 8, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "distanceCurve", + "value": { + "controlPoints": [ + [ + [0.17052797122027868, 0.6089303215977628], + [0.8822099930827179, 0.3961354099429192] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "type": { + "$id": "https://schemas.tokens.studio/floatCurve.json", + "title": "Float curve", + "type": "object", + "default": { + "controlPoints": [ + [ + [0.25, 0.25], + [0.75, 0.75] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "properties": { + "segments": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + }, + "controlPoints": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + } + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "blur", + "value": 20, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "blurCurve", + "value": { + "controlPoints": [ + [ + [0.8627544900645381, 0.07411938476562502], + [1, 0.05392041015624993] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "type": { + "$id": "https://schemas.tokens.studio/floatCurve.json", + "title": "Float curve", + "type": "object", + "default": { + "controlPoints": [ + [ + [0.25, 0.25], + [0.75, 0.75] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "properties": { + "segments": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + }, + "controlPoints": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + } + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "reduceSpread", + "value": 2, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + }, + "visible": false, + "annotations": { "ui.deletable": true } + }, + { + "name": "color", + "value": { + "space": "srgb", + "channels": [0.7215686274509804, 0, 0], + "alpha": 1 + }, + "type": { + "$id": "https://schemas.tokens.studio/color.json", + "title": "Color", + "type": "object", + "properties": { + "channels": { + "type": "array", + "items": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + "space": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + }, + "alpha": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + "default": { "channels": [0, 0, 0], "space": "srgb" }, + "required": ["channels", "space"] + }, + "visible": false, + "annotations": { "ui.deletable": true } + } + ], + "annotations": { + "engine.singleton": true, + "engine.dynamicInputs": true, + "engine.deletable": false, + "ui.position.x": -220.66666666666669, + "ui.position.y": 41.33333333333334 + } + }, + { + "id": "74e08d59-feb7-42a1-987e-4c580747f725", + "type": "studio.tokens.generic.output", + "inputs": [ + { + "name": "value", + "value": { + "x": "8", + "y": "0.1", + "blur": "20", + "spread": "-2", + "color": "hsl(0 100% 36.078% / 0.11)", + "type": "dropShadow" + }, + "type": { + "type": "array", + "items": { + "$id": "https://schemas.tokens.studio/any.json", + "title": "Any" + } + }, + "dynamicType": { + "$id": "https://schemas.tokens.studio/tokenBoxShadow.json", + "title": "Box Shadow Token", + "type": "object", + "properties": { + "x": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + }, + "y": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + }, + "blur": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + }, + "spread": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + }, + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + } + } + }, + "annotations": { "ui.editable": false } + } + ], + "annotations": { + "engine.singleton": true, + "engine.deletable": false, + "ui.position.x": 3221.5582649975045, + "ui.position.y": 187.97309108029617 + } + }, + { + "id": "5487309d-011e-4586-96bd-4e9f4846b0ef", + "type": "studio.tokens.curve.sampleFloat", + "inputs": [ + { + "name": "curve", + "value": { + "controlPoints": [ + [ + [0.14219337573716578, 0.7274187304710118], + [0.81242438670312, 0.3426880696053026] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "type": { + "$id": "https://schemas.tokens.studio/floatCurve.json", + "title": "Float curve", + "type": "object", + "default": { + "controlPoints": [ + [ + [0.25, 0.25], + [0.75, 0.75] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "properties": { + "segments": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + }, + "controlPoints": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + } + } + }, + { + "name": "x", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "minimum": 0, + "maximum": 1, + "default": 0 + } + } + ], + "annotations": { + "xpos": 442.648681640625, + "ypos": -11.999998728434244, + "ui.position.x": 589.1610428070996, + "ui.position.y": -278.4090119107178 + } + }, + { + "id": "2f75505a-951e-4ed0-b312-5a1a3c579fc3", + "type": "studio.tokens.curve.sampleFloat", + "inputs": [ + { + "name": "curve", + "value": { + "controlPoints": [ + [ + [0.17052797122027868, 0.6089303215977628], + [0.8822099930827179, 0.3961354099429192] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "type": { + "$id": "https://schemas.tokens.studio/floatCurve.json", + "title": "Float curve", + "type": "object", + "default": { + "controlPoints": [ + [ + [0.25, 0.25], + [0.75, 0.75] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "properties": { + "segments": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + }, + "controlPoints": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + } + } + }, + { + "name": "x", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "minimum": 0, + "maximum": 1, + "default": 0 + } + } + ], + "annotations": { + "xpos": 527.3153483072916, + "ypos": 247.33333460489908, + "ui.position.x": 567.3153483072915, + "ui.position.y": 200.6666679382324 + } + }, + { + "id": "b52df2df-ea97-4734-8300-b2c7d202c6f6", + "type": "studio.tokens.curve.sampleFloat", + "inputs": [ + { + "name": "curve", + "value": { + "controlPoints": [ + [ + [0.8627544900645381, 0.07411938476562502], + [1, 0.05392041015624993] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "type": { + "$id": "https://schemas.tokens.studio/floatCurve.json", + "title": "Float curve", + "type": "object", + "default": { + "controlPoints": [ + [ + [0.25, 0.25], + [0.75, 0.75] + ] + ], + "segments": [ + [0, 0], + [1, 1] + ] + }, + "properties": { + "segments": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + }, + "controlPoints": { + "type": "array", + "items": { + "type": "array", + "items": [{ "type": "number" }, { "type": "number" }] + } + } + } + } + }, + { + "name": "x", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "minimum": 0, + "maximum": 1, + "default": 0 + } + } + ], + "annotations": { + "xpos": 553.3153483072916, + "ypos": 472.00000127156574, + "ui.position.x": 557.8268444628663, + "ui.position.y": 514.8390550935214 + } + }, + { + "id": "2dda05f1-037a-460e-ad6f-5da38201df00", + "type": "studio.tokens.generic.passthrough", + "inputs": [ + { + "name": "value", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/any.json", + "title": "Any" + }, + "dynamicType": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + } + ], + "annotations": { + "ui.nodeType": "studio.tokens.generic.passthrough", + "ui.position.x": 291.9820149739583, + "ui.position.y": 95.3333346048991 + } + }, + { + "id": "4308b66f-87fc-4042-a041-5e466c9c64b6", + "type": "studio.tokens.math.dataMapping", + "inputs": [ + { + "name": "inputValue", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 5 + } + }, + { + "name": "inputMin", + "value": 0, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "inputMax", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 100 + } + }, + { + "name": "outputMin", + "value": 0, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "outputMax", + "value": 0.11, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 1 + } + }, + { + "name": "clamp", + "value": true, + "type": { + "$id": "https://schemas.tokens.studio/boolean.json", + "title": "Boolean", + "type": "boolean", + "default": true + } + } + ], + "annotations": { + "xpos": 947.3153483072916, + "ypos": 8.000001271565756, + "ui.position.x": 1057.4710371782862, + "ui.position.y": -314.50738288261266 + } + }, + { + "id": "c16a6788-d13d-4eae-9178-217100d218ab", + "type": "studio.tokens.math.dataMapping", + "inputs": [ + { + "name": "inputValue", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 5 + } + }, + { + "name": "inputMin", + "value": 0, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "inputMax", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 100 + } + }, + { + "name": "outputMin", + "value": 0, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "outputMax", + "value": 8, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 1 + } + }, + { + "name": "clamp", + "value": true, + "type": { + "$id": "https://schemas.tokens.studio/boolean.json", + "title": "Boolean", + "type": "boolean", + "default": true + } + } + ], + "annotations": { + "xpos": 1135.6379758865478, + "ypos": 166.62821458505226, + "ui.position.x": 1058.4885810424078, + "ui.position.y": -15.396138875340455 + } + }, + { + "id": "b026c32c-e9ba-46c9-829d-077d56125962", + "type": "studio.tokens.math.dataMapping", + "inputs": [ + { + "name": "inputValue", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 5 + } + }, + { + "name": "inputMin", + "value": 0, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "inputMax", + "value": 100, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 100 + } + }, + { + "name": "outputMin", + "value": 0, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "outputMax", + "value": 8, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 1 + } + }, + { + "name": "clamp", + "value": true, + "type": { + "$id": "https://schemas.tokens.studio/boolean.json", + "title": "Boolean", + "type": "boolean", + "default": true + } + } + ], + "annotations": { + "xpos": 1185.0618069585748, + "ypos": 367.93991675647993, + "ui.position.x": 1056.0776624535285, + "ui.position.y": 294.40689979565906 + } + }, + { + "id": "f0b19249-c392-45cc-b3c4-2345ed22948d", + "type": "studio.tokens.math.dataMapping", + "inputs": [ + { + "name": "inputValue", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 5 + } + }, + { + "name": "inputMin", + "value": 0, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "inputMax", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 100 + } + }, + { + "name": "outputMin", + "value": 0, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "outputMax", + "value": 20, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 1 + } + }, + { + "name": "clamp", + "value": true, + "type": { + "$id": "https://schemas.tokens.studio/boolean.json", + "title": "Boolean", + "type": "boolean", + "default": true + } + } + ], + "annotations": { + "xpos": 1154.9253245975829, + "ypos": 596.9771827000204, + "ui.position.x": 1056.0776624535285, + "ui.position.y": 598.1826419944601 + } + }, + { + "id": "764fd8d6-0206-4bd1-92c0-f520393d55a3", + "type": "studio.tokens.math.lerp", + "inputs": [ + { + "name": "a", + "value": 0, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "b", + "value": -2, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "t", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + } + ], + "annotations": { + "xpos": 1071.7486332812443, + "ypos": 967.0531860930042, + "ui.position.x": 1053.666743864649, + "ui.position.y": 962.2313489152456 + } + }, + { + "id": "3f17b8b8-9eaf-4fc7-aba4-87a372bc8068", + "type": "studio.tokens.math.multiply", + "inputs": [ + { + "name": "a", + "value": 2, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + { + "name": "b", + "value": -1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + } + ], + "annotations": { + "xpos": 617.2904792774825, + "ypos": 917.6293550209771, + "ui.position.x": 617.2904792774825, + "ui.position.y": 917.6293550209771 + } + }, + { + "id": "b8f3b6ec-f48d-4e3d-b413-ed9df98906bd", + "type": "studio.tokens.design.createBoxShadow", + "inputs": [ + { + "name": "x", + "value": "8", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + } + }, + { + "name": "y", + "value": "0.1", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + } + }, + { + "name": "blur", + "value": "20", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + } + }, + { + "name": "spread", + "value": "-2", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + } + }, + { + "name": "color", + "value": "hsl(0 100% 36.078% / 0.11)", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + } + }, + { + "name": "type", + "value": "dropShadow", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + } + } + ], + "annotations": { + "xpos": 1978.2540226998892, + "ypos": 226.9011793070364, + "ui.position.x": 2787.1172092689194, + "ui.position.y": 182.29918541276794 + } + }, + { + "id": "02a399e7-eea0-4cc0-b868-88fb2d3d6036", + "type": "studio.tokens.color.colorToString", + "inputs": [ + { + "name": "color", + "value": { + "space": "srgb", + "channels": [0.7215686274509804, 0, 0], + "alpha": 0.11 + }, + "type": { + "$id": "https://schemas.tokens.studio/color.json", + "title": "Color", + "type": "object", + "properties": { + "channels": { + "type": "array", + "items": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + "space": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + }, + "alpha": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + "default": { "space": "srgb", "channels": [0, 0, 0] }, + "required": ["channels", "space"] + } + }, + { + "name": "space", + "value": "hsl", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string", + "enum": [ + "hex", + "a98rgb", + "a98rgb-linear", + "acescc", + "acescg", + "cam16-jmh", + "hct", + "hpluv", + "hsl", + "hsluv", + "hsv", + "hwb", + "ictcp", + "jzczhz", + "jzazbz", + "lch", + "lchuv", + "lab", + "lab-d65", + "luv", + "oklch", + "oklab", + "p3", + "p3-linear", + "prophoto", + "prophoto-linear", + "rec2020", + "rec2020-linear", + "rec2100hlg", + "rec2100pq", + "xyz-abs-d65", + "xyz-d50", + "xyz-d65", + "xyz", + "srgb", + "srgb-linear" + ], + "default": "hex" + }, + "visible": false + } + ], + "annotations": { + "xpos": 1903.5155464446286, + "ypos": 612.6481535277361, + "ui.position.x": 2056.6088768384693, + "ui.position.y": -439.71781051811047 + } + }, + { + "id": "ac97665c-d4cf-475d-9f56-1ac096223ba2", + "type": "studio.tokens.string.stringify", + "inputs": [ + { + "name": "value", + "value": 8, + "type": { + "$id": "https://schemas.tokens.studio/any.json", + "title": "Any" + }, + "dynamicType": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + } + ], + "annotations": { + "xpos": 1605.7671007180259, + "ypos": 76.21876750207554, + "ui.position.x": 1643.1363388456564, + "ui.position.y": -14.190679580901019 + } + }, + { + "id": "67e3e464-063c-4e39-b379-d83b654ad69e", + "type": "studio.tokens.string.stringify", + "inputs": [ + { + "name": "value", + "value": 0.1, + "type": { + "$id": "https://schemas.tokens.studio/any.json", + "title": "Any" + }, + "dynamicType": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + } + ], + "annotations": { + "xpos": 1553.9323510571194, + "ypos": 349.8580273398844, + "ui.position.x": 1651.5745539067339, + "ui.position.y": 314.89970780113356 + } + }, + { + "id": "6724cc7f-c073-40ea-83dc-b1387b03b410", + "type": "studio.tokens.typing.passUnit", + "inputs": [ + { + "name": "value", + "value": "8", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + } + }, + { + "name": "fallback", + "value": "px", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string", + "default": "px" + } + } + ], + "annotations": { + "xpos": 1811.9006400672124, + "ypos": 7.507587719013392, + "ui.position.x": 1967.404889049932, + "ui.position.y": -17.807057464220065 + } + }, + { + "id": "e9d1ba3b-25d9-4a62-bc5d-56b4acdf155b", + "type": "studio.tokens.typing.passUnit", + "inputs": [ + { + "name": "value", + "value": "0.1", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + } + }, + { + "name": "fallback", + "value": "px", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string", + "default": "px" + } + } + ], + "annotations": { + "xpos": 1809.489721478333, + "ypos": 469.1984974894134, + "ui.position.x": 1985.4867784665273, + "ui.position.y": 289.5850626179 + } + }, + { + "id": "b91d221e-d577-48c5-a918-99192a6d1f3d", + "type": "studio.tokens.color.create", + "inputs": [ + { + "name": "space", + "value": "srgb", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string", + "enum": [ + "a98rgb", + "a98rgb-linear", + "acescc", + "acescg", + "cam16-jmh", + "hct", + "hpluv", + "hsl", + "hsluv", + "hsv", + "hwb", + "ictcp", + "jzczhz", + "jzazbz", + "lch", + "lchuv", + "lab", + "lab-d65", + "luv", + "oklch", + "oklab", + "p3", + "p3-linear", + "prophoto", + "prophoto-linear", + "rec2020", + "rec2020-linear", + "rec2100hlg", + "rec2100pq", + "xyz-abs-d65", + "xyz-d50", + "xyz-d65", + "xyz", + "srgb", + "srgb-linear" + ], + "default": "srgb" + } + }, + { + "name": "a", + "value": 0.7215686274509804, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": "0" + } + }, + { + "name": "b", + "value": 0, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": "0" + } + }, + { + "name": "c", + "value": 0, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": "0" + } + }, + { + "name": "alpha", + "value": 0.11, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + } + ], + "annotations": { + "xpos": 2032.499690949675, + "ypos": -344.48652625737526, + "ui.position.x": 1682.9164955621657, + "ui.position.y": -660.3168614005731 + } + }, + { + "id": "97968a5a-3db3-473b-b190-d95de246a79f", + "type": "studio.tokens.color.deconstruct", + "inputs": [ + { + "name": "color", + "value": { + "space": "srgb", + "channels": [0.7215686274509804, 0, 0], + "alpha": 1 + }, + "type": { + "$id": "https://schemas.tokens.studio/color.json", + "title": "Color", + "type": "object", + "properties": { + "channels": { + "type": "array", + "items": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + "space": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + }, + "alpha": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + }, + "default": { "channels": [0, 0, 0], "space": "srgb" }, + "required": ["channels", "space"] + } + } + ], + "annotations": { + "xpos": 712.521763538218, + "ypos": -526.510879717768, + "ui.position.x": 589.56491550537, + "ui.position.y": -631.3858383340207 + } + }, + { + "id": "b5f61ef4-62c6-4c60-ba2f-e7fdfcbae835", + "type": "studio.tokens.string.stringify", + "inputs": [ + { + "name": "value", + "value": 20, + "type": { + "$id": "https://schemas.tokens.studio/any.json", + "title": "Any" + }, + "dynamicType": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + } + ], + "annotations": { + "xpos": 1475.5774969185397, + "ypos": 662.0719845997631, + "ui.position.x": 1645.5472574345356, + "ui.position.y": 596.9771827000201 + } + }, + { + "id": "c40c042e-a542-4bb5-b577-80d2d447db4e", + "type": "studio.tokens.string.stringify", + "inputs": [ + { + "name": "value", + "value": -2, + "type": { + "$id": "https://schemas.tokens.studio/any.json", + "title": "Any" + }, + "dynamicType": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number" + } + } + ], + "annotations": { + "xpos": 1535.8504616405241, + "ypos": 980.3132383318405, + "ui.position.x": 1658.8073096733722, + "ui.position.y": 944.1494594986499 + } + }, + { + "id": "a8f473f0-b522-4950-99e6-10774353eb1c", + "type": "studio.tokens.typing.passUnit", + "inputs": [ + { + "name": "value", + "value": "20", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + } + }, + { + "name": "fallback", + "value": "px", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string", + "default": "px" + } + } + ], + "annotations": { + "xpos": 1829.9825294838076, + "ypos": 640.3737172998489, + "ui.position.x": 1984.2813191720877, + "ui.position.y": 594.5662641111408 + } + }, + { + "id": "3885e04e-89ce-4a1e-8096-d7e1b7b8c264", + "type": "studio.tokens.typing.passUnit", + "inputs": [ + { + "name": "value", + "value": "-2", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + } + }, + { + "name": "fallback", + "value": "px", + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string", + "default": "px" + } + } + ], + "annotations": { + "xpos": 1840.8316631337648, + "ypos": 980.3132383318405, + "ui.position.x": 1996.3359121164845, + "ui.position.y": 947.7658373819689 + } + }, + { + "id": "6310ca41-409c-41f4-baaf-cb0374025842", + "type": "studio.tokens.math.round", + "inputs": [ + { + "name": "value", + "value": 8, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "precision", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + } + ], + "annotations": { + "xpos": 1397.2226427799599, + "ypos": 67.7805524409976, + "ui.position.x": 1340.5660559412947, + "ui.position.y": -11.779760992021721 + } + }, + { + "id": "6130d9f4-adad-4424-8878-f425dba0173b", + "type": "studio.tokens.math.round", + "inputs": [ + { + "name": "value", + "value": 0.08, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "precision", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + } + ], + "annotations": { + "xpos": 1443.0300959686679, + "ypos": 339.0088936899271, + "ui.position.x": 1351.4151895912519, + "ui.position.y": 318.51608568445243 + } + }, + { + "id": "913ef632-19d9-493c-b1be-10e77f19cd56", + "type": "studio.tokens.math.round", + "inputs": [ + { + "name": "value", + "value": 20, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "precision", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + } + ], + "annotations": { + "xpos": 1421.3318286687536, + "ypos": 628.3191243554519, + "ui.position.x": 1345.3878931190532, + "ui.position.y": 595.7717234055802 + } + }, + { + "id": "176f4789-42fb-4ba8-831a-7d49aedab6cf", + "type": "studio.tokens.math.round", + "inputs": [ + { + "name": "value", + "value": -2, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "precision", + "value": 1, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + } + ], + "annotations": { + "xpos": 1417.7154507854345, + "ypos": 958.6149710319261, + "ui.position.x": 1367.0861604189677, + "ui.position.y": 945.3549187930895 + } + }, + { + "id": "0a45843b-26f8-4466-8f1c-52d199088d97", + "type": "studio.tokens.math.round", + "inputs": [ + { + "name": "value", + "value": 0.11, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + }, + { + "name": "precision", + "value": 2, + "type": { + "$id": "https://schemas.tokens.studio/number.json", + "title": "Number", + "type": "number", + "default": 0 + } + } + ], + "annotations": { + "xpos": 1430.9755030242711, + "ypos": -369.8011714406087, + "ui.position.x": 1381.551671952244, + "ui.position.y": -405.96495027379933 + } + } + ], + "edges": [ + { + "id": "c84a3c31-80f9-438b-9a69-809b625537c7", + "source": "063f5476-ca80-4609-b2db-fde8e87be84f", + "sourceHandle": "value", + "target": "2dda05f1-037a-460e-ad6f-5da38201df00", + "targetHandle": "value" + }, + { + "id": "72a6c104-126c-4e18-a0d9-4ee5fe8ff412", + "source": "2dda05f1-037a-460e-ad6f-5da38201df00", + "sourceHandle": "value", + "target": "5487309d-011e-4586-96bd-4e9f4846b0ef", + "targetHandle": "x" + }, + { + "id": "954f11c5-9294-4e00-aa5a-f79fc874e0d0", + "source": "2dda05f1-037a-460e-ad6f-5da38201df00", + "sourceHandle": "value", + "target": "2f75505a-951e-4ed0-b312-5a1a3c579fc3", + "targetHandle": "x" + }, + { + "id": "8214be91-ea06-44fb-b781-703fff416262", + "source": "2dda05f1-037a-460e-ad6f-5da38201df00", + "sourceHandle": "value", + "target": "b52df2df-ea97-4734-8300-b2c7d202c6f6", + "targetHandle": "x" + }, + { + "id": "5a8a3879-5040-413c-8bb2-f8e120a6b09e", + "source": "063f5476-ca80-4609-b2db-fde8e87be84f", + "sourceHandle": "transparencyCurve", + "target": "5487309d-011e-4586-96bd-4e9f4846b0ef", + "targetHandle": "curve" + }, + { + "id": "d4e43d6b-b078-4234-a1e9-0ff0581013f2", + "source": "063f5476-ca80-4609-b2db-fde8e87be84f", + "sourceHandle": "distanceCurve", + "target": "2f75505a-951e-4ed0-b312-5a1a3c579fc3", + "targetHandle": "curve" + }, + { + "id": "1cc948a2-e811-4863-9f37-539b1d1763eb", + "source": "063f5476-ca80-4609-b2db-fde8e87be84f", + "sourceHandle": "blurCurve", + "target": "b52df2df-ea97-4734-8300-b2c7d202c6f6", + "targetHandle": "curve" + }, + { + "id": "0d63d070-27c9-4656-a704-745323cfa36b", + "source": "5487309d-011e-4586-96bd-4e9f4846b0ef", + "sourceHandle": "y", + "target": "4308b66f-87fc-4042-a041-5e466c9c64b6", + "targetHandle": "inputValue" + }, + { + "id": "4f61a0dc-735e-4042-8b76-aec614f12d72", + "source": "063f5476-ca80-4609-b2db-fde8e87be84f", + "sourceHandle": "finalTransparency", + "target": "4308b66f-87fc-4042-a041-5e466c9c64b6", + "targetHandle": "outputMax" + }, + { + "id": "d21751f5-9671-4202-be45-1ae0183a210f", + "source": "2f75505a-951e-4ed0-b312-5a1a3c579fc3", + "sourceHandle": "y", + "target": "c16a6788-d13d-4eae-9178-217100d218ab", + "targetHandle": "inputValue" + }, + { + "id": "91c131df-4912-4c57-a1eb-243598af67aa", + "source": "063f5476-ca80-4609-b2db-fde8e87be84f", + "sourceHandle": "horizontalDistance", + "target": "c16a6788-d13d-4eae-9178-217100d218ab", + "targetHandle": "outputMax" + }, + { + "id": "678dc4a8-2938-40e7-b3c6-e04962d14e13", + "source": "2f75505a-951e-4ed0-b312-5a1a3c579fc3", + "sourceHandle": "y", + "target": "b026c32c-e9ba-46c9-829d-077d56125962", + "targetHandle": "inputValue" + }, + { + "id": "290dd078-24e7-4ef7-bf94-9f6058a055a2", + "source": "063f5476-ca80-4609-b2db-fde8e87be84f", + "sourceHandle": "verticalDistance", + "target": "b026c32c-e9ba-46c9-829d-077d56125962", + "targetHandle": "outputMax" + }, + { + "id": "a3eb7be2-f343-4a38-b963-be17cbcb3429", + "source": "063f5476-ca80-4609-b2db-fde8e87be84f", + "sourceHandle": "blur", + "target": "f0b19249-c392-45cc-b3c4-2345ed22948d", + "targetHandle": "outputMax" + }, + { + "id": "9c868e49-d1b2-4fd8-a486-8d865efc7f16", + "source": "b52df2df-ea97-4734-8300-b2c7d202c6f6", + "sourceHandle": "y", + "target": "f0b19249-c392-45cc-b3c4-2345ed22948d", + "targetHandle": "inputValue" + }, + { + "id": "f762ae20-2936-4ed9-8476-d6291b2f64a0", + "source": "063f5476-ca80-4609-b2db-fde8e87be84f", + "sourceHandle": "value", + "target": "764fd8d6-0206-4bd1-92c0-f520393d55a3", + "targetHandle": "t" + }, + { + "id": "1e379a71-98e5-4202-955b-b15715d86e0a", + "source": "063f5476-ca80-4609-b2db-fde8e87be84f", + "sourceHandle": "reduceSpread", + "target": "3f17b8b8-9eaf-4fc7-aba4-87a372bc8068", + "targetHandle": "a" + }, + { + "id": "a7511bdd-7e2b-4b84-8ad9-1b90922a4056", + "source": "3f17b8b8-9eaf-4fc7-aba4-87a372bc8068", + "sourceHandle": "value", + "target": "764fd8d6-0206-4bd1-92c0-f520393d55a3", + "targetHandle": "b" + }, + { + "id": "b8c17ef4-da5f-4061-a0bb-82a530503f11", + "source": "02a399e7-eea0-4cc0-b868-88fb2d3d6036", + "sourceHandle": "value", + "target": "b8f3b6ec-f48d-4e3d-b413-ed9df98906bd", + "targetHandle": "color" + }, + { + "id": "9ff8cdf9-9507-4268-b691-24e4b4c635c8", + "source": "ac97665c-d4cf-475d-9f56-1ac096223ba2", + "sourceHandle": "value", + "target": "6724cc7f-c073-40ea-83dc-b1387b03b410", + "targetHandle": "value" + }, + { + "id": "46cdd19d-3501-42c3-b250-043627734554", + "source": "67e3e464-063c-4e39-b379-d83b654ad69e", + "sourceHandle": "value", + "target": "e9d1ba3b-25d9-4a62-bc5d-56b4acdf155b", + "targetHandle": "value" + }, + { + "id": "1ffd8770-ace9-4bb5-804c-0485f6d120fa", + "source": "063f5476-ca80-4609-b2db-fde8e87be84f", + "sourceHandle": "color", + "target": "97968a5a-3db3-473b-b190-d95de246a79f", + "targetHandle": "color" + }, + { + "id": "1ce2e5d0-5c53-4370-a7c5-b2f0eb39360a", + "source": "97968a5a-3db3-473b-b190-d95de246a79f", + "sourceHandle": "space", + "target": "b91d221e-d577-48c5-a918-99192a6d1f3d", + "targetHandle": "space" + }, + { + "id": "30c049f2-cebb-4956-885e-e17459745484", + "source": "97968a5a-3db3-473b-b190-d95de246a79f", + "sourceHandle": "a", + "target": "b91d221e-d577-48c5-a918-99192a6d1f3d", + "targetHandle": "a" + }, + { + "id": "8e924bb1-b7b6-43e1-9970-3134f9f5f22a", + "source": "97968a5a-3db3-473b-b190-d95de246a79f", + "sourceHandle": "b", + "target": "b91d221e-d577-48c5-a918-99192a6d1f3d", + "targetHandle": "b" + }, + { + "id": "3ad06e62-7966-4116-99a1-bdd9248af934", + "source": "97968a5a-3db3-473b-b190-d95de246a79f", + "sourceHandle": "c", + "target": "b91d221e-d577-48c5-a918-99192a6d1f3d", + "targetHandle": "c" + }, + { + "id": "de168b8f-af97-486b-b648-9da1542ab857", + "source": "b5f61ef4-62c6-4c60-ba2f-e7fdfcbae835", + "sourceHandle": "value", + "target": "a8f473f0-b522-4950-99e6-10774353eb1c", + "targetHandle": "value" + }, + { + "id": "f26504e4-5a77-493a-878a-51b303b69c86", + "source": "c40c042e-a542-4bb5-b577-80d2d447db4e", + "sourceHandle": "value", + "target": "3885e04e-89ce-4a1e-8096-d7e1b7b8c264", + "targetHandle": "value" + }, + { + "id": "e0bafaa0-e425-494a-81c5-c8af459beab1", + "source": "b8f3b6ec-f48d-4e3d-b413-ed9df98906bd", + "sourceHandle": "value", + "target": "74e08d59-feb7-42a1-987e-4c580747f725", + "targetHandle": "value" + }, + { + "id": "8a5b8efc-e9df-48e9-9c43-719e9c201454", + "source": "c16a6788-d13d-4eae-9178-217100d218ab", + "sourceHandle": "mappedValue", + "target": "6310ca41-409c-41f4-baaf-cb0374025842", + "targetHandle": "value" + }, + { + "id": "c32a0856-9113-4e56-a755-3e516d01f77c", + "source": "6310ca41-409c-41f4-baaf-cb0374025842", + "sourceHandle": "value", + "target": "ac97665c-d4cf-475d-9f56-1ac096223ba2", + "targetHandle": "value" + }, + { + "id": "b31cb2f6-9698-4d3b-8e63-bb16d01ba438", + "source": "b026c32c-e9ba-46c9-829d-077d56125962", + "sourceHandle": "mappedValue", + "target": "6130d9f4-adad-4424-8878-f425dba0173b", + "targetHandle": "value" + }, + { + "id": "5eedb3c9-d3a1-4bce-ab4f-60502bbb6bcf", + "source": "6130d9f4-adad-4424-8878-f425dba0173b", + "sourceHandle": "value", + "target": "67e3e464-063c-4e39-b379-d83b654ad69e", + "targetHandle": "value" + }, + { + "id": "6c2f12a2-c7bf-414c-8c41-c0e239642031", + "source": "f0b19249-c392-45cc-b3c4-2345ed22948d", + "sourceHandle": "mappedValue", + "target": "913ef632-19d9-493c-b1be-10e77f19cd56", + "targetHandle": "value" + }, + { + "id": "90e7715e-aae1-4848-8cf3-28694b1cb906", + "source": "913ef632-19d9-493c-b1be-10e77f19cd56", + "sourceHandle": "value", + "target": "b5f61ef4-62c6-4c60-ba2f-e7fdfcbae835", + "targetHandle": "value" + }, + { + "id": "a4f0cad9-2d55-4af1-b20c-b0806ca5e050", + "source": "764fd8d6-0206-4bd1-92c0-f520393d55a3", + "sourceHandle": "value", + "target": "176f4789-42fb-4ba8-831a-7d49aedab6cf", + "targetHandle": "value" + }, + { + "id": "14119e35-e93e-4936-9188-d310c344f78d", + "source": "176f4789-42fb-4ba8-831a-7d49aedab6cf", + "sourceHandle": "value", + "target": "c40c042e-a542-4bb5-b577-80d2d447db4e", + "targetHandle": "value" + }, + { + "id": "698b0c92-8f69-41c6-9677-61bede4d8ac5", + "source": "ac97665c-d4cf-475d-9f56-1ac096223ba2", + "sourceHandle": "value", + "target": "b8f3b6ec-f48d-4e3d-b413-ed9df98906bd", + "targetHandle": "x" + }, + { + "id": "925fb64c-d549-499b-ad09-84983187064c", + "source": "67e3e464-063c-4e39-b379-d83b654ad69e", + "sourceHandle": "value", + "target": "b8f3b6ec-f48d-4e3d-b413-ed9df98906bd", + "targetHandle": "y" + }, + { + "id": "78d0cd7f-70ef-4446-bd13-20581dcb65a8", + "source": "b5f61ef4-62c6-4c60-ba2f-e7fdfcbae835", + "sourceHandle": "value", + "target": "b8f3b6ec-f48d-4e3d-b413-ed9df98906bd", + "targetHandle": "blur" + }, + { + "id": "844cbfc7-b8a2-47c6-a799-38d951dc0257", + "source": "c40c042e-a542-4bb5-b577-80d2d447db4e", + "sourceHandle": "value", + "target": "b8f3b6ec-f48d-4e3d-b413-ed9df98906bd", + "targetHandle": "spread" + }, + { + "id": "625c5dd1-82a2-41ba-b680-2257351d5380", + "source": "4308b66f-87fc-4042-a041-5e466c9c64b6", + "sourceHandle": "mappedValue", + "target": "0a45843b-26f8-4466-8f1c-52d199088d97", + "targetHandle": "value" + }, + { + "id": "245bf699-de9f-458a-acea-bd89022545c8", + "source": "0a45843b-26f8-4466-8f1c-52d199088d97", + "sourceHandle": "value", + "target": "b91d221e-d577-48c5-a918-99192a6d1f3d", + "targetHandle": "alpha" + }, + { + "id": "ada57503-6af2-4547-acfc-27a5c211300b", + "source": "b91d221e-d577-48c5-a918-99192a6d1f3d", + "sourceHandle": "value", + "target": "02a399e7-eea0-4cc0-b868-88fb2d3d6036", + "targetHandle": "color" + } + ], + "annotations": { + "engine.id": "cb60a496-0d56-403c-bdc4-775d270b0792", + "ui.viewports": [ + null, + null, + null, + null, + null, + null, + null, + null, + null + ], + "engine.capabilities.web-audio": "0.0.0", + "engine.capabilities.fs": "0.0.0", + "engine.version": "0.12.0" + } + } + }, + { + "id": "94647d04-0cbe-4c7d-9316-93fecb8d9efa", + "type": "studio.tokens.generic.output", + "inputs": [ + { + "name": "tokens", + "value": [ + { + "x": "0", + "y": "0", + "blur": "0", + "spread": "0", + "color": "hsl(0 100% 36.078% / 0)", + "type": "dropShadow" + }, + { + "x": "2.6", + "y": "0", + "blur": "1.1", + "spread": "-0.5", + "color": "hsl(0 100% 36.078% / 0.04)", + "type": "dropShadow" + }, + { + "x": "4", + "y": "0", + "blur": "3.5", + "spread": "-1", + "color": "hsl(0 100% 36.078% / 0.06)", + "type": "dropShadow" + }, + { + "x": "5.4", + "y": "0.1", + "blur": "9.1", + "spread": "-1.5", + "color": "hsl(0 100% 36.078% / 0.07)", + "type": "dropShadow" + }, + { + "x": "8", + "y": "0.1", + "blur": "20", + "spread": "-2", + "color": "hsl(0 100% 36.078% / 0.11)", + "type": "dropShadow" + } + ], + "type": { + "$id": "https://schemas.tokens.studio/any.json", + "title": "Any" + }, + "dynamicType": { + "title": "Box Shadow Token", + "type": "array", + "items": { + "$id": "https://schemas.tokens.studio/tokenBoxShadow.json", + "title": "Box Shadow Token", + "type": "object", + "properties": { + "x": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + }, + "y": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + }, + "blur": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + }, + "spread": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + }, + "type": { + "$id": "https://schemas.tokens.studio/string.json", + "title": "String", + "type": "string" + } + } + } + }, + "annotations": { "ui.deletable": true } + } + ], + "annotations": { + "engine.singleton": true, + "engine.dynamicInputs": true, + "xpos": 2583.2903973414855, + "ypos": 565.3989406064055, + "ui.position.x": 1350.6084987559905, + "ui.position.y": 432.4138203887596 + } + } + ], + "edges": [ + { + "id": "f7f01cd1-bfb2-451f-bdb5-bd76a3c7a799", + "source": "acc83d6c-bb42-45a0-9574-944fc6a18916", + "sourceHandle": "layers", + "target": "b5a484f5-1e3b-4a11-9530-680f6d8e0dde", + "targetHandle": "length" + }, + { + "id": "b7b37ddb-7231-489c-8107-64a2ee6b73b4", + "source": "b5a484f5-1e3b-4a11-9530-680f6d8e0dde", + "sourceHandle": "array", + "target": "be3cbc58-94c8-4fd9-b3c6-fdc5e2ed1020", + "targetHandle": "array" + }, + { + "id": "80a33ddd-8957-406f-8f04-083e1a84777d", + "source": "acc83d6c-bb42-45a0-9574-944fc6a18916", + "sourceHandle": "finalTransparency", + "target": "be3cbc58-94c8-4fd9-b3c6-fdc5e2ed1020", + "targetHandle": "finalTransparency" + }, + { + "id": "de39957c-e63f-4f01-88e4-7d177410b0cb", + "source": "acc83d6c-bb42-45a0-9574-944fc6a18916", + "sourceHandle": "transparencyCurve", + "target": "be3cbc58-94c8-4fd9-b3c6-fdc5e2ed1020", + "targetHandle": "transparencyCurve" + }, + { + "id": "c3a39c25-0355-449a-a687-6c2e7b3405d7", + "source": "acc83d6c-bb42-45a0-9574-944fc6a18916", + "sourceHandle": "horizontalDistance", + "target": "be3cbc58-94c8-4fd9-b3c6-fdc5e2ed1020", + "targetHandle": "horizontalDistance" + }, + { + "id": "fce77eaa-a1d1-4c8f-a2fb-4bf3c206e0ea", + "source": "acc83d6c-bb42-45a0-9574-944fc6a18916", + "sourceHandle": "verticalDistance", + "target": "be3cbc58-94c8-4fd9-b3c6-fdc5e2ed1020", + "targetHandle": "verticalDistance" + }, + { + "id": "14b87b10-c47f-47be-ad93-16c9017e1ae4", + "source": "acc83d6c-bb42-45a0-9574-944fc6a18916", + "sourceHandle": "distanceCurve", + "target": "be3cbc58-94c8-4fd9-b3c6-fdc5e2ed1020", + "targetHandle": "distanceCurve" + }, + { + "id": "e3a9fc6b-023c-453f-a492-2048e25286a8", + "source": "acc83d6c-bb42-45a0-9574-944fc6a18916", + "sourceHandle": "blur", + "target": "be3cbc58-94c8-4fd9-b3c6-fdc5e2ed1020", + "targetHandle": "blur" + }, + { + "id": "ca41f137-e2f5-425b-bc44-0ca82b634663", + "source": "acc83d6c-bb42-45a0-9574-944fc6a18916", + "sourceHandle": "blurCurve", + "target": "be3cbc58-94c8-4fd9-b3c6-fdc5e2ed1020", + "targetHandle": "blurCurve" + }, + { + "id": "3ed28d7b-a3a8-4f60-9bb0-820394dc74cb", + "source": "acc83d6c-bb42-45a0-9574-944fc6a18916", + "sourceHandle": "reduceSpread", + "target": "be3cbc58-94c8-4fd9-b3c6-fdc5e2ed1020", + "targetHandle": "reduceSpread" + }, + { + "id": "f64803b4-238c-4d4d-84f5-3912bc61c9b6", + "source": "acc83d6c-bb42-45a0-9574-944fc6a18916", + "sourceHandle": "color", + "target": "be3cbc58-94c8-4fd9-b3c6-fdc5e2ed1020", + "targetHandle": "color" + }, + { + "id": "5bb76efd-f840-46c4-94c6-70c4340c2192", + "source": "be3cbc58-94c8-4fd9-b3c6-fdc5e2ed1020", + "sourceHandle": "value", + "target": "94647d04-0cbe-4c7d-9316-93fecb8d9efa", + "targetHandle": "tokens" + } + ], + "annotations": { + "engine.id": "67114c77-244d-4c48-9087-634bfd37782b", + "engine.version": "0.12.0", + "ui.viewports": [null, null, null, null, null, null, null, null, null], + "ui.viewport": { + "x": -537.6583873800437, + "y": -93.3409458248409, + "zoom": 0.75 + }, + "ui.version": "4.3.9" + } +} diff --git a/packages/ui/src/data/layout/default.json b/packages/ui/src/data/layout/default.json new file mode 100644 index 000000000..26bbd6bc3 --- /dev/null +++ b/packages/ui/src/data/layout/default.json @@ -0,0 +1,84 @@ +{ + "dockbox": { + "id": "+18", + "size": 200, + "mode": "horizontal", + "children": [ + { + "id": "+22", + "size": 0.25, + "tabs": [ + { + "id": "dropPanel" + }, + { + "id": "previewNodesPanel" + }, + { + "id": "marketplace" + } + ], + "group": "popout", + "activeId": "dropPanel" + }, + { + "id": "graphs", + "size": 1.4166666666666665, + "tabs": [ + { + "id": "graph1" + } + ], + "group": "graph", + "activeId": "graph1" + }, + { + "id": "+24", + "size": 0.3333333333333333, + "mode": "vertical", + "children": [ + { + "id": "+25", + "size": 12, + "tabs": [ + { + "id": "input" + } + ], + "group": "popout", + "activeId": "input" + }, + { + "id": "+26", + "size": 12, + "tabs": [ + { + "id": "outputs" + } + ], + "group": "popout", + "activeId": "outputs" + } + ] + } + ] + }, + "floatbox": { + "id": "+30", + "size": 1, + "mode": "float", + "children": [] + }, + "windowbox": { + "id": "+31", + "size": 1, + "mode": "window", + "children": [] + }, + "maxbox": { + "id": "+32", + "size": 1, + "mode": "maximize", + "children": [] + } +} diff --git a/yarn.lock b/yarn.lock index d2142f22f..ecc662c83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3131,18 +3131,6 @@ resolved "https://registry.yarnpkg.com/@interactjs/types/-/types-1.10.27.tgz#10afd71cef2498e2b5192cf0d46f937d8ceb767f" integrity sha512-BUdv0cvs4H5ODuwft2Xp4eL8Vmi3LcihK42z0Ft/FbVJZoRioBsxH+LlsBdK4tAie7PqlKGy+1oyOncu1nQ6eA== -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - "@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" @@ -12521,6 +12509,14 @@ handlebars@^4.7.7: optionalDependencies: uglify-js "^3.1.4" +happy-dom@^16.3.0: + version "16.3.0" + resolved "https://registry.yarnpkg.com/happy-dom/-/happy-dom-16.3.0.tgz#e138b5692cdff8e1e25d02b7730d320b4a151f65" + integrity sha512-Q71RaIhyS21vhW17Tpa5W36yqQXIlE1TZ0A0Gguts8PShUSQE/7fBgxYGxgm3+5y0gF6afdlAVHLQqgrIcfRzg== + dependencies: + webidl-conversions "^7.0.0" + whatwg-mimetype "^3.0.0" + hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" @@ -13843,22 +13839,15 @@ itty-router@^5.0.9: resolved "https://registry.yarnpkg.com/itty-router/-/itty-router-5.0.17.tgz#e5a015756bfc420ea20f09da80935a7feb8c4ef8" integrity sha512-ZHnPI0OOyTTLuNp2FdciejYaK4Wl3ZV3O0yEm8njOGggh/k/ek3BL7X2I5YsCOfc5vLhIJgj3Z4pUtLs6k9Ucg== -jackspeak@^3.1.2: - version "3.4.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" - integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== +jackspeak@2.1.1, jackspeak@^3.1.2, jackspeak@^4.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.1.1.tgz#2a42db4cfbb7e55433c28b6f75d8b796af9669cd" + integrity sha512-juf9stUEwUaILepraGOWIJTLwg48bUnBmRqd2ln2Os1sW987zeoj/hzhbvRB95oMuS2ZTpjULmdwHNX4rzZIZw== dependencies: - "@isaacs/cliui" "^8.0.2" + cliui "^8.0.1" optionalDependencies: "@pkgjs/parseargs" "^0.11.0" -jackspeak@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.0.2.tgz#11f9468a3730c6ff6f56823a820d7e3be9bef015" - integrity sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw== - dependencies: - "@isaacs/cliui" "^8.0.2" - jake@^10.8.5: version "10.9.1" resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.1.tgz#8dc96b7fcc41cb19aa502af506da4e1d56f5e62b" @@ -18630,13 +18619,6 @@ react-element-to-jsx-string@^15.0.0: is-plain-object "5.0.0" react-is "18.1.0" -react-error-boundary@4.0.11: - version "4.0.11" - resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.11.tgz#36bf44de7746714725a814630282fee83a7c9a1c" - integrity sha512-U13ul67aP5DOSPNSCWQ/eO0AQEYzEFkVljULQIjMV0KlffTAhxuDoBKdO0pb/JZ8mDhMKFZ9NZi0BmLGUiNphw== - dependencies: - "@babel/runtime" "^7.12.5" - react-error-boundary@^4.0.13: version "4.0.13" resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.0.13.tgz#80386b7b27b1131c5fbb7368b8c0d983354c7947" @@ -19585,14 +19567,7 @@ responselike@^3.0.0: dependencies: lowercase-keys "^3.0.0" -resq@1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/resq/-/resq-1.10.2.tgz#cedf4f20d53f6e574b1e12afbda446ad9576c193" - integrity sha512-HmgVS3j+FLrEDBTDYysPdPVF9/hioDMJ/otOiQDKqk77YfZeeLOj0qi34yObumcud1gBpk+wpBTEg4kMicD++A== - dependencies: - fast-deep-equal "^2.0.1" - -resq@^1.11.0: +resq@1.10.2, resq@^1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/resq/-/resq-1.11.0.tgz#edec8c58be9af800fd628118c0ca8815283de196" integrity sha512-G10EBz+zAAy3zUd/CDoBbXRL6ia9kOo3xRHrMDsHljI0GDkhYlyjwoCx5+3eCC4swi1uCoZQhskuJkj7Gp57Bw== @@ -20588,15 +20563,6 @@ string-to-arraybuffer@^1.0.0: atob-lite "^2.0.0" is-base64 "^0.1.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -20720,13 +20686,6 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -22256,6 +22215,11 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + webpack-bundle-analyzer@4.10.1: version "4.10.1" resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz#84b7473b630a7b8c21c741f81d8fe4593208b454" @@ -22425,6 +22389,11 @@ websocket@^1.0.8, websocket@~1.0.22: utf-8-validate "^5.0.2" yaeti "^0.0.6" +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -22559,15 +22528,6 @@ workerpool@^6.5.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" From 98200e02980bd7ea8df3068877cea2883c57af1e Mon Sep 17 00:00:00 2001 From: SorsOps <80043879+sorsOps@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:42:01 +0200 Subject: [PATCH 02/10] Fix rebase issues --- .../src/ui/controls/token.tsx | 4 +- .../src/ui/controls/tokenArray.tsx | 8 +- packages/ui/src/components/editor/index.tsx | 25 +----- packages/ui/src/components/editor/layout.ts | 80 ------------------- 4 files changed, 9 insertions(+), 108 deletions(-) delete mode 100644 packages/ui/src/components/editor/layout.ts diff --git a/packages/nodes-design-tokens/src/ui/controls/token.tsx b/packages/nodes-design-tokens/src/ui/controls/token.tsx index 992f413af..43ab3d3c3 100644 --- a/packages/nodes-design-tokens/src/ui/controls/token.tsx +++ b/packages/nodes-design-tokens/src/ui/controls/token.tsx @@ -1,7 +1,7 @@ import { IField } from '@tokens-studio/graph-editor'; import { PreviewColor } from './preview/color.js'; +import { SingleToken, TokenTypes } from '@tokens-studio/types'; import { Stack } from '@tokens-studio/ui'; -import { TokenTypes } from '@tokens-studio/types'; import { observer } from 'mobx-react-lite'; import React from 'react'; @@ -16,7 +16,7 @@ export const getPreview = tokenData => { type PreviewProps = { // eslint-disable-next-line @typescript-eslint/no-explicit-any - token: any; + token: SingleToken; hideName?: boolean; }; diff --git a/packages/nodes-design-tokens/src/ui/controls/tokenArray.tsx b/packages/nodes-design-tokens/src/ui/controls/tokenArray.tsx index e7452d227..49d534d5d 100644 --- a/packages/nodes-design-tokens/src/ui/controls/tokenArray.tsx +++ b/packages/nodes-design-tokens/src/ui/controls/tokenArray.tsx @@ -1,5 +1,5 @@ import { Accordion, Button, Separator, Stack } from '@tokens-studio/ui'; -import { IField, flatTokensRestoreToMap } from '@tokens-studio/graph-editor'; +import { IField, IResolvedToken, flatTokensRestoreToMap } from '@tokens-studio/graph-editor'; import { Token } from './token.js'; import { observer } from 'mobx-react-lite'; import React from 'react'; @@ -8,7 +8,11 @@ export const TokenArrayField = ({ port }: IField) => { return ; }; -export const TokenArrayFieldInner = observer(({ value }) => { +interface TokenArrayFieldInnerProps { + value: IResolvedToken[]; +} + +export const TokenArrayFieldInner = observer(({ value }: TokenArrayFieldInnerProps) => { const downloadTokens = () => { const element = document.createElement('a'); const asObj = flatTokensRestoreToMap(value); diff --git a/packages/ui/src/components/editor/index.tsx b/packages/ui/src/components/editor/index.tsx index 6e1f23af9..fbad68143 100644 --- a/packages/ui/src/components/editor/index.tsx +++ b/packages/ui/src/components/editor/index.tsx @@ -1,8 +1,7 @@ 'use client'; -import { DropPanelInner, Editor } from '@tokens-studio/graph-editor'; +import { Editor } from '@tokens-studio/graph-editor'; import { EmptyStateEditor } from '../EmptyStateEditor.tsx'; -import { ErrorBoundary } from 'react-error-boundary'; import { ExamplesPicker } from '../ExamplesPicker.tsx'; import { capabilities, @@ -11,10 +10,8 @@ import { menu, nodeTypes, panelItems, - previewItems, specifics } from './data.tsx'; -import { defaultLayout } from './layout.ts'; import { loadCompounds } from '@/data/compounds/index.tsx'; import { observer } from 'mobx-react-lite'; import { tabLoader } from './tabLoader.tsx'; @@ -26,25 +23,7 @@ import initialLayout from '@/data/layout/default.json'; import styles from './styles.module.css'; import type { LayoutBase } from 'rc-dock'; import type { ReactElement } from 'react'; -import type { TabBase, TabData } from 'rc-dock'; -const tabLoader = (tab: TabBase): TabData | undefined => { - const { id } = tab; - switch (id) { - case 'previewNodesPanel': - return { - group: 'popout', - id: 'previewNodesPanel', - title: 'Preview', - content: ( - }> - - - ), - closable: true - }; - } -}; export const EditorTab = observer( ( @@ -78,8 +57,6 @@ export const EditorTab = observer( capabilities={capabilities} tabLoader={tabLoader} controls={controls} - initialLayout={defaultLayout} - tabLoader={tabLoader} specifics={specifics} icons={icons} toolbarButtons={toolbarButtons} diff --git a/packages/ui/src/components/editor/layout.ts b/packages/ui/src/components/editor/layout.ts deleted file mode 100644 index cbeca686f..000000000 --- a/packages/ui/src/components/editor/layout.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { LayoutBase } from 'rc-dock'; - -export const defaultLayout: LayoutBase = { - dockbox: { - mode: 'vertical', - children: [ - { - mode: 'horizontal', - children: [ - { - size: 2, - mode: 'vertical', - children: [ - { - mode: 'horizontal', - children: [ - { - size: 3, - mode: 'vertical', - children: [ - { - tabs: [ - { - id: 'dropPanel' - }, - { - id: 'previewNodesPanel' - } - ] - } - ] - }, - { - size: 17, - mode: 'vertical', - children: [ - { - id: 'graphs', - size: 700, - group: 'graph', - tabs: [ - { - id: 'graph1' - } - ] - } - ] - }, - - { - size: 4, - mode: 'vertical', - children: [ - { - size: 12, - tabs: [ - { - id: 'input' - } - ] - }, - { - size: 12, - tabs: [ - { - id: 'outputs' - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } -}; From a2e571b746663e68bd8044df15577c174bf87d91 Mon Sep 17 00:00:00 2001 From: SorsOps <80043879+sorsOps@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:16:58 +0200 Subject: [PATCH 03/10] Start implementing systems and frames --- packages/graph-editor/readme.md | 4 + .../contextMenus/paneContextMenu.tsx | 17 +- .../src/components/controls/array.tsx | 19 +- .../src/components/controls/color.tsx | 11 +- .../src/components/controls/curve.tsx | 38 +- .../src/components/controls/interface.tsx | 2 + .../src/components/controls/numeric.tsx | 12 +- .../src/components/controls/slider.tsx | 12 +- .../src/components/controls/string.tsx | 9 +- .../src/components/controls/text.tsx | 10 +- .../src/components/dialogs/findDialog.tsx | 15 +- .../src/components/flow/edges/edge.tsx | 185 +++++----- .../components/flow/nodes/passthroughNode.tsx | 7 +- .../src/components/flow/wrapper/nodeV2.tsx | 100 ++++-- .../src/components/hotKeys/index.tsx | 26 +- .../components/panels/dropPanel/dropPanel.tsx | 7 +- .../panels/inputs/dynamicInputs.tsx | 2 - .../src/components/panels/inputs/index.tsx | 53 +-- .../src/components/panels/legend/index.tsx | 7 +- .../src/components/panels/output/index.tsx | 44 +-- .../panels/output/styles.module.css | 10 + .../src/components/panels/settings/index.tsx | 329 +++++++++--------- .../src/components/panels/unified/index.tsx | 59 ++++ .../panels/unified/styles.module.css | 14 + .../src/components/portPanel/index.tsx | 27 +- .../src/components/toolbar/dropdowns/add.tsx | 9 +- .../src/components/toolbar/toolbar.tsx | 9 +- .../graph-editor/src/editor/editorTypes.ts | 44 +-- packages/graph-editor/src/editor/graph.tsx | 59 ++-- .../src/editor/hooks/useAutolayout.ts | 14 +- packages/graph-editor/src/editor/index.tsx | 34 +- .../src/editor/layoutController.tsx | 53 +-- packages/graph-editor/src/index.tsx | 2 + packages/graph-editor/src/redux/index.tsx | 43 +-- .../graph-editor/src/redux/models/index.ts | 2 - .../graph-editor/src/redux/models/registry.ts | 62 +--- .../graph-editor/src/redux/models/root.ts | 2 - .../graph-editor/src/redux/models/settings.ts | 133 ------- .../graph-editor/src/redux/selectors/index.ts | 1 - .../src/redux/selectors/registry.ts | 22 -- .../graph-editor/src/redux/selectors/roots.ts | 1 - .../src/redux/selectors/settings.ts | 42 --- packages/graph-editor/src/redux/store.tsx | 2 - packages/graph-editor/src/registry/icon.tsx | 2 +- packages/graph-editor/src/system/hook.tsx | 8 + packages/graph-editor/src/system/index.tsx | 98 ++++++ packages/graph-editor/src/system/settings.tsx | 91 +++++ .../src/ui/controls/tokenArray.tsx | 80 +++-- packages/ui/src/components/editor/index.tsx | 31 +- packages/ui/src/data/layout/default.json | 15 +- 50 files changed, 869 insertions(+), 1009 deletions(-) create mode 100644 packages/graph-editor/src/components/panels/output/styles.module.css create mode 100644 packages/graph-editor/src/components/panels/unified/index.tsx create mode 100644 packages/graph-editor/src/components/panels/unified/styles.module.css delete mode 100644 packages/graph-editor/src/redux/models/settings.ts delete mode 100644 packages/graph-editor/src/redux/selectors/settings.ts create mode 100644 packages/graph-editor/src/system/hook.tsx create mode 100644 packages/graph-editor/src/system/index.tsx create mode 100644 packages/graph-editor/src/system/settings.tsx diff --git a/packages/graph-editor/readme.md b/packages/graph-editor/readme.md index 274985faa..41dd56bac 100644 --- a/packages/graph-editor/readme.md +++ b/packages/graph-editor/readme.md @@ -83,3 +83,7 @@ In the example above, we use the useRef hook to create a reference to the Editor ## Development We need to force the cypress react selector to use a version of `resq` that supports 18.2.0. This is done through resolutions and by manually adding it as a dev dependency + +## Notes + +This project uses [CSS modules](https://github.com/css-modules/css-modules) to handle encapsulating css. We distribute this in the dist with imports to `*.module.css` files still remaining without transformation to the css files. We assume that the implementing system will handle this transformation and export of the mangled css classnames if needed. diff --git a/packages/graph-editor/src/components/contextMenus/paneContextMenu.tsx b/packages/graph-editor/src/components/contextMenus/paneContextMenu.tsx index b64d011a8..12b62d453 100644 --- a/packages/graph-editor/src/components/contextMenus/paneContextMenu.tsx +++ b/packages/graph-editor/src/components/contextMenus/paneContextMenu.tsx @@ -1,13 +1,12 @@ import { ContextMenuItem } from './ContextMenuStyles.js'; import { Menu, Separator } from 'react-contexify'; import { clear } from '../../editor/actions/clear.js'; -import { showGrid, snapGrid } from '@/redux/selectors/settings.js'; import { useAction } from '@/editor/actions/provider.js'; import { useAutoLayout } from '../../editor/hooks/useAutolayout.js'; import { useDispatch } from '@/hooks/index.js'; import { useLocalGraph } from '@/context/graph.js'; import { useReactFlow } from 'reactflow'; -import { useSelector } from 'react-redux'; +import { useSystem } from '@/system/hook.js'; import React, { useCallback } from 'react'; export interface IPaneContextMenu { @@ -17,8 +16,8 @@ export interface IPaneContextMenu { export const PaneContextMenu = ({ id }: IPaneContextMenu) => { const reactFlowInstance = useReactFlow(); - const showGridValue = useSelector(showGrid); - const snapGridValue = useSelector(snapGrid); + + const system = useSystem(); const dispatch = useDispatch(); const graph = useLocalGraph(); const createNode = useAction('createNode'); @@ -51,12 +50,12 @@ export const PaneContextMenu = ({ id }: IPaneContextMenu) => { }, [reactFlowInstance]); const setShowGrid = useCallback(() => { - dispatch.settings.setShowGrid(!showGridValue); - }, [dispatch.settings, showGridValue]); + system.settings.setShowGrid(!system.settings.showGrid); + }, [system.settings]); const setSnapGrid = useCallback(() => { - dispatch.settings.setSnapGrid(!snapGridValue); - }, [dispatch.settings, snapGridValue]); + system.settings.setSnapGrid(!system.settings.snapGrid); + }, [system.settings]); const clearCallback = useCallback(() => { clear(reactFlowInstance, graph); @@ -70,7 +69,7 @@ export const PaneContextMenu = ({ id }: IPaneContextMenu) => { Apply Layout - {showGridValue ? 'Hide' : 'Show'} Grid + {system.settings.showGrid ? 'Hide' : 'Show'} Grid Recenter diff --git a/packages/graph-editor/src/components/controls/array.tsx b/packages/graph-editor/src/components/controls/array.tsx index 7fba5ac2c..47ad0b9d7 100644 --- a/packages/graph-editor/src/components/controls/array.tsx +++ b/packages/graph-editor/src/components/controls/array.tsx @@ -21,10 +21,8 @@ import { import { ColorPickerPopover } from '../colorPicker/index.js'; import { IField } from './interface.js'; import { JSONTree } from 'react-json-tree'; -import { delayedUpdateSelector } from '@/redux/selectors/index.js'; import { observer } from 'mobx-react-lite'; import { toJS } from 'mobx'; -import { useSelector } from 'react-redux'; import FloppyDisk from '@tokens-studio/icons/FloppyDisk.js'; import Minus from '@tokens-studio/icons/Minus.js'; import Plus from '@tokens-studio/icons/Plus.js'; @@ -40,7 +38,7 @@ const NEW_ITEM_DEFAULTS = { }, }; -export const ArrayField = observer(({ port, readOnly }: IField) => { +export const ArrayField = observer(({ port, readOnly, settings }: IField) => { const [value, setValue] = React.useState(port.value); const [selectItemsType, setSelectItemsType] = React.useState( port.type.items.$id, @@ -49,7 +47,6 @@ export const ArrayField = observer(({ port, readOnly }: IField) => { const [autofocusIndex, setAutofocusIndex] = React.useState( null, ); - const useDelayed = useSelector(delayedUpdateSelector); React.useEffect(() => { setValue(port.value); @@ -82,13 +79,13 @@ export const ArrayField = observer(({ port, readOnly }: IField) => { setValue(newValueArray); - if (useDelayed) { + if (settings.delayedUpdate) { return; } (port as Input).setValue(newValueArray); }, - [port, useDelayed, value], + [port, settings.delayedUpdate, value], ); const onColorChange = useCallback( @@ -106,12 +103,12 @@ export const ArrayField = observer(({ port, readOnly }: IField) => { setAutofocusIndex(newValueArray.length - 1); - if (useDelayed) { + if (settings.delayedUpdate) { return; } (port as Input).setValue(newValueArray); - }, [itemsType, port, useDelayed, value]); + }, [itemsType, port, settings.delayedUpdate, value]); const removeItem = useCallback( (index: number) => { @@ -121,13 +118,13 @@ export const ArrayField = observer(({ port, readOnly }: IField) => { setValue(newValueArray); setAutofocusIndex(null); - if (useDelayed) { + if (settings.delayedUpdate) { return; } (port as Input).setValue(newValueArray); }, - [port, useDelayed, value], + [port, settings.delayedUpdate, value], ); const itemList = React.useMemo(() => { @@ -255,7 +252,7 @@ export const ArrayField = observer(({ port, readOnly }: IField) => { onClick={addItem} />
- {useDelayed && ( + {settings.delayedUpdate && ( } diff --git a/packages/graph-editor/src/components/controls/color.tsx b/packages/graph-editor/src/components/controls/color.tsx index 20f4815de..4e51cc16c 100644 --- a/packages/graph-editor/src/components/controls/color.tsx +++ b/packages/graph-editor/src/components/controls/color.tsx @@ -2,15 +2,12 @@ import { ColorPickerPopover } from '../colorPicker/index.js'; import { IField } from './interface.js'; import { IconButton, Stack, Text } from '@tokens-studio/ui'; import { Input, hexToColor, toColor, toHex } from '@tokens-studio/graph-engine'; -import { delayedUpdateSelector } from '@/redux/selectors/index.js'; import { observer } from 'mobx-react-lite'; -import { useSelector } from 'react-redux'; import FloppyDisk from '@tokens-studio/icons/FloppyDisk.js'; import React, { useCallback } from 'react'; import styles from './color.module.css'; -export const ColorField = observer(({ port, readOnly }: IField) => { - const useDelayed = useSelector(delayedUpdateSelector); +export const ColorField = observer(({ port, readOnly, settings }: IField) => { const [val, setVal] = React.useState(''); React.useEffect(() => { @@ -33,14 +30,14 @@ export const ColorField = observer(({ port, readOnly }: IField) => { col = e.target.value; } setVal(col); - if (useDelayed) { + if (settings.delayedUpdate) { return; } //We need to convert from hex (port as Input).setValue(hexToColor(col)); }, - [port, useDelayed], + [port, settings.delayedUpdate], ); if (readOnly) { @@ -62,7 +59,7 @@ export const ColorField = observer(({ port, readOnly }: IField) => { {val} - {useDelayed && ( + {settings.delayedUpdate && ( } onClick={() => (port as Input).setValue(val)} diff --git a/packages/graph-editor/src/components/controls/curve.tsx b/packages/graph-editor/src/components/controls/curve.tsx index fc96a719b..b3bce2a9a 100644 --- a/packages/graph-editor/src/components/controls/curve.tsx +++ b/packages/graph-editor/src/components/controls/curve.tsx @@ -4,21 +4,23 @@ import { Input } from '@tokens-studio/graph-engine'; import { observer } from 'mobx-react-lite'; import React from 'react'; -export const CurveField = observer(({ port, readOnly }: IField) => { - const onChange = (index: number, value: number[]) => { - if (!readOnly) { - const points = [...port.value.curves[0].points]; - points[index] = value; - (port as Input).setValue({ - curves: [ - { - points, - }, - ], - }); - } - }; - return ( - - ); -}); +export const CurveField = observer( + ({ port, readOnly }: Omit) => { + const onChange = (index: number, value: number[]) => { + if (!readOnly) { + const points = [...port.value.curves[0].points]; + points[index] = value; + (port as Input).setValue({ + curves: [ + { + points, + }, + ], + }); + } + }; + return ( + + ); + }, +); diff --git a/packages/graph-editor/src/components/controls/interface.tsx b/packages/graph-editor/src/components/controls/interface.tsx index 6cb097cee..64ef95932 100644 --- a/packages/graph-editor/src/components/controls/interface.tsx +++ b/packages/graph-editor/src/components/controls/interface.tsx @@ -1,5 +1,7 @@ import { Port } from '@tokens-studio/graph-engine'; +import { SystemSettings } from '@/system/settings.js'; export interface IField { port: Port; readOnly?: boolean; + settings: SystemSettings; } diff --git a/packages/graph-editor/src/components/controls/numeric.tsx b/packages/graph-editor/src/components/controls/numeric.tsx index 6bb608bd9..a51012629 100644 --- a/packages/graph-editor/src/components/controls/numeric.tsx +++ b/packages/graph-editor/src/components/controls/numeric.tsx @@ -1,18 +1,16 @@ import { IField } from './interface.js'; import { IconButton, Stack, TextInput } from '@tokens-studio/ui'; import { Input } from '@tokens-studio/graph-engine'; -import { delayedUpdateSelector } from '@/redux/selectors/index.js'; import { observer } from 'mobx-react-lite'; -import { useSelector } from 'react-redux'; + import FloppyDisk from '@tokens-studio/icons/FloppyDisk.js'; import React, { useCallback } from 'react'; -export const NumericField = observer(({ port, readOnly }: IField) => { +export const NumericField = observer(({ port, readOnly, settings }: IField) => { const [intermediate, setIntermediate] = React.useState( undefined, ); const [hadErr, setHadErr] = React.useState(false); - const useDelayed = useSelector(delayedUpdateSelector); const [val, setVal] = React.useState(port.value); React.useEffect(() => { @@ -24,7 +22,7 @@ export const NumericField = observer(({ port, readOnly }: IField) => { if (!readOnly) { const number = Number.parseFloat(e.target.value); if (!Number.isNaN(number)) { - if (!useDelayed) { + if (!settings.delayedUpdate) { (port as Input).setValue(number); } else { setVal(number); @@ -37,7 +35,7 @@ export const NumericField = observer(({ port, readOnly }: IField) => { setIntermediate(e.target.value); } }, - [port, readOnly, useDelayed], + [readOnly, settings.delayedUpdate, port], ); return ( @@ -49,7 +47,7 @@ export const NumericField = observer(({ port, readOnly }: IField) => { onChange={onChange} disabled={readOnly} /> - {useDelayed && ( + {settings.delayedUpdate && ( } onClick={() => (port as Input).setValue(val)} diff --git a/packages/graph-editor/src/components/controls/slider.tsx b/packages/graph-editor/src/components/controls/slider.tsx index a7ee58a4d..1509f67fe 100644 --- a/packages/graph-editor/src/components/controls/slider.tsx +++ b/packages/graph-editor/src/components/controls/slider.tsx @@ -2,18 +2,16 @@ import { IField } from './interface.js'; import { IconButton, Stack, Text } from '@tokens-studio/ui'; import { Input } from '@tokens-studio/graph-engine'; import { Slider } from '../slider/index.js'; -import { delayedUpdateSelector } from '@/redux/selectors/index.js'; + import { observer } from 'mobx-react-lite'; -import { useSelector } from 'react-redux'; import FloppyDisk from '@tokens-studio/icons/FloppyDisk.js'; import React, { useCallback } from 'react'; -export const SliderField = observer(({ port, readOnly }: IField) => { +export const SliderField = observer(({ port, readOnly, settings }: IField) => { const min = port.type.minimum || 0; const max = port.type.maximum || 1; const step = port.type.multipleOf || (max - min) / 100; - const useDelayed = useSelector(delayedUpdateSelector); const [val, setVal] = React.useState(port.value); React.useEffect(() => { @@ -23,14 +21,14 @@ export const SliderField = observer(({ port, readOnly }: IField) => { const onChange = useCallback( (value: number[]) => { if (!readOnly) { - if (!useDelayed) { + if (!settings.delayedUpdate) { (port as Input).setValue(value[0]); } else { setVal(value[0]); } } }, - [port, readOnly, useDelayed], + [port, readOnly, settings.delayedUpdate], ); return ( @@ -43,7 +41,7 @@ export const SliderField = observer(({ port, readOnly }: IField) => { onValueChange={onChange} /> {val} - {useDelayed && ( + {settings.delayedUpdate && ( } onClick={() => (port as Input).setValue(val)} diff --git a/packages/graph-editor/src/components/controls/string.tsx b/packages/graph-editor/src/components/controls/string.tsx index 438b53bdc..41640f5b6 100644 --- a/packages/graph-editor/src/components/controls/string.tsx +++ b/packages/graph-editor/src/components/controls/string.tsx @@ -1,14 +1,11 @@ import { IField } from './interface.js'; import { IconButton, Stack, Text, TextInput } from '@tokens-studio/ui'; import { Input } from '@tokens-studio/graph-engine'; -import { delayedUpdateSelector } from '@/redux/selectors/index.js'; import { observer } from 'mobx-react-lite'; -import { useSelector } from 'react-redux'; import FloppyDisk from '@tokens-studio/icons/FloppyDisk.js'; import React, { useEffect } from 'react'; -export const Textfield = observer(({ port, readOnly }: IField) => { - const useDelayed = useSelector(delayedUpdateSelector); +export const Textfield = observer(({ port, readOnly, settings }: IField) => { const [val, setVal] = React.useState(port.value); useEffect(() => { @@ -18,7 +15,7 @@ export const Textfield = observer(({ port, readOnly }: IField) => { const onChange = (e: React.ChangeEvent) => { const str = e.target.value; setVal(str); - if (useDelayed) { + if (settings.delayedUpdate) { return; } @@ -32,7 +29,7 @@ export const Textfield = observer(({ port, readOnly }: IField) => { return ( - {useDelayed && ( + {settings.delayedUpdate && ( } onClick={() => (port as Input).setValue(val)} diff --git a/packages/graph-editor/src/components/controls/text.tsx b/packages/graph-editor/src/components/controls/text.tsx index 33b5ad6c8..e64df340c 100644 --- a/packages/graph-editor/src/components/controls/text.tsx +++ b/packages/graph-editor/src/components/controls/text.tsx @@ -1,14 +1,12 @@ import { IField } from './interface.js'; import { IconButton, Textarea as UITextarea } from '@tokens-studio/ui'; import { Input } from '@tokens-studio/graph-engine'; -import { delayedUpdateSelector } from '@/redux/selectors/index.js'; import { observer } from 'mobx-react-lite'; -import { useSelector } from 'react-redux'; + import FloppyDisk from '@tokens-studio/icons/FloppyDisk.js'; import React, { useEffect } from 'react'; -export const TextArea = observer(({ port, readOnly }: IField) => { - const useDelayed = useSelector(delayedUpdateSelector); +export const TextArea = observer(({ settings, port, readOnly }: IField) => { const [val, setVal] = React.useState(port.value); useEffect(() => { @@ -17,7 +15,7 @@ export const TextArea = observer(({ port, readOnly }: IField) => { const onChange = (str: string) => { setVal(str); - if (useDelayed) { + if (settings.delayedUpdate) { return; } @@ -31,7 +29,7 @@ export const TextArea = observer(({ port, readOnly }: IField) => { return ( <> - {useDelayed && ( + {settings.delayedUpdate && ( } onClick={() => (port as Input).setValue(val)} diff --git a/packages/graph-editor/src/components/dialogs/findDialog.tsx b/packages/graph-editor/src/components/dialogs/findDialog.tsx index a16e4d58a..5525503b2 100644 --- a/packages/graph-editor/src/components/dialogs/findDialog.tsx +++ b/packages/graph-editor/src/components/dialogs/findDialog.tsx @@ -1,11 +1,9 @@ import { Button, Dialog, IconButton, Text, TextInput } from '@tokens-studio/ui'; import { title as annotatedTitle } from '@/annotations/index.js'; -import { - graphEditorSelector, - showSearchSelector, -} from '@/redux/selectors/index.js'; -import { useDispatch, useGraph } from '@/hooks/index.js'; +import { graphEditorSelector } from '@/redux/selectors/index.js'; +import { useGraph } from '@/hooks/index.js'; import { useSelector } from 'react-redux'; +import { useSystem } from '@/system/hook.js'; import React from 'react'; import Xmark from '@tokens-studio/icons/Xmark.js'; @@ -13,12 +11,11 @@ export const FindDialog = () => { const [id, setId] = React.useState(''); const [title, setTitle] = React.useState(''); const localGraph = useGraph(); - const dispatch = useDispatch(); const graph = useSelector(graphEditorSelector); - const open = useSelector(showSearchSelector); + const system = useSystem(); const setOpen = (value: boolean) => { - dispatch.settings.setShowSearch(value); + system.settings.setShowSearch(value); }; const onClick = () => { @@ -65,7 +62,7 @@ export const FindDialog = () => { }; return ( - + diff --git a/packages/graph-editor/src/components/flow/edges/edge.tsx b/packages/graph-editor/src/components/flow/edges/edge.tsx index e86b056e8..5b0263068 100644 --- a/packages/graph-editor/src/components/flow/edges/edge.tsx +++ b/packages/graph-editor/src/components/flow/edges/edge.tsx @@ -1,14 +1,14 @@ -import { EdgeType } from '../../../redux/models/settings.js'; +import { EdgeType, SystemSettings } from '@/system/settings.js'; import { Port } from '@tokens-studio/graph-engine'; -import { edgeType as edgeTypeSelector } from '../../../redux/selectors/settings.js'; import { getBetterBezierPath } from './offsetBezier.js'; import { getSimpleBezierPath, getSmoothStepPath, getStraightPath, } from 'reactflow'; +import { observer } from 'mobx-react-lite'; import { useLocalGraph } from '@/context/graph.js'; -import { useSelector } from 'react-redux'; +import { useSystem } from '@/system/hook.js'; import React from 'react'; import colors from '@/tokens/colors.js'; @@ -26,89 +26,112 @@ const extractColor = (port: Port) => { return { color, backgroundColor }; }; -export default function CustomEdge({ - id, - sourceX, - sourceY, - targetX, - targetY, - sourcePosition, - targetPosition, - style = {}, - data, - markerEnd, -}) { - const edgeType = useSelector(edgeTypeSelector); - const graph = useLocalGraph(); - - const edge = graph.getEdge(id); - let col = undefined; - - if (edge) { - const sourceNode = graph.getNode(edge?.source); - - const sourcePort = sourceNode?.outputs[edge?.sourceHandle]; - - if (sourcePort) { - const { backgroundColor } = extractColor(sourcePort as Port); - col = backgroundColor; - } - } +export default (props) => { + const system = useSystem(); + return ; +}; - let edgeFn; - switch (edgeType) { - case EdgeType.bezier: - edgeFn = getBetterBezierPath; - break; - case EdgeType.simpleBezier: - edgeFn = getSimpleBezierPath; - break; - case EdgeType.smoothStep: - edgeFn = getSmoothStepPath; - break; - case EdgeType.straight: - edgeFn = getStraightPath; - break; - default: - edgeFn = getBetterBezierPath; - } +export interface ICustomEdge { + settings: SystemSettings; + id: string; + sourceX: number; + sourceY: number; + targetX: number; + targetY: number; + sourcePosition: object; + targetPosition: object; + style: object; + data?: { + text: string; + }; + markerEnd: string; +} - const [edgePath] = edgeFn({ +export const CustomEdgeInner = observer( + ({ + settings, + id, sourceX, sourceY, - sourcePosition, targetX, targetY, + sourcePosition, targetPosition, - }); + style = {}, + data, + markerEnd, + }: ICustomEdge) => { + const graph = useLocalGraph(); - return ( - <> - - - - - - {data?.text} - - - - ); -} + const edge = graph.getEdge(id); + let col = undefined; + + if (edge) { + const sourceNode = graph.getNode(edge?.source); + + const sourcePort = sourceNode?.outputs[edge?.sourceHandle]; + + if (sourcePort) { + const { backgroundColor } = extractColor(sourcePort as Port); + col = backgroundColor; + } + } + + let edgeFn; + switch (settings.edgeType) { + case EdgeType.bezier: + edgeFn = getBetterBezierPath; + break; + case EdgeType.simpleBezier: + edgeFn = getSimpleBezierPath; + break; + case EdgeType.smoothStep: + edgeFn = getSmoothStepPath; + break; + case EdgeType.straight: + edgeFn = getStraightPath; + break; + default: + edgeFn = getBetterBezierPath; + } + + const [edgePath] = edgeFn({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + }); + + return ( + <> + + + + + + {data?.text} + + + + ); + }, +); diff --git a/packages/graph-editor/src/components/flow/nodes/passthroughNode.tsx b/packages/graph-editor/src/components/flow/nodes/passthroughNode.tsx index 092dff2a5..70aa97571 100644 --- a/packages/graph-editor/src/components/flow/nodes/passthroughNode.tsx +++ b/packages/graph-editor/src/components/flow/nodes/passthroughNode.tsx @@ -2,21 +2,20 @@ import { Handle } from '../handles.js'; import { HandleContainer } from '../handles.js'; import { Stack } from '@tokens-studio/ui'; import { extractType, extractTypeIcon } from '../wrapper/nodeV2.js'; -import { icons } from '@/registry/icon.js'; import { useLocalGraph } from '@/context/graph.js'; -import { useSelector } from 'react-redux'; +import { useSystem } from '@/system/hook.js'; import React from 'react'; export const PassthroughNode = (args) => { const { id } = args; const graph = useLocalGraph(); const node = graph.getNode(id); - const iconTypeRegistry = useSelector(icons); + const system = useSystem(); if (!node) return null; const port = node.inputs.value; - const typeCol = extractTypeIcon(port, iconTypeRegistry); + const typeCol = extractTypeIcon(port, system.icons); const type = extractType(port.type); return ( diff --git a/packages/graph-editor/src/components/flow/wrapper/nodeV2.tsx b/packages/graph-editor/src/components/flow/wrapper/nodeV2.tsx index 3cf7ee226..61c6731bb 100644 --- a/packages/graph-editor/src/components/flow/wrapper/nodeV2.tsx +++ b/packages/graph-editor/src/components/flow/wrapper/nodeV2.tsx @@ -17,16 +17,11 @@ import { ErrorBoundaryContent } from '@/components/ErrorBoundaryContent.js'; import { Node as GraphNode } from '@tokens-studio/graph-engine'; import { Handle, HandleContainer, useHandle } from '../handles.js'; import { Stack, Text } from '@tokens-studio/ui'; -import { icons, nodeSpecifics } from '@/redux/selectors/registry.js'; -import { - inlineTypes, - inlineValues, - showTimings, -} from '@/redux/selectors/settings.js'; +import { SystemSettings } from '@/system/settings.js'; import { observer } from 'mobx-react-lite'; import { title } from '@/annotations/index.js'; import { useLocalGraph } from '@/context/graph.js'; -import { useSelector } from 'react-redux'; +import { useSystem } from '@/system/hook.js'; import React from 'react'; import clsx from 'clsx'; import colors from '@/tokens/colors.js'; @@ -57,6 +52,7 @@ export const NodeV2 = (args) => { const { id } = args; const graph = useLocalGraph(); const node = graph.getNode(id); + const system = useSystem(); if (!node) { return
Node not found
; @@ -64,7 +60,11 @@ export const NodeV2 = (args) => { return ( }> - + ); }; @@ -77,10 +77,40 @@ export interface INodeWrap { subtitle?: string; icon?: React.ReactNode; node: GraphNode; + settings: SystemSettings; +} + +export interface ISpecificWrapper { + specifics: Record< + string, + React.FC<{ + node: GraphNode; + }> + >; + node: GraphNode; } -const NodeWrap = observer(({ node, icon }: INodeWrap) => { - const showTimingsValue = useSelector(showTimings); - const specifics = useSelector(nodeSpecifics); + +export const SpecificWrapper = observer( + ({ specifics, node }: ISpecificWrapper) => { + const Specific = specifics[node.factory.type]; + + if (!Specific) { + return null; + } + return ( + + + + ); + }, +); + +const NodeWrap = observer(({ settings, node, icon }: INodeWrap) => { + const system = useSystem(); //Check if the input allows for dynamic inputs const isInput = !!( @@ -88,8 +118,6 @@ const NodeWrap = observer(({ node, icon }: INodeWrap) => { node.annotations[annotatedDynamicInputs] ); - const Specific = specifics[node.factory.type]; - return ( {
- {Specific && ( - - - - )} +
- {showTimingsValue && ( + {settings.showTimings && (
{node.lastExecutedDuration}ms @@ -137,18 +157,25 @@ const NodeWrap = observer(({ node, icon }: INodeWrap) => { ); }); + export interface IPortArray { ports: Record; hideNames?: boolean; } export const PortArray = observer(({ ports, hideNames }: IPortArray) => { + const system = useSystem(); const entries = Object.values(ports).sort(); return ( <> {entries .filter((x) => x.visible != false || x.isConnected) .map((input) => ( - + ))} ); @@ -313,12 +340,17 @@ const getValuePreview = (value, type) => { : valuePreview; }; +export interface IInputHandle { + port: Port; + hideName?: boolean; + inlineTypes: boolean; + inlineValues: boolean; +} + const InputHandle = observer( - ({ port, hideName }: { port: Port; hideName?: boolean }) => { - const inlineTypesValue = useSelector(inlineTypes); - const iconTypeRegistry = useSelector(icons); - const inlineValuesValue = useSelector(inlineValues); - const typeCol = extractTypeIcon(port, iconTypeRegistry); + ({ port, hideName, inlineTypes, inlineValues }: IInputHandle) => { + const system = useSystem(); + const typeCol = extractTypeIcon(port, system.icons); const input = port as unknown as Input; const type = extractType(port.type); const handleInformation = useHandle(); @@ -334,7 +366,7 @@ const InputHandle = observer( variadic > {!hideName && {port.name} + } - {inlineTypesValue && } + {inlineTypes && } {port._edges.map((edge, i) => { return ( @@ -354,7 +386,7 @@ const InputHandle = observer( flexDirection: 'row', }} > - {inlineValuesValue && ( + {inlineValues && ( )} - {inlineTypesValue && } + {inlineTypes && } ); })} @@ -401,7 +433,7 @@ const InputHandle = observer( > {input.name} - {inlineValuesValue && ( + {inlineValues && ( )} - {inlineTypesValue && } + {inlineTypes && } ); }, diff --git a/packages/graph-editor/src/components/hotKeys/index.tsx b/packages/graph-editor/src/components/hotKeys/index.tsx index 7fbb3e2d6..8f3ec8c29 100644 --- a/packages/graph-editor/src/components/hotKeys/index.tsx +++ b/packages/graph-editor/src/components/hotKeys/index.tsx @@ -1,12 +1,6 @@ import { HotKeys as HotKeysComp } from 'react-hotkeys'; import { SerializedNode } from '@/types/serializedNode.js'; import { annotatedDeleteable } from '@tokens-studio/graph-engine'; -import { - inlineTypes, - inlineValues, - showGrid, - snapGrid, -} from '@/redux/selectors/settings.js'; import { savedViewports } from '@/annotations/index.js'; import { useAction } from '@/editor/actions/provider.js'; import { useAutoLayout } from '@/editor/hooks/useAutolayout.js'; @@ -14,7 +8,7 @@ import { useDispatch } from '@/hooks/useDispatch.js'; import { useLocalGraph } from '@/hooks/index.js'; import { useMemo } from 'react'; import { useReactFlow } from 'reactflow'; -import { useSelector } from 'react-redux'; +import { useSystem } from '@/system/hook.js'; import { useToast } from '@/hooks/useToast.js'; import React from 'react'; import copy from 'copy-to-clipboard'; @@ -79,10 +73,7 @@ export const getViewports = (graph) => { }; export const useHotkeys = () => { - const showGridValue = useSelector(showGrid); - const snapGridValue = useSelector(snapGrid); - const inlineTypesValue = useSelector(inlineTypes); - const inlineValuesValue = useSelector(inlineValues); + const system = useSystem(); const duplicateNodes = useAction('duplicateNodes'); const deleteNode = useAction('deleteNode'); const copyNodes = useAction('copyNodes'); @@ -232,20 +223,20 @@ export const useHotkeys = () => { }, TOGGLE_GRID: (event) => { event.preventDefault(); - dispatch.settings.setShowGrid(!showGridValue); + system.settings.setShowGrid(!system.settings.showGrid); }, FIND: (event) => { event.preventDefault(); dispatch.settings.setShowSearch(true); }, TOGGLE_SNAP_GRID: () => { - dispatch.settings.setSnapGrid(!snapGridValue); + system.settings.setSnapGrid(!system.settings.snapGrid); }, TOGGLE_TYPES: () => { - dispatch.settings.setInlineTypes(!inlineTypesValue); + system.settings.setInlineTypes(!system.settings.inlineTypes); }, TOGGLE_VALUES: () => { - dispatch.settings.setInlineValues(!inlineValuesValue); + dispatch.settings.setInlineValues(!system.settings.inlineValues); }, }), @@ -257,10 +248,7 @@ export const useHotkeys = () => { graph, layout, reactFlowInstance, - showGridValue, - snapGridValue, - inlineTypesValue, - inlineValuesValue, + system.settings, trigger, ], ); diff --git a/packages/graph-editor/src/components/panels/dropPanel/dropPanel.tsx b/packages/graph-editor/src/components/panels/dropPanel/dropPanel.tsx index f47d36f07..dd494e9f6 100644 --- a/packages/graph-editor/src/components/panels/dropPanel/dropPanel.tsx +++ b/packages/graph-editor/src/components/panels/dropPanel/dropPanel.tsx @@ -2,8 +2,7 @@ import { Accordion, Stack, TextInput } from '@tokens-studio/ui'; import { DragItem } from './DragItem.js'; import { DropPanelStore } from './data.js'; import { observer } from 'mobx-react-lite'; -import { panelItemsSelector } from '@/redux/selectors/registry.js'; -import { useSelector } from 'react-redux'; +import { useSystem } from '@/system/hook.js'; import NavArrowRight from '@tokens-studio/icons/NavArrowRight.js'; import React, { useState } from 'react'; import styles from './dropPanel.module.css'; @@ -23,8 +22,8 @@ export interface IDropPanel { } export const DropPanel = () => { - const data = useSelector(panelItemsSelector); - return ; + const sys = useSystem(); + return ; }; export const DropPanelInner = observer(({ data }: IDropPanel) => { diff --git a/packages/graph-editor/src/components/panels/inputs/dynamicInputs.tsx b/packages/graph-editor/src/components/panels/inputs/dynamicInputs.tsx index e8cc5e78d..c7ab04606 100644 --- a/packages/graph-editor/src/components/panels/inputs/dynamicInputs.tsx +++ b/packages/graph-editor/src/components/panels/inputs/dynamicInputs.tsx @@ -1,7 +1,6 @@ import { Button, Checkbox, - Heading, Label, Select, Stack, @@ -77,7 +76,6 @@ export const DynamicInputs = observer(({ node }: { node: Node }) => { return ( - Add Input - - - - {selectedNode.factory.title} - - - -
- {dynamicInputs && } + + {dynamicInputs && } - {SpecificInput ? : null} - - {/* The purpose of the key is to invalidate the port panel if the selected node changes */} - - -
+ {SpecificInput ? : null} + + {/* The purpose of the key is to invalidate the port panel if the selected node changes */} + -
+
); } diff --git a/packages/graph-editor/src/components/panels/legend/index.tsx b/packages/graph-editor/src/components/panels/legend/index.tsx index 2fed1030f..a7f4f8ad9 100644 --- a/packages/graph-editor/src/components/panels/legend/index.tsx +++ b/packages/graph-editor/src/components/panels/legend/index.tsx @@ -1,12 +1,11 @@ import { Stack, Text } from '@tokens-studio/ui'; -import { icons } from '@/redux/selectors/registry.js'; -import { useSelector } from 'react-redux'; +import { useSystem } from '@/system/hook.js'; import React, { useMemo } from 'react'; import colors from '@/tokens/colors.js'; export const Legend = () => { - const iconsRegistry = useSelector(icons); - return ; + const system = useSystem(); + return ; }; export interface ILegendInner { diff --git a/packages/graph-editor/src/components/panels/output/index.tsx b/packages/graph-editor/src/components/panels/output/index.tsx index 8621fbf08..1256baf77 100644 --- a/packages/graph-editor/src/components/panels/output/index.tsx +++ b/packages/graph-editor/src/components/panels/output/index.tsx @@ -1,11 +1,12 @@ -import { Heading, Stack } from '@tokens-studio/ui'; import { Node } from '@tokens-studio/graph-engine'; import { PortPanel } from '@/components/portPanel/index.js'; +import { Stack } from '@tokens-studio/ui/Stack.js'; import { currentNode } from '@/redux/selectors/graph.js'; import { observer } from 'mobx-react-lite'; import { useGraph } from '@/hooks/useGraph.js'; import { useSelector } from 'react-redux'; import React, { useMemo } from 'react'; +import styles from './styles.module.css'; export function OutputSheet() { const graph = useGraph(); @@ -23,43 +24,10 @@ export function OutputSheet() { */ const OutputSheetObserver = observer(({ node }: { node: Node }) => { return ( -
- - - - {node.factory.title} - - - -
- - - -
+ + + -
+
); }); diff --git a/packages/graph-editor/src/components/panels/output/styles.module.css b/packages/graph-editor/src/components/panels/output/styles.module.css new file mode 100644 index 000000000..61e2800ca --- /dev/null +++ b/packages/graph-editor/src/components/panels/output/styles.module.css @@ -0,0 +1,10 @@ +.outer { + height: 100%; + flex: 1; + padding: var(--component-spacing-md); +} + +.inner { + padding-top: var(--component-spacing-md); + padding-bottom: var(--component-spacing-md); +} \ No newline at end of file diff --git a/packages/graph-editor/src/components/panels/settings/index.tsx b/packages/graph-editor/src/components/panels/settings/index.tsx index 5b7659377..624bef8a8 100644 --- a/packages/graph-editor/src/components/panels/settings/index.tsx +++ b/packages/graph-editor/src/components/panels/settings/index.tsx @@ -1,191 +1,184 @@ import { Checkbox, Label, Select, Stack, Text } from '@tokens-studio/ui'; -import { EdgeType, LayoutType } from '@/redux/models/settings.js'; -import { - connectOnClickSelector, - delayedUpdateSelector, - edgeType, - inlineTypes, - inlineValues, - layoutType, - showMinimapSelector, - showTimings, -} from '@/redux/selectors/settings.js'; +import { EdgeType, LayoutType, SystemSettings } from '@/system/settings.js'; import { contextMenuSelector } from '@/redux/selectors/ui.js'; +import { observer } from 'mobx-react-lite'; import { useDispatch } from '@/hooks/useDispatch.js'; import { useSelector } from 'react-redux'; +import { useSystem } from '@/system/hook.js'; import React from 'react'; const EdgeValues = Object.values(EdgeType); const LayoutValues = Object.values(LayoutType); export const Settings = () => { - const edgeTypeValue = useSelector(edgeType); - const layoutTypeValue = useSelector(layoutType); - const showTimingsValue = useSelector(showTimings); - const inlineTypesValue = useSelector(inlineTypes); - const inlineValuesValue = useSelector(inlineValues); - const delayedUpdateValue = useSelector(delayedUpdateSelector); - const connectOnClick = useSelector(connectOnClickSelector); - const contextMenus = useSelector(contextMenuSelector); - const showMinimap = useSelector(showMinimapSelector); - const dispatch = useDispatch(); + const system = useSystem(); - return ( -
- - - - dispatch.settings.setInlineTypes(Boolean(checked)) - } - checked={inlineTypesValue} - /> - - - - Adds additional labels to help differentiate types for colorblind - users. - - - - - - dispatch.settings.setInlineValues(Boolean(checked)) - } - checked={inlineValuesValue} - /> - - - - Shows values directly on the node. Useful for debugging but can be - cluttered. - - - - - - dispatch.settings.setDelayedUpdate(Boolean(checked)) - } - checked={delayedUpdateValue} - /> - - - - Forces a user to click save to update port. - + return ; +}; + +export const SettingsInner = observer( + ({ settings }: { settings: SystemSettings }) => { + const contextMenus = useSelector(contextMenuSelector); + const dispatch = useDispatch(); + + return ( +
+ + + + settings.setInlineTypes(Boolean(checked)) + } + checked={settings.inlineTypes} + /> + + + + Adds additional labels to help differentiate types for + colorblind users. + + - - - - dispatch.settings.setConnectOnClick(Boolean(checked)) - } - checked={connectOnClick} - /> - - - - Allows you to quick connect nodes by clicking on the 2 port. - + + + settings.setInlineValues(Boolean(checked)) + } + checked={settings.inlineValues} + /> + + + + Shows values directly on the node. Useful for debugging but can + be cluttered. + + - - - - dispatch.settings.setShowTimings(Boolean(checked)) - } - checked={showTimingsValue} - /> - - - - Shows how long it takes for a node to process. - + + + settings.setDelayedUpdate(Boolean(checked)) + } + checked={settings.delayedUpdate} + /> + + + + Forces a user to click save to update port. + + - - - - dispatch.settings.setShowMinimap(Boolean(checked)) - } - checked={showMinimap} - /> - - - - Shows the minimap in the graph editing area - + + + settings.setConnectOnClick(Boolean(checked)) + } + checked={settings.connectOnClick} + /> + + + + Allows you to quick connect nodes by clicking on the 2 port. + + - - - - dispatch.ui.setContextMenus(Boolean(checked)) - } - checked={contextMenus} - /> - - - - Provides right click context menus. - + + + settings.setShowTimings(Boolean(checked)) + } + checked={settings.showTimings} + /> + + + + Shows how long it takes for a node to process. + + - - -
- + checked={settings.showMinimap} + /> + + + + Shows the minimap in the graph editing area + +
-
- + checked={contextMenus} + /> + + + + Provides right click context menus. + + +
+ + +
+ +
+ +
+ +
-
-
- ); -}; +
+ ); + }, +); diff --git a/packages/graph-editor/src/components/panels/unified/index.tsx b/packages/graph-editor/src/components/panels/unified/index.tsx new file mode 100644 index 000000000..a740cff91 --- /dev/null +++ b/packages/graph-editor/src/components/panels/unified/index.tsx @@ -0,0 +1,59 @@ +import { + Accordion, + Heading, + IconButton, + Separator, + Stack, +} from '@tokens-studio/ui'; +import { InfoCircle } from 'iconoir-react'; +import { Inputsheet } from '../inputs/index.js'; +import { OutputSheet } from '../output/index.js'; +import { currentNode } from '@/redux/selectors/graph.js'; +import { useGraph } from '@/hooks/useGraph.js'; +import { useSelector } from 'react-redux'; +import React, { useMemo } from 'react'; +import styles from './styles.module.css'; + +export function UnifiedSheet() { + const graph = useGraph(); + const nodeID = useSelector(currentNode); + const selectedNode = useMemo(() => graph?.getNode(nodeID), [graph, nodeID]); + + if (!selectedNode) { + return <>; + } + + return ( +
+ + + + {selectedNode.factory.title} + } + /> + + + + + Inputs + + + + + + + + Outputs + + + + + + + +
+ ); +} diff --git a/packages/graph-editor/src/components/panels/unified/styles.module.css b/packages/graph-editor/src/components/panels/unified/styles.module.css new file mode 100644 index 000000000..1569b3c66 --- /dev/null +++ b/packages/graph-editor/src/components/panels/unified/styles.module.css @@ -0,0 +1,14 @@ +.container { + height: 100%; + width: 100%; + flex: 1; + display: flex; + overflow: auto; + flex-direction: column; +} + +.inner { + height: 100%; + flex: 1; + padding: var(--component-spacing-md); +} \ No newline at end of file diff --git a/packages/graph-editor/src/components/portPanel/index.tsx b/packages/graph-editor/src/components/portPanel/index.tsx index 4fcc84818..61088c06d 100644 --- a/packages/graph-editor/src/components/portPanel/index.tsx +++ b/packages/graph-editor/src/components/portPanel/index.tsx @@ -6,21 +6,19 @@ import { Tooltip, } from '@tokens-studio/ui'; import { Port as GraphPort } from '@tokens-studio/graph-engine'; -import { Input } from '@tokens-studio/graph-engine'; -import { observer } from 'mobx-react-lite'; -import { useSelector } from 'react-redux'; -import React, { useCallback, useMemo } from 'react'; - import { IField } from '@/components/controls/interface.js'; import { InlineTypeLabel } from '@/components/flow/index.js'; -import { controls } from '@/redux/selectors/registry.js'; +import { Input } from '@tokens-studio/graph-engine'; import { deletable, hidden, resetable } from '@/annotations/index.js'; +import { observer } from 'mobx-react-lite'; import { useGraph } from '@/hooks/useGraph.js'; +import { useSystem } from '@/system/hook.js'; import Download from '@tokens-studio/icons/Download.js'; import Eye from '@tokens-studio/icons/Eye.js'; import EyeClosed from '@tokens-studio/icons/EyeClosed.js'; import MoreVert from '@tokens-studio/icons/MoreVert.js'; import Puzzle from '@tokens-studio/icons/Puzzle.js'; +import React, { useCallback, useMemo } from 'react'; import Undo from '@tokens-studio/icons/Undo.js'; import Xmark from '@tokens-studio/icons/Xmark.js'; import copy from 'copy-to-clipboard'; @@ -30,6 +28,11 @@ export interface IPortPanel { readOnly?: boolean; } +export interface IPort { + port: GraphPort; + readOnly?: boolean; +} + export const PortPanel = observer(({ ports, readOnly }: IPortPanel) => { const entries = Object.values(ports).sort(); @@ -44,22 +47,24 @@ export const PortPanel = observer(({ ports, readOnly }: IPortPanel) => { ); }); -export const Port = observer(({ port, readOnly: isReadOnly }: IField) => { +export const Port = observer(({ port, readOnly: isReadOnly }: IPort) => { const readOnly = isReadOnly || port.isConnected; - const controlSelector = useSelector(controls); + const sys = useSystem(); const graph = useGraph(); const isInput = 'studio.tokens.generic.input' === port.node.factory.type; const isDynamicInput = Boolean(port.annotations[deletable]); const resettable = Boolean(port.annotations[resetable]); const inner = useMemo(() => { - const field = controlSelector.find((x) => x.matcher(port, { readOnly })); + const field = sys.controls.find((x) => x.matcher(port, { readOnly })); const Component = field?.component as React.FC; - return ; + return ( + + ); //We use an explicit dependency on the type // eslint-disable-next-line react-hooks/exhaustive-deps - }, [controlSelector, port, readOnly, port.type]); + }, [sys.controls, port, readOnly, port.type]); const onClick = useCallback(() => { port.setVisible(!port.visible); diff --git a/packages/graph-editor/src/components/toolbar/dropdowns/add.tsx b/packages/graph-editor/src/components/toolbar/dropdowns/add.tsx index c71c792c6..79c0e7f82 100644 --- a/packages/graph-editor/src/components/toolbar/dropdowns/add.tsx +++ b/packages/graph-editor/src/components/toolbar/dropdowns/add.tsx @@ -1,16 +1,15 @@ import { Button, DropdownMenu, Stack, Tooltip } from '@tokens-studio/ui'; -import { panelItemsSelector } from '@/redux/selectors/index.js'; import { useAction } from '@/editor/actions/provider.js'; import { useDispatch } from '@/hooks/index.js'; import { useReactFlow } from 'reactflow'; import { useSelectAddedNodes } from '@/hooks/useSelectAddedNodes.js'; -import { useSelector } from 'react-redux'; +import { useSystem } from '@/system/hook.js'; import NavArrowRight from '@tokens-studio/icons/NavArrowRight.js'; import Plus from '@tokens-studio/icons/Plus.js'; import React, { useCallback } from 'react'; export const AddDropdown = () => { - const data = useSelector(panelItemsSelector); + const sys = useSystem(); const createNode = useAction('createNode'); const reactFlowInstance = useReactFlow(); const dispatch = useDispatch(); @@ -55,7 +54,7 @@ export const AddDropdown = () => { ); const nodes = React.useMemo(() => { - return data.groups.map((group) => { + return sys.panelItems.groups.map((group) => { return ( @@ -82,7 +81,7 @@ export const AddDropdown = () => { ); }); - }, [data.groups, addNode]); + }, [sys.panelItems.groups, addNode]); return ( diff --git a/packages/graph-editor/src/components/toolbar/toolbar.tsx b/packages/graph-editor/src/components/toolbar/toolbar.tsx index fce1acc44..a89f9443f 100644 --- a/packages/graph-editor/src/components/toolbar/toolbar.tsx +++ b/packages/graph-editor/src/components/toolbar/toolbar.tsx @@ -1,12 +1,13 @@ import * as Toolbar from '@radix-ui/react-toolbar'; -import { ToolBarButtonsSelector } from '@/redux/selectors/index.js'; -import { useSelector } from 'react-redux'; +import { useSystem } from '@/system/hook.js'; import React from 'react'; import styles from './toolbar.module.css'; export const GraphToolbar = () => { - const toolbarButtons = useSelector(ToolBarButtonsSelector); - return {toolbarButtons}; + const system = useSystem(); + return ( + {system.toolbarButtons} + ); }; export const ToolbarSeparator = ({ className = '', ...props }) => ( diff --git a/packages/graph-editor/src/editor/editorTypes.ts b/packages/graph-editor/src/editor/editorTypes.ts index e1aede73e..f69893a04 100644 --- a/packages/graph-editor/src/editor/editorTypes.ts +++ b/packages/graph-editor/src/editor/editorTypes.ts @@ -1,24 +1,23 @@ import { - CapabilityFactory, ExternalLoader, Graph, Node as GraphNode, - NodeLoader, SchemaObject, SerializedGraph, } from '@tokens-studio/graph-engine'; -import { Control } from '../types/controls.js'; -import { DropPanelStore } from '@/components/panels/dropPanel/index.js'; + import { Edge, Node, ReactFlowInstance } from 'reactflow'; import { Menu } from '@/components/menubar/data.js'; -import type { LayoutBase, TabBase, TabData } from 'rc-dock'; +import { System } from '@/system/index.js'; +import type { LayoutBase } from 'rc-dock'; export interface EditorProps { id?: string; - nodeLoader?: NodeLoader; - tabLoader?: (tab: TabBase) => TabData | undefined; - + /** + * The system to use for the editor + */ + system: System; /** * A lookup of the custom node types to display in the editor. * Not populating this will result in the default items being displayed. @@ -46,31 +45,6 @@ export interface EditorProps { * A custom menu to display in the editor. */ menuItems?: Menu; - - /** - * Capabilities to load into the graphs. Each factory is loaded into each graph individually - */ - capabilities?: CapabilityFactory[]; - /** - * Items to display in the drop panel. - * Not populating this will result in the default items being displayed. - */ - panelItems: DropPanelStore; - /** - * Customize the controls that are displayed in the editor - */ - controls?: Control[]; - - /** - * A lookup of the custom node ui types to display in the editor. - */ - customNodeUI?: Record; - - /** - * Additional specifics to display in the editor for custom types - */ - specifics?: Record>; - /** * An initial layout to use */ @@ -81,10 +55,6 @@ export interface EditorProps { */ schemas?: SchemaObject[]; - /** - * Additional icons to display in the editor for custom types - */ - icons?: Record; /** * Additional buttons to display in the toolbar */ diff --git a/packages/graph-editor/src/editor/graph.tsx b/packages/graph-editor/src/editor/graph.tsx index 3e17a7652..380cf0956 100644 --- a/packages/graph-editor/src/editor/graph.tsx +++ b/packages/graph-editor/src/editor/graph.tsx @@ -55,19 +55,8 @@ import { NodeV2 } from '@/components/index.js'; import { PaneContextMenu } from '../components/contextMenus/paneContextMenu.js'; import { PassthroughNode } from '@/components/flow/nodes/passthroughNode.js'; import { SelectionContextMenu } from '@/components/contextMenus/selectionContextMenu.js'; -import { - capabilitiesSelector, - nodeLoaderSelector, - panelItemsSelector, -} from '@/redux/selectors/registry.js'; import { clear } from './actions/clear.js'; import { connectNodes } from './actions/connect.js'; -import { - connectOnClickSelector, - showGrid, - showMinimapSelector, - snapGrid, -} from '@/redux/selectors/settings.js'; import { contextMenuSelector } from '@/redux/selectors/ui.js'; import { copyNodeAction } from './actions/copyNodes.js'; import { currentPanelIdSelector } from '@/redux/selectors/graph.js'; @@ -87,6 +76,7 @@ import { useExternalLoader } from '@/context/ExternalLoaderContext.js'; import { useSelectAddedNodes } from '@/hooks/useSelectAddedNodes.js'; import { useSelector } from 'react-redux'; import { useSetCurrentNode } from '@/hooks/useSetCurrentNode.js'; +import { useSystem } from '@/system/hook.js'; import { version } from '@/data/version.js'; const snapGridCoords: SnapGrid = [16, 16]; @@ -112,15 +102,12 @@ export const EditorApp = React.forwardRef< ImperativeEditorRef, GraphEditorProps >((props: GraphEditorProps, ref) => { - const panelItems = useSelector(panelItemsSelector); - const nodeLoader = useSelector(nodeLoaderSelector); - const { id, customNodeUI = {}, children } = props; + const system = useSystem(); + + const { id, children } = props; const externalLoader = useExternalLoader(); - const showMinimap = useSelector(showMinimapSelector); - const capabilities = useSelector(capabilitiesSelector); const contextMenus = useSelector(contextMenuSelector); - const connectOnClick = useSelector(connectOnClickSelector); const reactFlowWrapper = useRef(null); const reactFlowInstance = useReactFlow(); const dispatch = useDispatch(); @@ -135,8 +122,6 @@ export const EditorApp = React.forwardRef< }, []); const [graph, setTheGraph] = useState(initialGraph); - const showGridValue = useSelector(showGrid); - const snapGridValue = useSelector(snapGrid); const internalRef = useRef(null); const activeGraphId = useSelector(currentPanelIdSelector); @@ -146,13 +131,13 @@ export const EditorApp = React.forwardRef< }, [graph, externalLoader]); const iconLookup = useMemo(() => { - return panelItems.groups.reduce((acc, group) => { + return system.panelItems.groups.reduce((acc, group) => { group.items.forEach((item) => { acc[item.type] = item.icon || group.icon; }); return acc; }, {}); - }, [panelItems]); + }, [system.panelItems.groups]); const refProxy = useCallback( (v) => { @@ -173,7 +158,7 @@ export const EditorApp = React.forwardRef< //Attach sideeffect listeners useEffect(() => { - capabilities.forEach((factory) => graph.registerCapability(factory)); + system.capabilities.forEach((factory) => graph.registerCapability(factory)); graph.onFinalize('serialize', (serialized) => { const nodes = reactFlowInstance.getNodes(); @@ -370,7 +355,7 @@ export const EditorApp = React.forwardRef< // Create flow node types here, instead of the global scope to ensure that custom nodes added by the user are available in nodeTypes const fullNodeTypesRef = useRef({ - ...customNodeUI, + ...system.customNodeUI, GenericNode: NodeV2, [PASSTHROUGH]: PassthroughNode, [EditorNodeTypes.GROUP]: groupNode, @@ -381,12 +366,12 @@ export const EditorApp = React.forwardRef< //Turn it into an O(1) lookup object return Object.fromEntries( Object.entries({ - ...customNodeUI, + ...system.customNodeUI, [NOTE]: NOTE, 'studio.tokens.generic.preview': 'studio.tokens.generic.preview', }).map(([k]) => [k, k]), ); - }, [customNodeUI]); + }, [system.customNodeUI]); const handleDeleteNode = useMemo(() => { return deleteNode(graph, dispatch, reactFlowInstance); @@ -397,7 +382,7 @@ export const EditorApp = React.forwardRef< createNode({ reactFlowInstance, graph, - nodeLoader, + nodeLoader: system.nodeLoader, iconLookup, customUI: customNodeMap, dropPanelPosition, @@ -406,7 +391,7 @@ export const EditorApp = React.forwardRef< [ reactFlowInstance, graph, - nodeLoader, + system.nodeLoader, iconLookup, customNodeMap, dropPanelPosition, @@ -429,7 +414,7 @@ export const EditorApp = React.forwardRef< }, loadRaw: async (serializedGraph) => { if (internalRef.current) { - await graph.deserialize(serializedGraph, nodeLoader); + await graph.deserialize(serializedGraph, system.nodeLoader); internalRef?.current.load(graph); } }, @@ -510,7 +495,7 @@ export const EditorApp = React.forwardRef< [ reactFlowInstance, graph, - nodeLoader, + system.nodeLoader, setNodes, setEdges, dispatch.graph, @@ -596,7 +581,7 @@ export const EditorApp = React.forwardRef< x: event.clientX, y: event.clientY, }); - const PassthroughFactory = await nodeLoader(PASSTHROUGH); + const PassthroughFactory = await system.nodeLoader(PASSTHROUGH); const newNode = new PassthroughFactory({ graph, @@ -658,7 +643,7 @@ export const EditorApp = React.forwardRef< return [...filtered, newEdge, newEdge2]; }); }, - [nodeLoader, graph, reactFlowInstance, setEdges, setNodes], + [reactFlowInstance, system, graph, setNodes, setEdges], ); const onNodeDrag = useCallback( @@ -701,7 +686,7 @@ export const EditorApp = React.forwardRef< reactFlowInstance, }); - const copyNodes = copyNodeAction(reactFlowInstance, graph, nodeLoader); + const copyNodes = copyNodeAction(reactFlowInstance, graph, system.nodeLoader); const selectAddedNodes = useSelectAddedNodes(); const onDrop = useCallback( @@ -765,10 +750,10 @@ export const EditorApp = React.forwardRef< onEdgeDoubleClick={onEdgeDblClick} onEdgesDelete={onEdgesDeleted} edges={edges} - connectOnClick={connectOnClick} + connectOnClick={system.settings.connectOnClick} elevateNodesOnSelect={true} onNodeDragStop={onNodeDragStop} - snapToGrid={snapGridValue} + snapToGrid={system.settings.snapGrid} edgeTypes={edgeTypes} nodeTypes={fullNodeTypesRef.current} snapGrid={snapGridCoords} @@ -801,7 +786,7 @@ export const EditorApp = React.forwardRef< maxZoom={Infinity} proOptions={proOptions} > - {showGridValue && ( + {system.settings.showGrid && ( )} @@ -823,7 +808,7 @@ export const EditorApp = React.forwardRef<
- {showMinimap && } + {system.settings.showMinimap && } { + const system = useSystem(); const dagreAutoLayout = useDagreLayout(); - const layoutType = useSelector(layoutTypeSelector); - return useCallback(() => { - switch (layoutType) { + switch (system.settings.layoutType) { case LayoutType.dagre: dagreAutoLayout(); break; } - }, [dagreAutoLayout, layoutType]); + }, [dagreAutoLayout, system.settings.layoutType]); }; diff --git a/packages/graph-editor/src/editor/index.tsx b/packages/graph-editor/src/editor/index.tsx index 80d621908..99ea807a8 100644 --- a/packages/graph-editor/src/editor/index.tsx +++ b/packages/graph-editor/src/editor/index.tsx @@ -2,9 +2,8 @@ import { EditorProps, ImperativeEditorRef } from './editorTypes.js'; import { LayoutController } from './layoutController.js'; import { ReduxProvider } from '../redux/index.js'; import { ToastProvider } from '@/hooks/useToast.js'; -import { defaultControls } from '@/registry/control.js'; -import { defaultPanelGroupsFactory } from '@/components/index.js'; -import { defaultSpecifics } from '@/registry/specifics.js'; + +import { SystemContext } from '@/system/hook.js'; import React from 'react'; /** @@ -13,33 +12,16 @@ import React from 'react'; */ export const Editor = React.forwardRef( (props: EditorProps, ref) => { - const { - panelItems = defaultPanelGroupsFactory(), - capabilities, - - toolbarButtons, - schemas, - nodeLoader, - controls = [...defaultControls], - specifics = defaultSpecifics, - icons, - } = props; + const { schemas } = props; // Note that the provider exists around the layout controller so that the layout controller can register itself during mount return ( - - - + + + + + ); }, diff --git a/packages/graph-editor/src/editor/layoutController.tsx b/packages/graph-editor/src/editor/layoutController.tsx index 30d7a5a6d..122fa814b 100644 --- a/packages/graph-editor/src/editor/layoutController.tsx +++ b/packages/graph-editor/src/editor/layoutController.tsx @@ -16,14 +16,14 @@ import { ExternalLoaderProvider } from '@/context/ExternalLoaderContext.js'; import { FindDialog } from '@/components/dialogs/findDialog.js'; import { GraphEditor } from './graphEditor.js'; import { IconButton, Stack, Tooltip } from '@tokens-studio/ui'; -import { Inputsheet } from '@/components/panels/inputs/index.js'; import { MAIN_GRAPH_ID } from '@/constants.js'; import { MenuBar, defaultMenuDataFactory } from '@/components/menubar/index.js'; -import { OutputSheet } from '@/components/panels/output/index.js'; +import { UnifiedSheet } from '@/components/panels/unified/index.js'; import { dockerSelector } from '@/redux/selectors/refs.js'; import { useDispatch } from '@/hooks/useDispatch.js'; import { useRegisterRef } from '@/hooks/useRegisterRef.js'; import { useSelector } from 'react-redux'; +import { useSystem } from '@/system/hook.js'; import Maximize from '@tokens-studio/icons/Maximize.js'; import React, { MutableRefObject, useCallback, useEffect } from 'react'; import Reduce from '@tokens-studio/icons/Reduce.js'; @@ -200,15 +200,7 @@ const defaultLayout: LayoutBase = { size: 12, tabs: [ { - id: 'input', - }, - ], - }, - { - size: 12, - tabs: [ - { - id: 'outputs', + id: 'unifiedPorts', }, ], }, @@ -225,20 +217,20 @@ const defaultLayout: LayoutBase = { }; const layoutLoader = (tab: TabBase, props, ref): TabData => { - const { id, ...rest } = tab; + const { id } = tab; switch (id) { case 'graphs': return { - id: 'graphs', + ...tab, //@ts-expect-error size: 700, group: 'graph', panelLock: { panelStyle: 'graph' }, - ...rest, }; case MAIN_GRAPH_ID: return { + ...tab, closable: true, cached: true, group: 'graph', @@ -250,37 +242,23 @@ const layoutLoader = (tab: TabBase, props, ref): TabData => { ), }; - case 'input': + case 'unifiedPorts': return { + ...tab, closable: true, cached: true, group: 'popout', - id: 'input', - title: 'Inputs', + title: 'Ports', content: ( }> - + ), }; - case 'outputs': - return { - closable: true, - cached: true, - group: 'popout', - id: 'outputs', - title: 'Outputs', - content: ( - }> - - - ), - }; - case 'dropPanel': return { + ...tab, group: 'popout', - id: 'dropPanel', title: 'Nodes', content: ( }> @@ -300,12 +278,13 @@ export const LayoutController = React.forwardRef< EditorProps >((props: EditorProps, ref) => { const { - tabLoader, externalLoader, initialLayout, menuItems = defaultMenuDataFactory(), } = props; + const system = useSystem(); + const registerDocker = useRegisterRef('docker'); const dispatch = useDispatch(); @@ -313,13 +292,13 @@ export const LayoutController = React.forwardRef< const loadTab = useCallback( (tab): TabData => { - const loaded = tabLoader?.(tab); + const loaded = system.tabLoader?.(tab); if (!loaded) { return layoutLoader(tab, props, ref); } return loaded; }, - [tabLoader, props, ref], + [system, props, ref], ); useEffect(() => { @@ -332,6 +311,8 @@ export const LayoutController = React.forwardRef< //We need to find the graph tab container in the newlayout const graphContainer = findGraphPanel(newLayout); + console.log(graphContainer); + if (graphContainer?.activeId) { //Get the active Id to find the currently selected graph dispatch.graph.setCurrentPanel(graphContainer.activeId!); diff --git a/packages/graph-editor/src/index.tsx b/packages/graph-editor/src/index.tsx index 1207401ef..fa92a3a34 100644 --- a/packages/graph-editor/src/index.tsx +++ b/packages/graph-editor/src/index.tsx @@ -19,3 +19,5 @@ export * from './registry/control.js'; export * from './registry/specifics.js'; export * from './registry/toolbar.js'; export * from './types/index.js'; + +export * from './system/index.js'; diff --git a/packages/graph-editor/src/redux/index.tsx b/packages/graph-editor/src/redux/index.tsx index fe0d0742e..d0185779a 100644 --- a/packages/graph-editor/src/redux/index.tsx +++ b/packages/graph-editor/src/redux/index.tsx @@ -4,20 +4,14 @@ import React, { useEffect } from 'react'; export const ReduxProvider = ({ children, - nodeTypes, - panelItems, - capabilities, - icons, schemas, - controls, - specifics, - toolbarButtons, + // toolbarButtons, }) => { - useEffect(() => { - if (toolbarButtons) { - store.dispatch.registry.setToolbarButtons(toolbarButtons); - } - }, [toolbarButtons]); + // useEffect(() => { + // if (toolbarButtons) { + // store.dispatch.registry.setToolbarButtons(toolbarButtons); + // } + // }, [toolbarButtons]); useEffect(() => { if (schemas) { @@ -25,30 +19,5 @@ export const ReduxProvider = ({ } }, [schemas]); - useEffect(() => { - store.dispatch.registry.registerIcons(icons || {}); - }, [icons]); - - useEffect(() => { - store.dispatch.registry.setControls(controls); - }, [controls]); - - useEffect(() => { - console.log(nodeTypes); - store.dispatch.registry.setNodeTypes(nodeTypes); - }, [nodeTypes]); - - useEffect(() => { - store.dispatch.registry.setSpecifics(specifics); - }, [specifics]); - - useEffect(() => { - store.dispatch.registry.setPanelItems(panelItems); - }, [panelItems]); - - useEffect(() => { - store.dispatch.registry.setCapabilities(capabilities || []); - }, [capabilities]); - return {children}; }; diff --git a/packages/graph-editor/src/redux/models/index.ts b/packages/graph-editor/src/redux/models/index.ts index 0afb0f6b2..b8d86b01d 100644 --- a/packages/graph-editor/src/redux/models/index.ts +++ b/packages/graph-editor/src/redux/models/index.ts @@ -3,12 +3,10 @@ import { RootModel } from './root.js'; import { graphState } from './graph.js'; import { refState } from './refs.js'; import { registryState } from './registry.js'; -import { settingsState } from './settings.js'; import { uiState } from './ui.js'; export const models: RootModel = { graph: graphState, - settings: settingsState, ui: uiState, refs: refState, registry: registryState, diff --git a/packages/graph-editor/src/redux/models/registry.ts b/packages/graph-editor/src/redux/models/registry.ts index c2b0ed442..c276337fc 100644 --- a/packages/graph-editor/src/redux/models/registry.ts +++ b/packages/graph-editor/src/redux/models/registry.ts @@ -1,48 +1,26 @@ import { AllSchemas, - CapabilityFactory, Node, NodeLoader, SchemaObject, } from '@tokens-studio/graph-engine'; -import { Control } from '@/types/controls.js'; import { DefaultToolbarButtons } from '@/registry/toolbar.js'; -import { - DropPanelStore, - defaultPanelGroupsFactory, -} from '@/components/panels/dropPanel/index.js'; +import { DropPanelStore } from '@/components/panels/dropPanel/index.js'; import { ReactElement } from 'react'; import { RootModel } from './root.js'; import { createModel } from '@rematch/core'; -import { defaultControls } from '@/registry/control.js'; -import { defaultSpecifics } from '@/registry/specifics.js'; -import { icons } from '@/registry/icon.js'; + import { inputControls } from '@/registry/inputControls.js'; export interface RegistryState { - //Additional specific controls for nodes. Appended to the end of the default controls - nodeSpecifics: Record>; - icons: Record; inputControls: Record>; - controls: Control[]; - nodeTypes: NodeLoader; - panelItems: DropPanelStore; - capabilities: CapabilityFactory[]; toolbarButtons: ReactElement[]; schemas: SchemaObject[]; } export const registryState = createModel()({ state: { - nodeSpecifics: defaultSpecifics, - icons: icons(), inputControls: { ...inputControls }, - controls: [...(defaultControls as Control[])], - panelItems: defaultPanelGroupsFactory(), - nodeTypes: async () => { - throw new Error('Node type not found'); - }, - capabilities: [], toolbarButtons: DefaultToolbarButtons(), schemas: AllSchemas, } as RegistryState, @@ -53,48 +31,12 @@ export const registryState = createModel()({ schemas, }; }, - setToolbarButtons(state, toolbarButtons: ReactElement[]) { - return { - ...state, - toolbarButtons, - }; - }, - setCapabilities(state, capabilities: CapabilityFactory[]) { - return { - ...state, - capabilities, - }; - }, setNodeTypes: (state, nodeTypes: NodeLoader) => { return { ...state, nodeTypes, }; }, - setSpecifics: ( - state, - nodeSpecifics: Record>, - ) => { - return { - ...state, - nodeSpecifics, - }; - }, - setControls(state, controls: Control[]) { - return { - ...state, - controls, - }; - }, - registerIcons(state, payload: Record) { - return { - ...state, - icons: { - ...state.icons, - ...payload, - }, - }; - }, registerInputControl( state, payload: { key: string; value: React.FC<{ node: Node }> }, diff --git a/packages/graph-editor/src/redux/models/root.ts b/packages/graph-editor/src/redux/models/root.ts index df2bbe604..e586803e0 100644 --- a/packages/graph-editor/src/redux/models/root.ts +++ b/packages/graph-editor/src/redux/models/root.ts @@ -2,11 +2,9 @@ import { Models } from '@rematch/core'; import { graphState } from './graph.js'; import { refState } from './refs.js'; import { registryState } from './registry.js'; -import { settingsState } from './settings.js'; import { uiState } from './ui.js'; export interface RootModel extends Models { - settings: typeof settingsState; ui: typeof uiState; graph: typeof graphState; refs: typeof refState; diff --git a/packages/graph-editor/src/redux/models/settings.ts b/packages/graph-editor/src/redux/models/settings.ts deleted file mode 100644 index b9ff22b05..000000000 --- a/packages/graph-editor/src/redux/models/settings.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { RootModel } from './root.js'; -import { createModel } from '@rematch/core'; - -export enum EdgeType { - bezier = 'Bezier', - smoothStep = 'Smooth step', - straight = 'Straight', - simpleBezier = 'Simple Bezier', -} -export enum LayoutType { - dagre = 'Dagre', - elkForce = 'Elk - Force', - elkRect = 'Elk - Rect', - elkLayered = 'Elk - Layered', - elkStress = 'Elk - Stress', -} - -export interface SettingsState { - edgeType: EdgeType; - layoutType: LayoutType; - debugMode: boolean; - showTimings: boolean; - showMinimap: boolean; - showGrid: boolean; - showSearch: boolean; - /** - * Whether to delay the update of a node when a value is changed - */ - delayedUpdate: boolean; - /** - * Whether to show the types inline with the nodes - */ - inlineTypes: boolean; - /** - * Whether to show the values inline with the nodes - */ - inlineValues: boolean; - connectOnClick: boolean; - snapGrid: boolean; -} - -export const settingsState = createModel()({ - state: { - edgeType: EdgeType.bezier, - layoutType: LayoutType.dagre, - showGrid: true, - connectOnClick: true, - showTimings: false, - showSearch: false, - inlineTypes: false, - inlineValues: false, - snapGrid: false, - debugMode: false, - showMinimap: false, - delayedUpdate: false, - } as SettingsState, - reducers: { - setConnectOnClick(state, connectOnClick: boolean) { - return { - ...state, - connectOnClick, - }; - }, - setShowSearch(state, showSearch: boolean) { - return { - ...state, - showSearch, - }; - }, - setShowMinimap(state, showMinimap: boolean) { - return { - ...state, - showMinimap, - }; - }, - - setDelayedUpdate(state, delayedUpdate: boolean) { - return { - ...state, - delayedUpdate, - }; - }, - - setSnapGrid(state, snapGrid: boolean) { - return { - ...state, - snapGrid, - }; - }, - setShowGrid(state, showGrid: boolean) { - return { - ...state, - showGrid, - }; - }, - setDebugMode(state, debugMode: boolean) { - return { - ...state, - debugMode, - }; - }, - setInlineTypes(state, inlineTypes: boolean) { - return { - ...state, - inlineTypes, - }; - }, - setInlineValues(state, inlineValues: boolean) { - return { - ...state, - inlineValues, - }; - }, - setEdgeType(state, edgeType: EdgeType) { - return { - ...state, - edgeType, - }; - }, - setShowTimings(state, showTimings: boolean) { - return { - ...state, - showTimings, - }; - }, - setLayoutType(state, layoutType: LayoutType) { - return { - ...state, - layoutType, - }; - }, - }, -}); diff --git a/packages/graph-editor/src/redux/selectors/index.ts b/packages/graph-editor/src/redux/selectors/index.ts index 9365f451f..40ee7fda4 100644 --- a/packages/graph-editor/src/redux/selectors/index.ts +++ b/packages/graph-editor/src/redux/selectors/index.ts @@ -2,5 +2,4 @@ export * from './graph.js'; export * from './refs.js'; export * from './roots.js'; export * from './registry.js'; -export * from './settings.js'; export * from './ui.js'; diff --git a/packages/graph-editor/src/redux/selectors/registry.ts b/packages/graph-editor/src/redux/selectors/registry.ts index 478de24d5..cfdad6d63 100644 --- a/packages/graph-editor/src/redux/selectors/registry.ts +++ b/packages/graph-editor/src/redux/selectors/registry.ts @@ -1,32 +1,10 @@ import { createSelector } from 'reselect'; import { registry } from './roots.js'; -export const icons = createSelector(registry, (state) => state.icons); export const inputControls = createSelector( registry, (state) => state.inputControls, ); -export const controls = createSelector(registry, (state) => state.controls); - -export const nodeSpecifics = createSelector( - registry, - (state) => state.nodeSpecifics, -); - -export const panelItemsSelector = createSelector( - registry, - (state) => state.panelItems, -); - -export const capabilitiesSelector = createSelector( - registry, - (state) => state.capabilities, -); - -export const nodeLoaderSelector = createSelector( - registry, - (state) => state.nodeTypes, -); export const ToolBarButtonsSelector = createSelector( registry, diff --git a/packages/graph-editor/src/redux/selectors/roots.ts b/packages/graph-editor/src/redux/selectors/roots.ts index abcf184a6..dbc75ccff 100644 --- a/packages/graph-editor/src/redux/selectors/roots.ts +++ b/packages/graph-editor/src/redux/selectors/roots.ts @@ -1,5 +1,4 @@ import { RootState } from '../store.js'; -export const settings = (state: RootState) => state.settings; export const ui = (state: RootState) => state.ui; export const graph = (state: RootState) => state.graph; export const refs = (state: RootState) => state.refs; diff --git a/packages/graph-editor/src/redux/selectors/settings.ts b/packages/graph-editor/src/redux/selectors/settings.ts deleted file mode 100644 index f1685b829..000000000 --- a/packages/graph-editor/src/redux/selectors/settings.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { createSelector } from 'reselect'; -import { settings } from './roots.js'; - -export const edgeType = createSelector(settings, (state) => state.edgeType); - -export const layoutType = createSelector(settings, (state) => state.layoutType); - -export const debugMode = createSelector(settings, (state) => state.debugMode); -export const inlineTypes = createSelector( - settings, - (state) => state.inlineTypes, -); -export const inlineValues = createSelector( - settings, - (state) => state.inlineValues, -); - -export const showGrid = createSelector(settings, (state) => state.showGrid); - -export const snapGrid = createSelector(settings, (state) => state.snapGrid); -export const showTimings = createSelector( - settings, - (state) => state.showTimings, -); - -export const showMinimapSelector = createSelector( - settings, - (state) => state.showMinimap, -); - -export const delayedUpdateSelector = createSelector( - settings, - (state) => state.delayedUpdate, -); -export const showSearchSelector = createSelector( - settings, - (state) => state.showSearch, -); -export const connectOnClickSelector = createSelector( - settings, - (state) => state.connectOnClick, -); diff --git a/packages/graph-editor/src/redux/store.tsx b/packages/graph-editor/src/redux/store.tsx index 3ec8c5195..fef9bacef 100644 --- a/packages/graph-editor/src/redux/store.tsx +++ b/packages/graph-editor/src/redux/store.tsx @@ -3,7 +3,6 @@ import { RefState } from './models/refs.js'; import { RegistryState } from './models/registry.js'; import { RematchDispatch, init } from '@rematch/core'; import { RootModel, models } from './models/index.js'; -import { SettingsState } from './models/settings.js'; import { UIState } from './models/ui.js'; export const store = init({ @@ -20,7 +19,6 @@ export const store = init({ export type Dispatch = RematchDispatch; export type RootState = { graph: GraphState; - settings: SettingsState; ui: UIState; refs: RefState; registry: RegistryState; diff --git a/packages/graph-editor/src/registry/icon.tsx b/packages/graph-editor/src/registry/icon.tsx index aa2014ad7..bb5b2a4b1 100644 --- a/packages/graph-editor/src/registry/icon.tsx +++ b/packages/graph-editor/src/registry/icon.tsx @@ -20,7 +20,7 @@ import Text from '@tokens-studio/icons/Text.js'; * Default icons for the graph editor * These icons are used to represent the different types of custom types in the graph editor */ -export const icons = () => +export const iconsFactory = () => ({ [COLOR]: , [CURVE]: , diff --git a/packages/graph-editor/src/system/hook.tsx b/packages/graph-editor/src/system/hook.tsx new file mode 100644 index 000000000..3cb2d0cdb --- /dev/null +++ b/packages/graph-editor/src/system/hook.tsx @@ -0,0 +1,8 @@ +import { System } from './index.js'; +import { createContext, useContext } from 'react'; + +export const SystemContext = createContext(undefined); + +export const useSystem = (): System => { + return useContext(SystemContext)!; +}; diff --git a/packages/graph-editor/src/system/index.tsx b/packages/graph-editor/src/system/index.tsx new file mode 100644 index 000000000..e79a90b5c --- /dev/null +++ b/packages/graph-editor/src/system/index.tsx @@ -0,0 +1,98 @@ +import { + CapabilityFactory, + Node, + NodeLoader, +} from '@tokens-studio/graph-engine'; +import { Control } from '@/types/controls.js'; +import { DefaultToolbarButtons } from '@/registry/toolbar.js'; +import { DropPanelStore } from '@/components/index.js'; +import { SystemSettings } from './settings.js'; +import { TabBase, TabData } from 'rc-dock'; +import { defaultSpecifics } from '@/registry/specifics.js'; +import { iconsFactory } from '@/registry/icon.js'; +import { makeAutoObservable } from 'mobx'; +import React from 'react'; + +export type TabLoader = (tab: TabBase) => TabData | undefined; + +export interface ISystem { + /** + * Items to display in the drop panel. + * Not populating this will result in the default items being displayed. + */ + panelItems?: DropPanelStore; + /** + * Customize the controls that are displayed in the editor + */ + controls?: Control[]; + /** + * Additional specifics to display in the editor for custom types + */ + specifics?: Record< + string, + React.FC<{ + node: Node; + }> + >; + /** + * The loader for nodes to display in the editor + */ + nodeLoader: NodeLoader; + /** + * The loader for tabs to display in the editor + */ + tabLoader: TabLoader; + /** + * Capabilities to load into the graphs. Each factory is loaded into each graph individually. + */ + capabilities?: CapabilityFactory[]; + /** + * A lookup of the custom node ui types to display in the editor. + */ + customNodeUI?: Record; + /** + * An icon lookup to be used for legends, etc + */ + icons?: Record; + + settings?: SystemSettings; + + /** + * Additional buttons to display in the toolbar + */ + toolbarButtons?: React.ReactElement[]; +} + +export class System { + specifics!: Record< + string, + React.FC<{ + node: Node; + }> + >; + nodeLoader!: NodeLoader; + tabLoader!: TabLoader; + panelItems!: DropPanelStore; + capabilities!: CapabilityFactory[]; + customNodeUI!: Record; + controls!: Control[]; + settings!: SystemSettings; + icons!: Record; + toolbarButtons!: React.ReactElement[]; + + constructor(config: ISystem) { + const defaultConfig: Partial = { + specifics: defaultSpecifics, + panelItems: new DropPanelStore([]), + capabilities: [], + customNodeUI: {}, + controls: [], + icons: iconsFactory(), + settings: new SystemSettings({}), + toolbarButtons: DefaultToolbarButtons(), + }; + + Object.assign(this, defaultConfig, config); + makeAutoObservable(this); + } +} diff --git a/packages/graph-editor/src/system/settings.tsx b/packages/graph-editor/src/system/settings.tsx new file mode 100644 index 000000000..3527ecefc --- /dev/null +++ b/packages/graph-editor/src/system/settings.tsx @@ -0,0 +1,91 @@ +import { makeAutoObservable } from 'mobx'; + +export enum EdgeType { + bezier = 'Bezier', + smoothStep = 'Smooth step', + straight = 'Straight', + simpleBezier = 'Simple Bezier', +} +export enum LayoutType { + dagre = 'Dagre', + elkForce = 'Elk - Force', + elkRect = 'Elk - Rect', + elkLayered = 'Elk - Layered', + elkStress = 'Elk - Stress', +} + +export class SystemSettings { + edgeType: EdgeType = EdgeType.bezier; + layoutType: LayoutType = LayoutType.dagre; + debugMode: boolean = false; + showTimings: boolean = false; + showMinimap: boolean = false; + showGrid: boolean = true; + showSearch: boolean = false; + /** + * Whether to delay the update of a node when a value is changed + */ + delayedUpdate: boolean = false; + /** + * Whether to show the types inline with the nodes + */ + inlineTypes: boolean = false; + /** + * Whether to show the values inline with the nodes + */ + inlineValues: boolean = false; + connectOnClick: boolean = true; + snapGrid: boolean = false; + + constructor(config: Partial) { + makeAutoObservable(this); + Object.assign(this, config); + } + setEdgeType(edgeType: EdgeType) { + this.edgeType = edgeType; + } + + setLayoutType(layoutType: LayoutType) { + this.layoutType = layoutType; + } + + setDebugMode(debugMode: boolean) { + this.debugMode = debugMode; + } + + setShowTimings(showTimings: boolean) { + this.showTimings = showTimings; + } + + setShowMinimap(showMinimap: boolean) { + this.showMinimap = showMinimap; + } + + setShowGrid(showGrid: boolean) { + this.showGrid = showGrid; + } + + setShowSearch(showSearch: boolean) { + this.showSearch = showSearch; + } + + setDelayedUpdate(delayedUpdate: boolean) { + this.delayedUpdate = delayedUpdate; + } + + setInlineTypes(inlineTypes: boolean) { + this.inlineTypes = inlineTypes; + } + + setInlineValues(inlineValues: boolean) { + this.inlineValues = inlineValues; + } + + setConnectOnClick(connectOnClick: boolean) { + this.connectOnClick = connectOnClick; + } + + setSnapGrid(snapGrid: boolean) { + this.snapGrid = snapGrid; + } +} diff --git a/packages/nodes-design-tokens/src/ui/controls/tokenArray.tsx b/packages/nodes-design-tokens/src/ui/controls/tokenArray.tsx index 49d534d5d..5375f54d5 100644 --- a/packages/nodes-design-tokens/src/ui/controls/tokenArray.tsx +++ b/packages/nodes-design-tokens/src/ui/controls/tokenArray.tsx @@ -1,5 +1,9 @@ import { Accordion, Button, Separator, Stack } from '@tokens-studio/ui'; -import { IField, IResolvedToken, flatTokensRestoreToMap } from '@tokens-studio/graph-editor'; +import { + IField, + IResolvedToken, + flatTokensRestoreToMap +} from '@tokens-studio/graph-editor'; import { Token } from './token.js'; import { observer } from 'mobx-react-lite'; import React from 'react'; @@ -12,41 +16,43 @@ interface TokenArrayFieldInnerProps { value: IResolvedToken[]; } -export const TokenArrayFieldInner = observer(({ value }: TokenArrayFieldInnerProps) => { - const downloadTokens = () => { - const element = document.createElement('a'); - const asObj = flatTokensRestoreToMap(value); - const file = new Blob([JSON.stringify(asObj)], { - type: 'application/json' - }); - element.href = URL.createObjectURL(file); - element.download = 'tokens.json'; - document.body.appendChild(element); - element.click(); - }; +export const TokenArrayFieldInner = observer( + ({ value }: TokenArrayFieldInnerProps) => { + const downloadTokens = () => { + const element = document.createElement('a'); + const asObj = flatTokensRestoreToMap(value); + const file = new Blob([JSON.stringify(asObj)], { + type: 'application/json' + }); + element.href = URL.createObjectURL(file); + element.download = 'tokens.json'; + document.body.appendChild(element); + element.click(); + }; - return ( - - - - Tokens - - - - {(value || []).map(token => ( - - ))} - - - - + return ( + + + + Tokens + + + + {(value || []).map(token => ( + + ))} + + + + - - - ); -}); + + + ); + } +); diff --git a/packages/ui/src/components/editor/index.tsx b/packages/ui/src/components/editor/index.tsx index fbad68143..601792722 100644 --- a/packages/ui/src/components/editor/index.tsx +++ b/packages/ui/src/components/editor/index.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Editor } from '@tokens-studio/graph-editor'; +import { Editor, System } from '@tokens-studio/graph-editor'; import { EmptyStateEditor } from '../EmptyStateEditor.tsx'; import { ExamplesPicker } from '../ExamplesPicker.tsx'; import { @@ -8,7 +8,6 @@ import { controls, icons, menu, - nodeTypes, panelItems, specifics } from './data.tsx'; @@ -16,7 +15,7 @@ import { loadCompounds } from '@/data/compounds/index.tsx'; import { observer } from 'mobx-react-lite'; import { tabLoader } from './tabLoader.tsx'; import { useGetEditor } from '@/hooks/useGetEditor.ts'; -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import Spinner from '../spinner/index.tsx'; import globalState from '@/mobx/index.tsx'; import initialLayout from '@/data/layout/default.json'; @@ -24,13 +23,12 @@ import styles from './styles.module.css'; import type { LayoutBase } from 'rc-dock'; import type { ReactElement } from 'react'; - export const EditorTab = observer( ( { loading, toolbarButtons - }: { loading?: boolean; toolbarButtons?: ReactElement }, + }: { loading?: boolean; toolbarButtons?: ReactElement[] }, ref ) => { const { loadExample } = useGetEditor(); @@ -42,24 +40,29 @@ export const EditorTab = observer( globalState.ui.showExamplePicker = true; }, []); + const sys = useMemo(() => { + return new System({ + specifics, + panelItems, + tabLoader, + nodeLoader: loadCompounds, + capabilities, + controls, + icons, + toolbarButtons + }); + }, []); + return (
} diff --git a/packages/ui/src/data/layout/default.json b/packages/ui/src/data/layout/default.json index 26bbd6bc3..255d66338 100644 --- a/packages/ui/src/data/layout/default.json +++ b/packages/ui/src/data/layout/default.json @@ -42,22 +42,11 @@ "size": 12, "tabs": [ { - "id": "input" + "id": "unifiedPorts" } ], "group": "popout", - "activeId": "input" - }, - { - "id": "+26", - "size": 12, - "tabs": [ - { - "id": "outputs" - } - ], - "group": "popout", - "activeId": "outputs" + "activeId": "unifiedPorts" } ] } From 83753c92b4293242690b9fac68bce36109c28c6b Mon Sep 17 00:00:00 2001 From: SorsOps <80043879+sorsOps@users.noreply.github.com> Date: Tue, 21 Jan 2025 14:30:00 +0200 Subject: [PATCH 04/10] Start implementing frames --- .changeset/giant-steaks-jump.md | 6 + .changeset/wild-cars-kiss.md | 16 ++ .../contextMenus/paneContextMenu.tsx | 14 +- .../src/components/controls/interface.tsx | 2 +- .../src/components/dialogs/findDialog.tsx | 8 +- .../src/components/flow/edges/edge.tsx | 8 +- .../components/flow/nodes/passthroughNode.tsx | 6 +- .../src/components/flow/wrapper/nodeV2.tsx | 22 +-- .../src/components/hotKeys/index.tsx | 14 +- .../components/panels/dropPanel/dropPanel.tsx | 6 +- .../src/components/panels/legend/index.tsx | 6 +- .../src/components/panels/settings/index.tsx | 8 +- .../src/components/portPanel/index.tsx | 10 +- .../src/components/toolbar/dropdowns/add.tsx | 8 +- .../src/components/toolbar/toolbar.tsx | 6 +- .../src/context/ExternalDataContext.tsx | 54 ------ .../src/context/ExternalLoaderContext.tsx | 28 --- packages/graph-editor/src/context/index.ts | 1 - packages/graph-editor/src/data/version.ts | 2 +- .../graph-editor/src/editor/editorTypes.ts | 12 -- packages/graph-editor/src/editor/graph.tsx | 63 +++--- .../src/editor/hooks/useAutolayout.ts | 6 +- .../src/editor/layoutController.tsx | 11 +- packages/graph-editor/src/index.tsx | 2 +- .../graph-editor/src/redux/models/registry.ts | 30 --- .../src/redux/selectors/registry.ts | 4 - .../graph-editor/src/system/frame/hook.tsx | 8 + .../graph-editor/src/system/frame/index.tsx | 104 ++++++++++ .../src/system/{ => frame}/settings.tsx | 0 packages/graph-editor/src/system/index.tsx | 109 +++-------- packages/ui/src/app/editor/page.tsx | 2 +- packages/ui/src/components/editor/index.tsx | 24 ++- packages/ui/src/components/editor/toolbar.tsx | 181 ------------------ .../ui/src/components/editor/toolbar/ai.tsx | 27 +++ .../src/components/editor/toolbar/index.tsx | 107 +++++++++++ .../src/components/editor/toolbar/share.tsx | 62 ++++++ 36 files changed, 458 insertions(+), 519 deletions(-) create mode 100644 .changeset/giant-steaks-jump.md create mode 100644 .changeset/wild-cars-kiss.md delete mode 100644 packages/graph-editor/src/context/ExternalDataContext.tsx delete mode 100644 packages/graph-editor/src/context/ExternalLoaderContext.tsx delete mode 100644 packages/graph-editor/src/context/index.ts create mode 100644 packages/graph-editor/src/system/frame/hook.tsx create mode 100644 packages/graph-editor/src/system/frame/index.tsx rename packages/graph-editor/src/system/{ => frame}/settings.tsx (100%) delete mode 100644 packages/ui/src/components/editor/toolbar.tsx create mode 100644 packages/ui/src/components/editor/toolbar/ai.tsx create mode 100644 packages/ui/src/components/editor/toolbar/index.tsx create mode 100644 packages/ui/src/components/editor/toolbar/share.tsx diff --git a/.changeset/giant-steaks-jump.md b/.changeset/giant-steaks-jump.md new file mode 100644 index 000000000..39fecd6e2 --- /dev/null +++ b/.changeset/giant-steaks-jump.md @@ -0,0 +1,6 @@ +--- +"@tokens-studio/graph-editor": major +--- + +Removed `initialGraph` from the editor. This can now be passed through with the Frame.graph option instead + diff --git a/.changeset/wild-cars-kiss.md b/.changeset/wild-cars-kiss.md new file mode 100644 index 000000000..529b3c565 --- /dev/null +++ b/.changeset/wild-cars-kiss.md @@ -0,0 +1,16 @@ +--- +"@tokens-studio/graph-editor": major +--- + +Removes the External Loader from the editor as this will likely be scoped per Frame + +You should instead attach your external loader directly to the graph you created when creating a frame + +```ts +const graph = new Graph(); + +graph.externaloLoader = myExternalLoader; + +const Frame = new Frame({ graph , ...etc}) + +``` diff --git a/packages/graph-editor/src/components/contextMenus/paneContextMenu.tsx b/packages/graph-editor/src/components/contextMenus/paneContextMenu.tsx index 12b62d453..f828b14f5 100644 --- a/packages/graph-editor/src/components/contextMenus/paneContextMenu.tsx +++ b/packages/graph-editor/src/components/contextMenus/paneContextMenu.tsx @@ -4,9 +4,9 @@ import { clear } from '../../editor/actions/clear.js'; import { useAction } from '@/editor/actions/provider.js'; import { useAutoLayout } from '../../editor/hooks/useAutolayout.js'; import { useDispatch } from '@/hooks/index.js'; +import { useFrame } from '@/system/frame/hook.js'; import { useLocalGraph } from '@/context/graph.js'; import { useReactFlow } from 'reactflow'; -import { useSystem } from '@/system/hook.js'; import React, { useCallback } from 'react'; export interface IPaneContextMenu { @@ -17,7 +17,7 @@ export interface IPaneContextMenu { export const PaneContextMenu = ({ id }: IPaneContextMenu) => { const reactFlowInstance = useReactFlow(); - const system = useSystem(); + const frame = useFrame(); const dispatch = useDispatch(); const graph = useLocalGraph(); const createNode = useAction('createNode'); @@ -50,12 +50,12 @@ export const PaneContextMenu = ({ id }: IPaneContextMenu) => { }, [reactFlowInstance]); const setShowGrid = useCallback(() => { - system.settings.setShowGrid(!system.settings.showGrid); - }, [system.settings]); + frame.settings.setShowGrid(!frame.settings.showGrid); + }, [frame.settings]); const setSnapGrid = useCallback(() => { - system.settings.setSnapGrid(!system.settings.snapGrid); - }, [system.settings]); + frame.settings.setSnapGrid(!frame.settings.snapGrid); + }, [frame.settings]); const clearCallback = useCallback(() => { clear(reactFlowInstance, graph); @@ -69,7 +69,7 @@ export const PaneContextMenu = ({ id }: IPaneContextMenu) => { Apply Layout - {system.settings.showGrid ? 'Hide' : 'Show'} Grid + {frame.settings.showGrid ? 'Hide' : 'Show'} Grid Recenter diff --git a/packages/graph-editor/src/components/controls/interface.tsx b/packages/graph-editor/src/components/controls/interface.tsx index 64ef95932..d05d7682d 100644 --- a/packages/graph-editor/src/components/controls/interface.tsx +++ b/packages/graph-editor/src/components/controls/interface.tsx @@ -1,5 +1,5 @@ import { Port } from '@tokens-studio/graph-engine'; -import { SystemSettings } from '@/system/settings.js'; +import { SystemSettings } from '@/system/frame/settings.js'; export interface IField { port: Port; readOnly?: boolean; diff --git a/packages/graph-editor/src/components/dialogs/findDialog.tsx b/packages/graph-editor/src/components/dialogs/findDialog.tsx index 5525503b2..07fad3b88 100644 --- a/packages/graph-editor/src/components/dialogs/findDialog.tsx +++ b/packages/graph-editor/src/components/dialogs/findDialog.tsx @@ -1,9 +1,9 @@ import { Button, Dialog, IconButton, Text, TextInput } from '@tokens-studio/ui'; import { title as annotatedTitle } from '@/annotations/index.js'; import { graphEditorSelector } from '@/redux/selectors/index.js'; +import { useFrame } from '@/system/frame/hook.js'; import { useGraph } from '@/hooks/index.js'; import { useSelector } from 'react-redux'; -import { useSystem } from '@/system/hook.js'; import React from 'react'; import Xmark from '@tokens-studio/icons/Xmark.js'; @@ -12,10 +12,10 @@ export const FindDialog = () => { const [title, setTitle] = React.useState(''); const localGraph = useGraph(); const graph = useSelector(graphEditorSelector); - const system = useSystem(); + const frame = useFrame(); const setOpen = (value: boolean) => { - system.settings.setShowSearch(value); + frame.settings.setShowSearch(value); }; const onClick = () => { @@ -62,7 +62,7 @@ export const FindDialog = () => { }; return ( - + diff --git a/packages/graph-editor/src/components/flow/edges/edge.tsx b/packages/graph-editor/src/components/flow/edges/edge.tsx index 5b0263068..879169763 100644 --- a/packages/graph-editor/src/components/flow/edges/edge.tsx +++ b/packages/graph-editor/src/components/flow/edges/edge.tsx @@ -1,4 +1,4 @@ -import { EdgeType, SystemSettings } from '@/system/settings.js'; +import { EdgeType, SystemSettings } from '@/system/frame/settings.js'; import { Port } from '@tokens-studio/graph-engine'; import { getBetterBezierPath } from './offsetBezier.js'; import { @@ -7,8 +7,8 @@ import { getStraightPath, } from 'reactflow'; import { observer } from 'mobx-react-lite'; +import { useFrame } from '@/system/frame/hook.js'; import { useLocalGraph } from '@/context/graph.js'; -import { useSystem } from '@/system/hook.js'; import React from 'react'; import colors from '@/tokens/colors.js'; @@ -27,8 +27,8 @@ const extractColor = (port: Port) => { }; export default (props) => { - const system = useSystem(); - return ; + const frame = useFrame(); + return ; }; export interface ICustomEdge { diff --git a/packages/graph-editor/src/components/flow/nodes/passthroughNode.tsx b/packages/graph-editor/src/components/flow/nodes/passthroughNode.tsx index e7f2faa7b..ea83df2dd 100644 --- a/packages/graph-editor/src/components/flow/nodes/passthroughNode.tsx +++ b/packages/graph-editor/src/components/flow/nodes/passthroughNode.tsx @@ -2,20 +2,20 @@ import { Handle } from '../handles.js'; import { HandleContainer } from '../handles.js'; import { Stack } from '@tokens-studio/ui'; import { extractType, extractTypeIcon } from '../wrapper/nodeV2.js'; +import { useFrame } from '@/system/frame/hook.js'; import { useLocalGraph } from '@/context/graph.js'; -import { useSystem } from '@/system/hook.js'; import React from 'react'; export const PassthroughNode = (args) => { const { id } = args; const graph = useLocalGraph(); const node = graph.getNode(id); - const system = useSystem(); + const frame = useFrame(); if (!node) return null; const port = node.inputs.value; - const typeCol = extractTypeIcon(port, system.icons); + const typeCol = extractTypeIcon(port, frame.icons); const type = extractType(port.type); return ( diff --git a/packages/graph-editor/src/components/flow/wrapper/nodeV2.tsx b/packages/graph-editor/src/components/flow/wrapper/nodeV2.tsx index 61c6731bb..0d7934c13 100644 --- a/packages/graph-editor/src/components/flow/wrapper/nodeV2.tsx +++ b/packages/graph-editor/src/components/flow/wrapper/nodeV2.tsx @@ -17,11 +17,11 @@ import { ErrorBoundaryContent } from '@/components/ErrorBoundaryContent.js'; import { Node as GraphNode } from '@tokens-studio/graph-engine'; import { Handle, HandleContainer, useHandle } from '../handles.js'; import { Stack, Text } from '@tokens-studio/ui'; -import { SystemSettings } from '@/system/settings.js'; +import { SystemSettings } from '@/system/frame/settings.js'; import { observer } from 'mobx-react-lite'; import { title } from '@/annotations/index.js'; +import { useFrame } from '@/system/frame/hook.js'; import { useLocalGraph } from '@/context/graph.js'; -import { useSystem } from '@/system/hook.js'; import React from 'react'; import clsx from 'clsx'; import colors from '@/tokens/colors.js'; @@ -52,7 +52,7 @@ export const NodeV2 = (args) => { const { id } = args; const graph = useLocalGraph(); const node = graph.getNode(id); - const system = useSystem(); + const frame = useFrame(); if (!node) { return
Node not found
; @@ -63,7 +63,7 @@ export const NodeV2 = (args) => { ); @@ -110,7 +110,7 @@ export const SpecificWrapper = observer( ); const NodeWrap = observer(({ settings, node, icon }: INodeWrap) => { - const system = useSystem(); + const frame = useFrame(); //Check if the input allows for dynamic inputs const isInput = !!( @@ -145,7 +145,7 @@ const NodeWrap = observer(({ settings, node, icon }: INodeWrap) => { - + {settings.showTimings && (
@@ -163,7 +163,7 @@ export interface IPortArray { hideNames?: boolean; } export const PortArray = observer(({ ports, hideNames }: IPortArray) => { - const system = useSystem(); + const frame = useFrame(); const entries = Object.values(ports).sort(); return ( <> @@ -173,8 +173,8 @@ export const PortArray = observer(({ ports, hideNames }: IPortArray) => { ))} @@ -349,8 +349,8 @@ export interface IInputHandle { const InputHandle = observer( ({ port, hideName, inlineTypes, inlineValues }: IInputHandle) => { - const system = useSystem(); - const typeCol = extractTypeIcon(port, system.icons); + const frame = useFrame(); + const typeCol = extractTypeIcon(port, frame.icons); const input = port as unknown as Input; const type = extractType(port.type); const handleInformation = useHandle(); diff --git a/packages/graph-editor/src/components/hotKeys/index.tsx b/packages/graph-editor/src/components/hotKeys/index.tsx index 8f3ec8c29..882c32ccb 100644 --- a/packages/graph-editor/src/components/hotKeys/index.tsx +++ b/packages/graph-editor/src/components/hotKeys/index.tsx @@ -5,10 +5,10 @@ import { savedViewports } from '@/annotations/index.js'; import { useAction } from '@/editor/actions/provider.js'; import { useAutoLayout } from '@/editor/hooks/useAutolayout.js'; import { useDispatch } from '@/hooks/useDispatch.js'; +import { useFrame } from '@/system/frame/hook.js'; import { useLocalGraph } from '@/hooks/index.js'; import { useMemo } from 'react'; import { useReactFlow } from 'reactflow'; -import { useSystem } from '@/system/hook.js'; import { useToast } from '@/hooks/useToast.js'; import React from 'react'; import copy from 'copy-to-clipboard'; @@ -73,7 +73,7 @@ export const getViewports = (graph) => { }; export const useHotkeys = () => { - const system = useSystem(); + const frame = useFrame(); const duplicateNodes = useAction('duplicateNodes'); const deleteNode = useAction('deleteNode'); const copyNodes = useAction('copyNodes'); @@ -223,20 +223,20 @@ export const useHotkeys = () => { }, TOGGLE_GRID: (event) => { event.preventDefault(); - system.settings.setShowGrid(!system.settings.showGrid); + frame.settings.setShowGrid(!frame.settings.showGrid); }, FIND: (event) => { event.preventDefault(); dispatch.settings.setShowSearch(true); }, TOGGLE_SNAP_GRID: () => { - system.settings.setSnapGrid(!system.settings.snapGrid); + frame.settings.setSnapGrid(!frame.settings.snapGrid); }, TOGGLE_TYPES: () => { - system.settings.setInlineTypes(!system.settings.inlineTypes); + frame.settings.setInlineTypes(!frame.settings.inlineTypes); }, TOGGLE_VALUES: () => { - dispatch.settings.setInlineValues(!system.settings.inlineValues); + dispatch.settings.setInlineValues(!frame.settings.inlineValues); }, }), @@ -248,7 +248,7 @@ export const useHotkeys = () => { graph, layout, reactFlowInstance, - system.settings, + frame.settings, trigger, ], ); diff --git a/packages/graph-editor/src/components/panels/dropPanel/dropPanel.tsx b/packages/graph-editor/src/components/panels/dropPanel/dropPanel.tsx index dd494e9f6..b5b71f924 100644 --- a/packages/graph-editor/src/components/panels/dropPanel/dropPanel.tsx +++ b/packages/graph-editor/src/components/panels/dropPanel/dropPanel.tsx @@ -2,7 +2,7 @@ import { Accordion, Stack, TextInput } from '@tokens-studio/ui'; import { DragItem } from './DragItem.js'; import { DropPanelStore } from './data.js'; import { observer } from 'mobx-react-lite'; -import { useSystem } from '@/system/hook.js'; +import { useFrame } from '@/system/frame/hook.js'; import NavArrowRight from '@tokens-studio/icons/NavArrowRight.js'; import React, { useState } from 'react'; import styles from './dropPanel.module.css'; @@ -22,8 +22,8 @@ export interface IDropPanel { } export const DropPanel = () => { - const sys = useSystem(); - return ; + const frame = useFrame(); + return ; }; export const DropPanelInner = observer(({ data }: IDropPanel) => { diff --git a/packages/graph-editor/src/components/panels/legend/index.tsx b/packages/graph-editor/src/components/panels/legend/index.tsx index a7f4f8ad9..b27d1159f 100644 --- a/packages/graph-editor/src/components/panels/legend/index.tsx +++ b/packages/graph-editor/src/components/panels/legend/index.tsx @@ -1,11 +1,11 @@ import { Stack, Text } from '@tokens-studio/ui'; -import { useSystem } from '@/system/hook.js'; +import { useFrame } from '@/system/frame/hook.js'; import React, { useMemo } from 'react'; import colors from '@/tokens/colors.js'; export const Legend = () => { - const system = useSystem(); - return ; + const frame = useFrame(); + return ; }; export interface ILegendInner { diff --git a/packages/graph-editor/src/components/panels/settings/index.tsx b/packages/graph-editor/src/components/panels/settings/index.tsx index 624bef8a8..04b14d41a 100644 --- a/packages/graph-editor/src/components/panels/settings/index.tsx +++ b/packages/graph-editor/src/components/panels/settings/index.tsx @@ -1,19 +1,19 @@ import { Checkbox, Label, Select, Stack, Text } from '@tokens-studio/ui'; -import { EdgeType, LayoutType, SystemSettings } from '@/system/settings.js'; +import { EdgeType, LayoutType, SystemSettings } from '@/system/frame/settings.js'; import { contextMenuSelector } from '@/redux/selectors/ui.js'; import { observer } from 'mobx-react-lite'; import { useDispatch } from '@/hooks/useDispatch.js'; +import { useFrame } from '@/system/frame/hook.js'; import { useSelector } from 'react-redux'; -import { useSystem } from '@/system/hook.js'; import React from 'react'; const EdgeValues = Object.values(EdgeType); const LayoutValues = Object.values(LayoutType); export const Settings = () => { - const system = useSystem(); + const frame = useFrame(); - return ; + return ; }; export const SettingsInner = observer( diff --git a/packages/graph-editor/src/components/portPanel/index.tsx b/packages/graph-editor/src/components/portPanel/index.tsx index 61088c06d..aa5b78ee9 100644 --- a/packages/graph-editor/src/components/portPanel/index.tsx +++ b/packages/graph-editor/src/components/portPanel/index.tsx @@ -11,8 +11,8 @@ import { InlineTypeLabel } from '@/components/flow/index.js'; import { Input } from '@tokens-studio/graph-engine'; import { deletable, hidden, resetable } from '@/annotations/index.js'; import { observer } from 'mobx-react-lite'; +import { useFrame } from '@/system/frame/hook.js'; import { useGraph } from '@/hooks/useGraph.js'; -import { useSystem } from '@/system/hook.js'; import Download from '@tokens-studio/icons/Download.js'; import Eye from '@tokens-studio/icons/Eye.js'; import EyeClosed from '@tokens-studio/icons/EyeClosed.js'; @@ -49,22 +49,22 @@ export const PortPanel = observer(({ ports, readOnly }: IPortPanel) => { export const Port = observer(({ port, readOnly: isReadOnly }: IPort) => { const readOnly = isReadOnly || port.isConnected; - const sys = useSystem(); + const frame = useFrame(); const graph = useGraph(); const isInput = 'studio.tokens.generic.input' === port.node.factory.type; const isDynamicInput = Boolean(port.annotations[deletable]); const resettable = Boolean(port.annotations[resetable]); const inner = useMemo(() => { - const field = sys.controls.find((x) => x.matcher(port, { readOnly })); + const field = frame.controls.find((x) => x.matcher(port, { readOnly })); const Component = field?.component as React.FC; return ( - + ); //We use an explicit dependency on the type // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sys.controls, port, readOnly, port.type]); + }, [frame.controls, port, readOnly, port.type]); const onClick = useCallback(() => { port.setVisible(!port.visible); diff --git a/packages/graph-editor/src/components/toolbar/dropdowns/add.tsx b/packages/graph-editor/src/components/toolbar/dropdowns/add.tsx index 79c0e7f82..57cfac7d9 100644 --- a/packages/graph-editor/src/components/toolbar/dropdowns/add.tsx +++ b/packages/graph-editor/src/components/toolbar/dropdowns/add.tsx @@ -1,15 +1,15 @@ import { Button, DropdownMenu, Stack, Tooltip } from '@tokens-studio/ui'; import { useAction } from '@/editor/actions/provider.js'; import { useDispatch } from '@/hooks/index.js'; +import { useFrame } from '@/system/frame/hook.js'; import { useReactFlow } from 'reactflow'; import { useSelectAddedNodes } from '@/hooks/useSelectAddedNodes.js'; -import { useSystem } from '@/system/hook.js'; import NavArrowRight from '@tokens-studio/icons/NavArrowRight.js'; import Plus from '@tokens-studio/icons/Plus.js'; import React, { useCallback } from 'react'; export const AddDropdown = () => { - const sys = useSystem(); + const frame = useFrame(); const createNode = useAction('createNode'); const reactFlowInstance = useReactFlow(); const dispatch = useDispatch(); @@ -54,7 +54,7 @@ export const AddDropdown = () => { ); const nodes = React.useMemo(() => { - return sys.panelItems.groups.map((group) => { + return frame.panelItems.groups.map((group) => { return ( @@ -81,7 +81,7 @@ export const AddDropdown = () => { ); }); - }, [sys.panelItems.groups, addNode]); + }, [frame.panelItems.groups, addNode]); return ( diff --git a/packages/graph-editor/src/components/toolbar/toolbar.tsx b/packages/graph-editor/src/components/toolbar/toolbar.tsx index a89f9443f..76cf0b74b 100644 --- a/packages/graph-editor/src/components/toolbar/toolbar.tsx +++ b/packages/graph-editor/src/components/toolbar/toolbar.tsx @@ -1,12 +1,12 @@ import * as Toolbar from '@radix-ui/react-toolbar'; -import { useSystem } from '@/system/hook.js'; +import { useFrame } from '@/system/frame/hook.js'; import React from 'react'; import styles from './toolbar.module.css'; export const GraphToolbar = () => { - const system = useSystem(); + const frame = useFrame(); return ( - {system.toolbarButtons} + {frame.toolbarButtons} ); }; diff --git a/packages/graph-editor/src/context/ExternalDataContext.tsx b/packages/graph-editor/src/context/ExternalDataContext.tsx deleted file mode 100644 index 773bcb971..000000000 --- a/packages/graph-editor/src/context/ExternalDataContext.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React, { createContext, useContext, useMemo } from 'react'; - -interface ExternalSetData { - tokens: Record[]; -} - -export interface EditorExternalSet { - name: string; - identifier: string; -} - -type ExternalDataContextType = { - tokenSets?: EditorExternalSet[]; - loadSetTokens: (identifier: string) => Promise; -}; - -const ExternalDataContext = createContext({ - tokenSets: undefined, - loadSetTokens: async () => ({ tokens: [] }), -}); - -function ExternalDataContextProvider({ - children, - tokenSets, - loadSetTokens, -}: { - children: React.ReactNode; - tokenSets?: EditorExternalSet[]; - loadSetTokens: (identifier: string) => Promise; -}) { - const providerValue = useMemo( - () => ({ tokenSets, loadSetTokens }), - [tokenSets, loadSetTokens], - ); - - return ( - - {children} - - ); -} - -function useExternalData() { - const context = useContext(ExternalDataContext); - - if (context === undefined) { - console.error( - 'useExternalData must be used within a ExternalDataContextProvider', - ); - } - return context; -} - -export { ExternalDataContextProvider, useExternalData }; diff --git a/packages/graph-editor/src/context/ExternalLoaderContext.tsx b/packages/graph-editor/src/context/ExternalLoaderContext.tsx deleted file mode 100644 index 1c01083e2..000000000 --- a/packages/graph-editor/src/context/ExternalLoaderContext.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { ExternalLoader } from '@tokens-studio/graph-engine'; -import React, { createContext, useContext } from 'react'; - -const ExternalLoaderContext = createContext( - undefined, -); - -function ExternalLoaderProvider({ - children, - externalLoader, -}: { - children: React.ReactNode; - externalLoader?: ExternalLoader; -}) { - return ( - - {children} - - ); -} - -function useExternalLoader() { - const context = useContext(ExternalLoaderContext); - - return context; -} - -export { ExternalLoaderProvider, useExternalLoader }; diff --git a/packages/graph-editor/src/context/index.ts b/packages/graph-editor/src/context/index.ts deleted file mode 100644 index 36973211f..000000000 --- a/packages/graph-editor/src/context/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ExternalDataContext.js'; diff --git a/packages/graph-editor/src/data/version.ts b/packages/graph-editor/src/data/version.ts index 1a2aa36dc..0fad64366 100644 --- a/packages/graph-editor/src/data/version.ts +++ b/packages/graph-editor/src/data/version.ts @@ -1 +1 @@ -export const version = '4.3.11'; +export const version = '4.3.12'; diff --git a/packages/graph-editor/src/editor/editorTypes.ts b/packages/graph-editor/src/editor/editorTypes.ts index f69893a04..7b410ab99 100644 --- a/packages/graph-editor/src/editor/editorTypes.ts +++ b/packages/graph-editor/src/editor/editorTypes.ts @@ -1,5 +1,4 @@ import { - ExternalLoader, Graph, Node as GraphNode, SchemaObject, @@ -32,10 +31,6 @@ export interface EditorProps { emptyContent?: React.ReactNode; children?: React.ReactNode; onOutputChange?: (output: Record) => void; - /** - * An external loader to use for loading the graphs or node data - */ - externalLoader?: ExternalLoader; /** * Whether or not to show the menu */ @@ -55,17 +50,11 @@ export interface EditorProps { */ schemas?: SchemaObject[]; - /** - * Additional buttons to display in the toolbar - */ - toolbarButtons?: React.ReactElement; - /** * Additional colors to display in the editor for custom types */ typeColors?: Record; - initialGraph?: Graph; } export interface GraphEditorProps { @@ -83,7 +72,6 @@ export interface GraphEditorProps { */ nodeTypes?: Record; children?: React.ReactNode; - initialGraph?: SerializedGraph; } export type ImperativeEditorRef = { diff --git a/packages/graph-editor/src/editor/graph.tsx b/packages/graph-editor/src/editor/graph.tsx index 380cf0956..50e4bbe9d 100644 --- a/packages/graph-editor/src/editor/graph.tsx +++ b/packages/graph-editor/src/editor/graph.tsx @@ -72,11 +72,10 @@ import { } from '@/annotations/index.js'; import { duplicateNodes } from './actions/duplicate.js'; import { useContextMenu } from 'react-contexify'; -import { useExternalLoader } from '@/context/ExternalLoaderContext.js'; +import { useFrame } from '@/system/frame/hook.js'; import { useSelectAddedNodes } from '@/hooks/useSelectAddedNodes.js'; import { useSelector } from 'react-redux'; import { useSetCurrentNode } from '@/hooks/useSetCurrentNode.js'; -import { useSystem } from '@/system/hook.js'; import { version } from '@/data/version.js'; const snapGridCoords: SnapGrid = [16, 16]; @@ -102,42 +101,30 @@ export const EditorApp = React.forwardRef< ImperativeEditorRef, GraphEditorProps >((props: GraphEditorProps, ref) => { - const system = useSystem(); + const frame = useFrame(); const { id, children } = props; - const externalLoader = useExternalLoader(); const contextMenus = useSelector(contextMenuSelector); const reactFlowWrapper = useRef(null); const reactFlowInstance = useReactFlow(); const dispatch = useDispatch(); const { getIntersectingNodes } = reactFlowInstance; const store = useStoreApi(); - const initialGraph: Graph = useMemo(() => { - //Set defaults - const graph = new Graph(); - graph.annotations[title] = 'Untitled Graph'; - graph.annotations[description] = ''; - return graph; - }, []); - const [graph, setTheGraph] = useState(initialGraph); + + const [graph, setTheGraph] = useState(frame.graph); const internalRef = useRef(null); const activeGraphId = useSelector(currentPanelIdSelector); - //Update the external loader - useEffect(() => { - graph.externalLoader = externalLoader; - }, [graph, externalLoader]); - const iconLookup = useMemo(() => { - return system.panelItems.groups.reduce((acc, group) => { + return frame.panelItems.groups.reduce((acc, group) => { group.items.forEach((item) => { acc[item.type] = item.icon || group.icon; }); return acc; }, {}); - }, [system.panelItems.groups]); + }, [frame.panelItems.groups]); const refProxy = useCallback( (v) => { @@ -158,7 +145,7 @@ export const EditorApp = React.forwardRef< //Attach sideeffect listeners useEffect(() => { - system.capabilities.forEach((factory) => graph.registerCapability(factory)); + frame.capabilities.forEach((factory) => graph.registerCapability(factory)); graph.onFinalize('serialize', (serialized) => { const nodes = reactFlowInstance.getNodes(); @@ -355,7 +342,7 @@ export const EditorApp = React.forwardRef< // Create flow node types here, instead of the global scope to ensure that custom nodes added by the user are available in nodeTypes const fullNodeTypesRef = useRef({ - ...system.customNodeUI, + ...frame.customNodeUI, GenericNode: NodeV2, [PASSTHROUGH]: PassthroughNode, [EditorNodeTypes.GROUP]: groupNode, @@ -366,12 +353,12 @@ export const EditorApp = React.forwardRef< //Turn it into an O(1) lookup object return Object.fromEntries( Object.entries({ - ...system.customNodeUI, + ...frame.customNodeUI, [NOTE]: NOTE, 'studio.tokens.generic.preview': 'studio.tokens.generic.preview', }).map(([k]) => [k, k]), ); - }, [system.customNodeUI]); + }, [frame.customNodeUI]); const handleDeleteNode = useMemo(() => { return deleteNode(graph, dispatch, reactFlowInstance); @@ -382,7 +369,7 @@ export const EditorApp = React.forwardRef< createNode({ reactFlowInstance, graph, - nodeLoader: system.nodeLoader, + nodeLoader: frame.nodeLoader, iconLookup, customUI: customNodeMap, dropPanelPosition, @@ -391,7 +378,7 @@ export const EditorApp = React.forwardRef< [ reactFlowInstance, graph, - system.nodeLoader, + frame.nodeLoader, iconLookup, customNodeMap, dropPanelPosition, @@ -414,7 +401,7 @@ export const EditorApp = React.forwardRef< }, loadRaw: async (serializedGraph) => { if (internalRef.current) { - await graph.deserialize(serializedGraph, system.nodeLoader); + await graph.deserialize(serializedGraph, frame.nodeLoader); internalRef?.current.load(graph); } }, @@ -495,7 +482,7 @@ export const EditorApp = React.forwardRef< [ reactFlowInstance, graph, - system.nodeLoader, + frame.nodeLoader, setNodes, setEdges, dispatch.graph, @@ -505,12 +492,6 @@ export const EditorApp = React.forwardRef< useSetCurrentNode(); - useEffect(() => { - if (props.initialGraph) { - internalRef.current?.loadRaw(props.initialGraph); - } - }, []); - const onConnect = useMemo( () => connectNodes({ graph, setEdges, dispatch }), [dispatch, graph, setEdges], @@ -581,7 +562,7 @@ export const EditorApp = React.forwardRef< x: event.clientX, y: event.clientY, }); - const PassthroughFactory = await system.nodeLoader(PASSTHROUGH); + const PassthroughFactory = await frame.nodeLoader(PASSTHROUGH); const newNode = new PassthroughFactory({ graph, @@ -643,7 +624,7 @@ export const EditorApp = React.forwardRef< return [...filtered, newEdge, newEdge2]; }); }, - [reactFlowInstance, system, graph, setNodes, setEdges], + [reactFlowInstance, frame, graph, setNodes, setEdges], ); const onNodeDrag = useCallback( @@ -686,7 +667,7 @@ export const EditorApp = React.forwardRef< reactFlowInstance, }); - const copyNodes = copyNodeAction(reactFlowInstance, graph, system.nodeLoader); + const copyNodes = copyNodeAction(reactFlowInstance, graph, frame.nodeLoader); const selectAddedNodes = useSelectAddedNodes(); const onDrop = useCallback( @@ -750,10 +731,10 @@ export const EditorApp = React.forwardRef< onEdgeDoubleClick={onEdgeDblClick} onEdgesDelete={onEdgesDeleted} edges={edges} - connectOnClick={system.settings.connectOnClick} + connectOnClick={frame.settings.connectOnClick} elevateNodesOnSelect={true} onNodeDragStop={onNodeDragStop} - snapToGrid={system.settings.snapGrid} + snapToGrid={frame.settings.snapGrid} edgeTypes={edgeTypes} nodeTypes={fullNodeTypesRef.current} snapGrid={snapGridCoords} @@ -786,7 +767,7 @@ export const EditorApp = React.forwardRef< maxZoom={Infinity} proOptions={proOptions} > - {system.settings.showGrid && ( + {frame.settings.showGrid && ( )} @@ -808,7 +789,7 @@ export const EditorApp = React.forwardRef<
- {system.settings.showMinimap && } + {frame.settings.showMinimap && } { - const system = useSystem(); + const system = useFrame(); const dagreAutoLayout = useDagreLayout(); return useCallback(() => { switch (system.settings.layoutType) { diff --git a/packages/graph-editor/src/editor/layoutController.tsx b/packages/graph-editor/src/editor/layoutController.tsx index 122fa814b..906a40d43 100644 --- a/packages/graph-editor/src/editor/layoutController.tsx +++ b/packages/graph-editor/src/editor/layoutController.tsx @@ -12,8 +12,8 @@ import { DropPanel } from '@/components/panels/dropPanel/dropPanel.js'; import { EditorProps, ImperativeEditorRef } from './editorTypes.js'; import { ErrorBoundary } from 'react-error-boundary'; import { ErrorBoundaryContent } from '@/components/ErrorBoundaryContent.js'; -import { ExternalLoaderProvider } from '@/context/ExternalLoaderContext.js'; import { FindDialog } from '@/components/dialogs/findDialog.js'; +import { FrameContext } from '@/system/frame/hook.js'; import { GraphEditor } from './graphEditor.js'; import { IconButton, Stack, Tooltip } from '@tokens-studio/ui'; import { MAIN_GRAPH_ID } from '@/constants.js'; @@ -278,7 +278,6 @@ export const LayoutController = React.forwardRef< EditorProps >((props: EditorProps, ref) => { const { - externalLoader, initialLayout, menuItems = defaultMenuDataFactory(), } = props; @@ -309,9 +308,7 @@ export const LayoutController = React.forwardRef< const onLayoutChange = (newLayout: LayoutBase) => { //We need to find the graph tab container in the newlayout - const graphContainer = findGraphPanel(newLayout); - - console.log(graphContainer); + const graphContainer = findGraphPanel(newLayout) if (graphContainer?.activeId) { //Get the active Id to find the currently selected graph @@ -320,7 +317,7 @@ export const LayoutController = React.forwardRef< }; return ( - + - + ); }); LayoutController.displayName = 'LayoutController'; diff --git a/packages/graph-editor/src/index.tsx b/packages/graph-editor/src/index.tsx index fa92a3a34..5a56a4dc9 100644 --- a/packages/graph-editor/src/index.tsx +++ b/packages/graph-editor/src/index.tsx @@ -3,7 +3,6 @@ export * from './utils/index.js'; export * from './types/index.js'; export * from './redux/index.js'; export * from './redux/selectors/index.js'; -export * from './context/index.js'; export * from './editor/graph.js'; export * from './editor/index.js'; export * from './editor/editorTypes.js'; @@ -21,3 +20,4 @@ export * from './registry/toolbar.js'; export * from './types/index.js'; export * from './system/index.js'; +export * from './system/frame/index.js'; diff --git a/packages/graph-editor/src/redux/models/registry.ts b/packages/graph-editor/src/redux/models/registry.ts index c276337fc..50a97aa59 100644 --- a/packages/graph-editor/src/redux/models/registry.ts +++ b/packages/graph-editor/src/redux/models/registry.ts @@ -1,12 +1,8 @@ import { AllSchemas, Node, - NodeLoader, SchemaObject, } from '@tokens-studio/graph-engine'; -import { DefaultToolbarButtons } from '@/registry/toolbar.js'; -import { DropPanelStore } from '@/components/panels/dropPanel/index.js'; -import { ReactElement } from 'react'; import { RootModel } from './root.js'; import { createModel } from '@rematch/core'; @@ -14,14 +10,12 @@ import { inputControls } from '@/registry/inputControls.js'; export interface RegistryState { inputControls: Record>; - toolbarButtons: ReactElement[]; schemas: SchemaObject[]; } export const registryState = createModel()({ state: { inputControls: { ...inputControls }, - toolbarButtons: DefaultToolbarButtons(), schemas: AllSchemas, } as RegistryState, reducers: { @@ -31,29 +25,5 @@ export const registryState = createModel()({ schemas, }; }, - setNodeTypes: (state, nodeTypes: NodeLoader) => { - return { - ...state, - nodeTypes, - }; - }, - registerInputControl( - state, - payload: { key: string; value: React.FC<{ node: Node }> }, - ) { - return { - ...state, - inputControls: { - ...state.inputControls, - [payload.key]: payload.value, - }, - }; - }, - setPanelItems(state, payload: DropPanelStore) { - return { - ...state, - panelItems: payload, - }; - }, }, }); diff --git a/packages/graph-editor/src/redux/selectors/registry.ts b/packages/graph-editor/src/redux/selectors/registry.ts index cfdad6d63..5e2db078a 100644 --- a/packages/graph-editor/src/redux/selectors/registry.ts +++ b/packages/graph-editor/src/redux/selectors/registry.ts @@ -6,10 +6,6 @@ export const inputControls = createSelector( (state) => state.inputControls, ); -export const ToolBarButtonsSelector = createSelector( - registry, - (state) => state.toolbarButtons, -); export const SchemaSelector = createSelector( registry, diff --git a/packages/graph-editor/src/system/frame/hook.tsx b/packages/graph-editor/src/system/frame/hook.tsx new file mode 100644 index 000000000..39c4786bc --- /dev/null +++ b/packages/graph-editor/src/system/frame/hook.tsx @@ -0,0 +1,8 @@ +import { Frame } from './index.js'; +import { createContext, useContext } from 'react'; + +export const FrameContext = createContext(undefined); + +export const useFrame = (): Frame => { + return useContext(FrameContext)!; +}; diff --git a/packages/graph-editor/src/system/frame/index.tsx b/packages/graph-editor/src/system/frame/index.tsx new file mode 100644 index 000000000..afed16062 --- /dev/null +++ b/packages/graph-editor/src/system/frame/index.tsx @@ -0,0 +1,104 @@ +import { + CapabilityFactory, + Graph, + Node, + NodeLoader, +} from '@tokens-studio/graph-engine'; +import { Control } from '@/types/controls.js'; +import { DefaultToolbarButtons } from '@/registry/toolbar.js'; +import { DropPanelStore } from '@/components/index.js'; +import { SystemSettings } from './settings.js'; +import { defaultSpecifics } from '@/registry/specifics.js'; +import { iconsFactory } from '@/registry/icon.js'; +import { makeAutoObservable } from 'mobx'; +import React from 'react'; + + +export interface IFrame { + /** + * The underlying graph to use for the frame + */ + graph :Graph; + /** + * Items to display in the drop panel. + * Not populating this will result in the default items being displayed. + */ + panelItems?: DropPanelStore; + /** + * Customize the controls that are displayed in the editor + */ + controls?: Control[]; + /** + * Additional specifics to display in the editor for custom types + */ + specifics?: Record< + string, + React.FC<{ + node: Node; + }> + >; + /** + * The loader for nodes to display in the editor + */ + nodeLoader: NodeLoader; + + /** + * Capabilities to load into the graphs. Each factory is loaded into each graph individually. + */ + capabilities?: CapabilityFactory[]; + /** + * A lookup of the custom node ui types to display in the editor. + */ + customNodeUI?: Record; + /** + * An icon lookup to be used for legends, etc + */ + icons?: Record; + + settings?: SystemSettings; + + /** + * Additional buttons to display in the toolbar + */ + toolbarButtons?: React.ReactElement[]; +} + +export class Frame { + specifics!: Record< + string, + React.FC<{ + node: Node; + }> + >; + nodeLoader!: NodeLoader; + graph!: Graph; + panelItems!: DropPanelStore; + capabilities!: CapabilityFactory[]; + customNodeUI!: Record; + controls!: Control[]; + settings!: SystemSettings; + icons!: Record; + toolbarButtons!: React.ReactElement[]; + + constructor(config: IFrame) { + const defaultConfig: Partial