diff --git a/.changeset/tame-bobcats-shop.md b/.changeset/tame-bobcats-shop.md new file mode 100644 index 000000000..bf9467591 --- /dev/null +++ b/.changeset/tame-bobcats-shop.md @@ -0,0 +1,7 @@ +--- +"@tokens-studio/graph-engine-nodes-design-tokens": patch +"@tokens-studio/graph-editor": patch +"@tokens-studio/graph-engine": patch +--- + +Better handling of input error for the external tokens node. diff --git a/packages/graph-editor/src/components/portPanel/index.tsx b/packages/graph-editor/src/components/portPanel/index.tsx index 4fcc84818..56dd4f065 100644 --- a/packages/graph-editor/src/components/portPanel/index.tsx +++ b/packages/graph-editor/src/components/portPanel/index.tsx @@ -2,10 +2,14 @@ import { DropdownMenu, IconButton, Label, + Message, Stack, Tooltip, } from '@tokens-studio/ui'; -import { Port as GraphPort } from '@tokens-studio/graph-engine'; +import { + Port as GraphPort, + annotatedInputError, +} from '@tokens-studio/graph-engine'; import { Input } from '@tokens-studio/graph-engine'; import { observer } from 'mobx-react-lite'; import { useSelector } from 'react-redux'; @@ -47,6 +51,9 @@ export const PortPanel = observer(({ ports, readOnly }: IPortPanel) => { export const Port = observer(({ port, readOnly: isReadOnly }: IField) => { const readOnly = isReadOnly || port.isConnected; const controlSelector = useSelector(controls); + const hasError = Boolean(port.annotations[annotatedInputError]); + const errorMessage = port.annotations[annotatedInputError]?.message; + const graph = useGraph(); const isInput = 'studio.tokens.generic.input' === port.node.factory.type; const isDynamicInput = Boolean(port.annotations[deletable]); @@ -166,6 +173,7 @@ export const Port = observer(({ port, readOnly: isReadOnly }: IField) => { {inner} + {hasError && {errorMessage}} ); }); diff --git a/packages/graph-engine/src/annotations/index.ts b/packages/graph-engine/src/annotations/index.ts index 81991fa96..76ffb0e37 100644 --- a/packages/graph-engine/src/annotations/index.ts +++ b/packages/graph-engine/src/annotations/index.ts @@ -42,3 +42,10 @@ export const annotatedNodeRunning = 'engine.nodeRunning'; * Unique id of the entity */ export const annotatedId = 'engine.id'; + +/** + * Indicates that the node failed to load resources + */ +export const annotatedInputError = 'engine.inputError'; + + diff --git a/packages/nodes-design-tokens/src/nodes/externalTokens.ts b/packages/nodes-design-tokens/src/nodes/externalTokens.ts index d7bb48141..6d1129d31 100644 --- a/packages/nodes-design-tokens/src/nodes/externalTokens.ts +++ b/packages/nodes-design-tokens/src/nodes/externalTokens.ts @@ -1,7 +1,8 @@ import { INodeDefinition, Node, - StringSchema + StringSchema, + annotatedInputError } from '@tokens-studio/graph-engine'; import { TokenSchema } from '../schemas/index.js'; import { arrayOf } from '../schemas/utils.js'; @@ -22,15 +23,37 @@ export default class ExternalTokensNode extends Node { }); } - async execute() { + override async execute() { const { uri } = this.getAllInputs(); if (!uri) { - this.outputs.tokenSet.set([]); - return; + throw new Error('No uri specified'); } const tokens = await this.load(uri); - this.outputs.tokenSet.set(tokens); + + if (!tokens) { + // set this so we can show a red border around the node to indicate an error + this.error = new Error('Failed to load tokens'); + if (this.inputs['uri']) { + // set this so we can show an error in the graph editor input sheet + this.inputs['uri'].annotations[annotatedInputError] = { + message: + 'Failed to load tokens. Check if the uri is valid and the set was not deleted or renamed.' + }; + } + } else { + if (this.outputs && this.outputs.tokenSet) { + this.outputs.tokenSet.set(tokens); + } + + // clear the error if there is one + if (this.error) { + this.error = null; + if (this.inputs['uri']) { + delete this.inputs['uri'].annotations[annotatedInputError]; + } + } + } } }