UNPKG

@nodescript/core

Version:

Visual programming language for Browser and Node

269 lines 8.29 kB
import { PropSpecSchema } from '../schema/PropSpec.js'; import { clone } from '../util/clone.js'; import { createSubgraphModuleSpec } from '../util/graph.js'; import { GraphView } from './GraphView.js'; import { PropView } from './PropView.js'; export class NodeView { /** * Returns nodes in right-to-left order based on topology. */ static orderNodes(nodes) { const result = [...nodes]; for (let i = 0; i < result.length; i++) { const node = result[i]; for (const link of node.inboundLinks()) { const index = result.findIndex(_ => _.nodeUid === link.linkNode.nodeUid); if (index > -1 && index < i) { result.splice(i, 1); result.splice(index, 0, node); i = index; } } } return result; } constructor(graph, localId, nodeSpec) { this.graph = graph; this.localId = localId; this.nodeSpec = nodeSpec; this._moduleSpec = null; const { ref } = nodeSpec; if (ref === '@system/Result') { this.nodeSpec.ref = '@system/Output'; } this._nodeUid = this.graph.scopeId + ':' + localId; } toJSON() { return clone(this.nodeSpec); } get loader() { return this.graph.loader; } get ref() { return this.nodeSpec.ref; } get metadata() { return this.nodeSpec.metadata; } get nodeUid() { return this._nodeUid; } isParamNode() { return this.ref === '@system/Param'; } isOutputNode() { return this.ref === '@system/Output'; } isFrameNode() { return this.ref === '@system/Frame'; } isCommentNode() { return this.ref === '@system/Comment'; } async reloadModuleSpec() { this._moduleSpec = await this.loader.loadModule(this.ref); } getModuleSpec() { if (!this._moduleSpec) { this._moduleSpec = this.loader.resolveModule(this.ref); } return this._moduleSpec; } isRoot() { return this.graph.rootNodeId === this.localId; } supportsSubgraph() { const { subgraph } = this.getModuleSpec(); return !!subgraph; } getSubgraph() { const { subgraph } = this.getModuleSpec(); if (!subgraph) { return null; } const { nodes = {}, rootNodeId = '', metadata = {} } = this.nodeSpec.subgraph ?? {}; const moduleSpec = createSubgraphModuleSpec(subgraph); return new GraphView(this.loader, { moduleSpec, nodes, rootNodeId, metadata, }, this); } getProps() { const props = []; for (const key of Object.keys(this.getModuleSpec().params)) { const prop = this.getProp(key); if (prop) { props.push(prop); } } return props; } getProp(key) { const paramSpec = this.getModuleSpec().params[key]; if (!paramSpec) { return null; } const propSpec = this.nodeSpec.props[key] ?? PropSpecSchema.create({}); return new PropView(this, key, propSpec); } getDefaultProp() { const firstProp = this.getProps()[0]; // TODO add support to customize via ModuleSpec return firstProp ?? null; } isExpanded() { for (const _ of this.expandedLines()) { return true; } return false; } *allLines() { for (const prop of this.getProps()) { yield prop; if (prop.isUsesEntries()) { for (const entry of prop.getEntries()) { yield entry; } } } } *effectiveLines() { for (const prop of this.getProps()) { if (prop.isUsesEntries()) { for (const entry of prop.getEntries()) { yield entry; } } else { yield prop; } } } *expandedLines() { for (const line of this.effectiveLines()) { if (line.isExpanded()) { yield line; } } } getOutboundLinks(linkMap = this.graph.computeLinkMap()) { return linkMap.get(this.localId); } *inboundLinks() { for (const prop of this.getProps()) { const linkNode = prop.getLinkNode(); if (linkNode) { yield { node: this, linkNode, prop, linkKey: prop.linkKey, }; } if (prop.isSupportsEntries()) { for (const entry of prop.getEntries()) { const linkNode = entry.getLinkNode(); if (linkNode) { yield { node: this, linkNode, prop, entry, linkKey: entry.linkKey, }; } } } } } /** * Returns this node, plus all the nodes connect to its inbound sockets, recursively. * * This can also be thought of as a list of node's transitive dependencies. */ *leftNodes(visited = new Set()) { if (visited.has(this.localId)) { return; } visited.add(this.localId); yield this; for (const link of this.inboundLinks()) { yield* link.linkNode.leftNodes(visited); } } /** * Returns this node, plus all the nodes connected to its outbound sockets, recursively. * * This can also be thought of as a list of node's dependents. */ *rightNodes(linkMap = this.graph.computeLinkMap(), visited = new Set()) { if (visited.has(this.localId)) { return; } visited.add(this.localId); yield this; for (const link of linkMap.get(this.localId)) { yield* link.node.rightNodes(linkMap, visited); } } /** * Determines whether a link can be created from this node result socket * into one of the specified `node` property socket. * This is based solely on graph topology and disallows loops. */ canLinkTo(node) { for (const n of this.leftNodes()) { if (n.localId === node.localId) { return false; } } return true; } /** * Determines whether Node is evaluated manually (by pressing a Play button) * or immediately in the editor. Has no effect outside of the editor. * * Manually evaluated nodes cascade up and takes precedence, i.e. if a graph contains at least one * node with `evalMode: manual`, then its own `evalMode` is also manual. * Same applies to subgraphs: if a subgraph contains manually evaluated nodes, * then its enclosing node is also manually evaluated. */ getEvalMode() { if (this.metadata.forceManualEval) { return 'manual'; } const evalMode = this.getModuleSpec().evalMode; if (evalMode === 'auto' && this.supportsSubgraph()) { // Subgraphs with manual evaluation take precedence return this.getSubgraph()?.getEvalMode() ?? 'auto'; } return evalMode; } /** * Returns `true` if the node itself or any of its left nodes are async, * i.e. have `moduleSpec.result.async: true`. */ isAsync() { if (this.metadata.async) { return true; } if (this.getModuleSpec().result.async) { return true; } for (const link of this.inboundLinks()) { if (link.linkNode.isAsync()) { return true; } } return false; } canDock() { return this.isParamNode() && this.getOutboundLinks().size === 1; } isDocked() { return this.canDock() && !!this.metadata.docked; } } //# sourceMappingURL=NodeView.js.map