mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-12 07:12:54 +01:00
Merged Axel changes. Added dev docs for Cline
This commit is contained in:
378
dev-docs/reference/CODEBASE-MAP.md
Normal file
378
dev-docs/reference/CODEBASE-MAP.md
Normal file
@@ -0,0 +1,378 @@
|
||||
# OpenNoodl Codebase Quick Navigation
|
||||
|
||||
## 🗺️ Package Map
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ MONOREPO ROOT │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ package.json → Workspace config, global scripts │
|
||||
│ lerna.json → Monorepo management │
|
||||
│ scripts/ → Build and CI scripts │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────────────────┼───────────────────────────┐
|
||||
▼ ▼ ▼
|
||||
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
|
||||
│ EDITOR (GPL) │ │ RUNTIME (MIT) │ │ UI LIBRARY │
|
||||
│ noodl-editor │ │ noodl-runtime │ │ noodl-core-ui │
|
||||
│ │ │ │ │ │
|
||||
│ • Electron app │ │ • Node engine │ │ • React components│
|
||||
│ • React UI │ │ • Data flow │ │ • Storybook │
|
||||
│ • Property panels │ │ • Event system │ │ • Styling │
|
||||
└───────────────────┘ └───────────────────┘ └───────────────────┘
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌───────────────────┐
|
||||
│ │ VIEWER (MIT) │
|
||||
│ │ noodl-viewer-react│
|
||||
│ │ │
|
||||
│ │ • React runtime │
|
||||
│ │ • Visual nodes │
|
||||
│ │ • DOM handling │
|
||||
│ └───────────────────┘
|
||||
│
|
||||
▼
|
||||
┌───────────────────────────────────────────────────────────────────────┐
|
||||
│ PLATFORM LAYER │
|
||||
├───────────────────┬───────────────────┬───────────────────────────────┤
|
||||
│ noodl-platform │ platform-electron │ platform-node │
|
||||
│ (abstraction) │ (desktop impl) │ (server impl) │
|
||||
└───────────────────┴───────────────────┴───────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 Key Directories
|
||||
|
||||
### noodl-editor (Main Application)
|
||||
|
||||
```
|
||||
packages/noodl-editor/src/
|
||||
├── editor/src/
|
||||
│ ├── models/ # 🎯 Business logic & data
|
||||
│ │ ├── projectmodel.ts → Project state
|
||||
│ │ ├── nodegraphmodel.ts → Graph structure
|
||||
│ │ ├── componentmodel.ts → Components
|
||||
│ │ ├── nodelibrary/ → Node type registry
|
||||
│ │ ├── AiAssistant/ → AI features
|
||||
│ │ └── sidebar/ → Sidebar state
|
||||
│ │
|
||||
│ ├── views/ # 🖥️ UI components
|
||||
│ │ ├── nodegrapheditor.ts → Canvas/graph editor
|
||||
│ │ ├── panels/ → Property panels
|
||||
│ │ ├── NodePicker/ → Node creation UI
|
||||
│ │ ├── documents/ → Document views
|
||||
│ │ └── popups/ → Modal dialogs
|
||||
│ │
|
||||
│ ├── utils/ # 🔧 Utilities
|
||||
│ │ ├── CodeEditor/ → Monaco integration
|
||||
│ │ ├── filesystem.ts → File operations
|
||||
│ │ └── projectimporter.js → Import/export
|
||||
│ │
|
||||
│ ├── store/ # 💾 Persistent state
|
||||
│ │ └── AiAssistantStore.ts → AI settings
|
||||
│ │
|
||||
│ └── pages/ # 📄 Page components
|
||||
│ └── EditorPage/ → Main editor page
|
||||
│
|
||||
├── main/ # ⚡ Electron main process
|
||||
│ └── main.js → App entry point
|
||||
│
|
||||
└── shared/ # 🔗 Shared utilities
|
||||
└── utils/
|
||||
└── EventDispatcher.ts → Pub/sub system
|
||||
```
|
||||
|
||||
### noodl-runtime (Execution Engine)
|
||||
|
||||
```
|
||||
packages/noodl-runtime/
|
||||
├── src/
|
||||
│ ├── nodes/ # 📦 Node implementations
|
||||
│ │ └── std-library/
|
||||
│ │ ├── data/ → Data nodes (REST, DB, etc.)
|
||||
│ │ ├── logic/ → Logic nodes
|
||||
│ │ └── events/ → Event nodes
|
||||
│ │
|
||||
│ ├── node.js # Base node class
|
||||
│ ├── nodedefinition.js # Node definition API
|
||||
│ ├── noderegister.js # Node registry
|
||||
│ ├── nodescope.js # Component scope
|
||||
│ └── nodecontext.js # Runtime context
|
||||
│
|
||||
└── noodl-runtime.js # Main runtime entry
|
||||
```
|
||||
|
||||
### noodl-viewer-react (React Runtime)
|
||||
|
||||
```
|
||||
packages/noodl-viewer-react/src/
|
||||
├── nodes/ # 🎨 Visual nodes
|
||||
│ ├── basic/ → Group, Text, Image
|
||||
│ ├── controls/ → Button, Input, Checkbox
|
||||
│ ├── navigation/ → PageRouter, Page
|
||||
│ └── std-library/ → Standard library nodes
|
||||
│
|
||||
└── react-component-node.js # React node wrapper
|
||||
```
|
||||
|
||||
### noodl-core-ui (Component Library)
|
||||
|
||||
```
|
||||
packages/noodl-core-ui/src/
|
||||
├── components/
|
||||
│ ├── common/ # 🧩 Basic components
|
||||
│ │ ├── Icon/
|
||||
│ │ └── ActivityIndicator/
|
||||
│ │
|
||||
│ ├── inputs/ # 📝 Form controls
|
||||
│ │ ├── TextInput/
|
||||
│ │ ├── PrimaryButton/
|
||||
│ │ └── Checkbox/
|
||||
│ │
|
||||
│ ├── layout/ # 📐 Layout components
|
||||
│ │ ├── Box/
|
||||
│ │ ├── Container/
|
||||
│ │ └── Tabs/
|
||||
│ │
|
||||
│ ├── popups/ # 💬 Dialogs & menus
|
||||
│ │ ├── MenuDialog/
|
||||
│ │ └── PopupToolbar/
|
||||
│ │
|
||||
│ └── ai/ # 🤖 AI UI components
|
||||
│ ├── AiChatBox/
|
||||
│ └── AiChatMessage/
|
||||
│
|
||||
└── styles/ # 🎨 Global styles
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Finding Things
|
||||
|
||||
### Search Patterns
|
||||
|
||||
```bash
|
||||
# Find a file by name
|
||||
find packages/ -name "*NodeGraph*" -type f
|
||||
|
||||
# Find where a function is defined
|
||||
grep -rn "function processNode" packages/
|
||||
|
||||
# Find where something is imported/used
|
||||
grep -r "import.*from.*nodegraphmodel" packages/
|
||||
|
||||
# Find all usages of a component
|
||||
grep -r "<NodeEditor" packages/ --include="*.tsx"
|
||||
|
||||
# Find TODO/FIXME comments
|
||||
grep -rn "TODO\|FIXME" packages/noodl-editor/src
|
||||
```
|
||||
|
||||
### Common Search Targets
|
||||
|
||||
| Looking for... | Search pattern |
|
||||
|----------------|----------------|
|
||||
| Node definitions | `packages/noodl-runtime/src/nodes/` |
|
||||
| React visual nodes | `packages/noodl-viewer-react/src/nodes/` |
|
||||
| UI components | `packages/noodl-core-ui/src/components/` |
|
||||
| Models/state | `packages/noodl-editor/src/editor/src/models/` |
|
||||
| Property panels | `packages/noodl-editor/src/editor/src/views/panels/` |
|
||||
| Tests | `packages/noodl-editor/tests/` |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Commands
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Start everything
|
||||
npm run dev
|
||||
|
||||
# Start just the editor (faster)
|
||||
npm run start:editor
|
||||
|
||||
# Start Storybook (UI components)
|
||||
npm run start:storybook
|
||||
|
||||
# Start viewer dev server
|
||||
npm run start:viewer
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
# Build editor
|
||||
npm run build:editor
|
||||
|
||||
# Create distributable package
|
||||
npm run build:editor:pack
|
||||
|
||||
# Build cloud runtime
|
||||
npm run build:cloud-runtime
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run all editor tests
|
||||
npm run test:editor
|
||||
|
||||
# Run platform tests
|
||||
npm run test:platform
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
|
||||
```bash
|
||||
# Type check
|
||||
npx tsc --noEmit
|
||||
|
||||
# Lint
|
||||
npx eslint packages/noodl-editor/src
|
||||
|
||||
# Format
|
||||
npx prettier --write "packages/**/*.{ts,tsx}"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Key Files Reference
|
||||
|
||||
### Configuration
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `package.json` | Root workspace config |
|
||||
| `lerna.json` | Monorepo settings |
|
||||
| `tsconfig.json` | TypeScript config |
|
||||
| `.eslintrc.js` | Linting rules |
|
||||
| `.prettierrc` | Code formatting |
|
||||
|
||||
### Entry Points
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `noodl-editor/src/main/main.js` | Electron main process |
|
||||
| `noodl-editor/src/editor/src/index.js` | Renderer entry |
|
||||
| `noodl-runtime/noodl-runtime.js` | Runtime engine |
|
||||
| `noodl-viewer-react/index.js` | React runtime |
|
||||
|
||||
### Core Models
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `projectmodel.ts` | Project state management |
|
||||
| `nodegraphmodel.ts` | Graph data structure |
|
||||
| `componentmodel.ts` | Component definitions |
|
||||
| `nodelibrary.ts` | Node type registry |
|
||||
|
||||
### Important Views
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `nodegrapheditor.ts` | Main canvas editor |
|
||||
| `EditorPage.tsx` | Main page layout |
|
||||
| `NodePicker.tsx` | Node creation panel |
|
||||
| `PropertyEditor/` | Property panels |
|
||||
|
||||
---
|
||||
|
||||
## 🏷️ Type System
|
||||
|
||||
### Key Types (global.d.ts)
|
||||
|
||||
```typescript
|
||||
// TSFixme - Type escape hatch (TO BE REMOVED)
|
||||
type TSFixme = any;
|
||||
|
||||
// Node colors
|
||||
type NodeColor = 'data' | 'visual' | 'logic' | 'component' | 'javascript';
|
||||
|
||||
// CSS modules
|
||||
declare module '*.scss' {
|
||||
const styles: { readonly [key: string]: string };
|
||||
export default styles;
|
||||
}
|
||||
```
|
||||
|
||||
### Common Interfaces
|
||||
|
||||
```typescript
|
||||
// Node graph structures (nodegraphmodel.ts)
|
||||
interface NodeGraphNode {
|
||||
id: string;
|
||||
type: string;
|
||||
x: number;
|
||||
y: number;
|
||||
parameters: Record<string, any>;
|
||||
}
|
||||
|
||||
interface Connection {
|
||||
fromId: string;
|
||||
fromPort: string;
|
||||
toId: string;
|
||||
toPort: string;
|
||||
}
|
||||
|
||||
// Component structure (componentmodel.ts)
|
||||
interface ComponentModel {
|
||||
name: string;
|
||||
graph: NodeGraphModel;
|
||||
metadata: Record<string, any>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Path Aliases
|
||||
|
||||
```typescript
|
||||
// Configured in tsconfig.json
|
||||
@noodl-models/ → packages/noodl-editor/src/editor/src/models/
|
||||
@noodl-utils/ → packages/noodl-editor/src/editor/src/utils/
|
||||
@noodl-contexts/ → packages/noodl-editor/src/editor/src/contexts/
|
||||
@noodl-hooks/ → packages/noodl-editor/src/editor/src/hooks/
|
||||
@noodl-constants/ → packages/noodl-editor/src/editor/src/constants/
|
||||
@noodl-core-ui/ → packages/noodl-core-ui/src/
|
||||
@noodl/platform → packages/noodl-platform/src/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Common Issues
|
||||
|
||||
### Build Problems
|
||||
|
||||
```bash
|
||||
# Clear caches
|
||||
rm -rf node_modules/.cache
|
||||
rm -rf packages/*/node_modules/.cache
|
||||
|
||||
# Reinstall dependencies
|
||||
rm -rf node_modules
|
||||
npm install
|
||||
```
|
||||
|
||||
### TypeScript Errors
|
||||
|
||||
```bash
|
||||
# Check for circular dependencies
|
||||
npx madge --circular packages/noodl-editor/src
|
||||
```
|
||||
|
||||
### Electron Issues
|
||||
|
||||
```bash
|
||||
# Clear app data (macOS)
|
||||
rm -rf ~/Library/Application\ Support/OpenNoodl/
|
||||
|
||||
# Rebuild native modules
|
||||
npm run rebuild
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Quick reference card for OpenNoodl development. Print or pin to your IDE!*
|
||||
253
dev-docs/reference/COMMON-ISSUES.md
Normal file
253
dev-docs/reference/COMMON-ISSUES.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# Common Issues & Troubleshooting
|
||||
|
||||
Solutions to frequently encountered problems when developing OpenNoodl.
|
||||
|
||||
## Build Issues
|
||||
|
||||
### "Module not found" Errors
|
||||
|
||||
**Symptom**: Build fails with `Cannot find module '@noodl-xxx/...'`
|
||||
|
||||
**Solutions**:
|
||||
1. Run `npm install` from root directory
|
||||
2. Check if package exists in `packages/`
|
||||
3. Verify tsconfig paths are correct
|
||||
4. Try: `rm -rf node_modules && npm install`
|
||||
|
||||
### "Peer dependency" Warnings
|
||||
|
||||
**Symptom**: npm install shows peer dependency warnings
|
||||
|
||||
**Solutions**:
|
||||
1. Check if versions are compatible
|
||||
2. Update the conflicting package
|
||||
3. Last resort: `npm install --legacy-peer-deps`
|
||||
4. Document why in CHANGELOG.md
|
||||
|
||||
### TypeScript Errors After Update
|
||||
|
||||
**Symptom**: Types that worked before now fail
|
||||
|
||||
**Solutions**:
|
||||
1. Run `npx tsc --noEmit` to see all errors
|
||||
2. Check if `@types/*` packages need updating
|
||||
3. Look for breaking changes in updated packages
|
||||
4. Check `tsconfig.json` for configuration issues
|
||||
|
||||
### Webpack Build Hangs
|
||||
|
||||
**Symptom**: Build starts but never completes
|
||||
|
||||
**Solutions**:
|
||||
1. Check for circular imports: `npx madge --circular packages/`
|
||||
2. Increase Node memory: `NODE_OPTIONS=--max_old_space_size=4096`
|
||||
3. Check for infinite loops in build scripts
|
||||
4. Try building individual packages
|
||||
|
||||
## Runtime Issues
|
||||
|
||||
### Hot Reload Not Working
|
||||
|
||||
**Symptom**: Changes don't appear without full restart
|
||||
|
||||
**Solutions**:
|
||||
1. Check webpack dev server is running
|
||||
2. Verify file is being watched (check webpack config)
|
||||
3. Clear browser cache
|
||||
4. Check for syntax errors preventing reload
|
||||
5. Restart dev server: `npm run dev`
|
||||
|
||||
### Node Not Appearing in Picker
|
||||
|
||||
**Symptom**: Created a node but it doesn't show up
|
||||
|
||||
**Solutions**:
|
||||
1. Verify node is exported in `nodelibraryexport.js`
|
||||
2. Check `category` is valid
|
||||
3. Verify no JavaScript errors in node definition
|
||||
4. Restart the editor
|
||||
|
||||
### "Cannot read property of undefined"
|
||||
|
||||
**Symptom**: Runtime error accessing object properties
|
||||
|
||||
**Solutions**:
|
||||
1. Add null checks: `obj?.property`
|
||||
2. Verify data is loaded before access
|
||||
3. Check async timing issues
|
||||
4. Add defensive initialization
|
||||
|
||||
### State Not Updating
|
||||
|
||||
**Symptom**: Changed input but output doesn't update
|
||||
|
||||
**Solutions**:
|
||||
1. Verify `flagOutputDirty()` is called
|
||||
2. Check if batching is interfering
|
||||
3. Verify connection exists in graph
|
||||
4. Check for conditional logic preventing update
|
||||
|
||||
## Editor Issues
|
||||
|
||||
### Preview Not Loading
|
||||
|
||||
**Symptom**: Preview panel is blank or shows error
|
||||
|
||||
**Solutions**:
|
||||
1. Check browser console for errors
|
||||
2. Verify viewer runtime is built
|
||||
3. Check for JavaScript errors in project
|
||||
4. Try creating a new empty project
|
||||
|
||||
### Property Panel Empty
|
||||
|
||||
**Symptom**: Selected node but no properties shown
|
||||
|
||||
**Solutions**:
|
||||
1. Verify node has `inputs` defined
|
||||
2. Check `group` values are set
|
||||
3. Look for errors in property panel code
|
||||
4. Verify node type is registered
|
||||
|
||||
### Canvas Performance Issues
|
||||
|
||||
**Symptom**: Node graph is slow/laggy
|
||||
|
||||
**Solutions**:
|
||||
1. Reduce number of visible nodes
|
||||
2. Check for expensive render operations
|
||||
3. Verify no infinite update loops
|
||||
4. Profile with Chrome DevTools
|
||||
|
||||
## Git Issues
|
||||
|
||||
### Merge Conflicts in package-lock.json
|
||||
|
||||
**Symptom**: Complex conflicts in lock file
|
||||
|
||||
**Solutions**:
|
||||
1. Accept either version
|
||||
2. Run `npm install` to regenerate
|
||||
3. Commit the regenerated lock file
|
||||
|
||||
### Large File Warnings
|
||||
|
||||
**Symptom**: Git warns about large files
|
||||
|
||||
**Solutions**:
|
||||
1. Check `.gitignore` includes build outputs
|
||||
2. Verify `node_modules` not committed
|
||||
3. Use Git LFS for large assets if needed
|
||||
|
||||
## Testing Issues
|
||||
|
||||
### Tests Timeout
|
||||
|
||||
**Symptom**: Tests hang or timeout
|
||||
|
||||
**Solutions**:
|
||||
1. Check for unresolved promises
|
||||
2. Verify mocks are set up correctly
|
||||
3. Increase timeout if legitimately slow
|
||||
4. Check for infinite loops
|
||||
|
||||
### Snapshot Tests Failing
|
||||
|
||||
**Symptom**: Snapshot doesn't match
|
||||
|
||||
**Solutions**:
|
||||
1. Review the diff carefully
|
||||
2. If change is intentional: `npm test -- -u`
|
||||
3. If unexpected, investigate component changes
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
### Enable Verbose Logging
|
||||
|
||||
```javascript
|
||||
// Add to see more info
|
||||
console.log('[DEBUG]', variable);
|
||||
|
||||
// For node execution
|
||||
this.context.debugLog('Message', data);
|
||||
```
|
||||
|
||||
### Use Chrome DevTools
|
||||
|
||||
1. Open editor
|
||||
2. Press `Cmd+Option+I` (Mac) or `Ctrl+Shift+I` (Windows)
|
||||
3. Check Console for errors
|
||||
4. Use Sources for breakpoints
|
||||
5. Use Network for API issues
|
||||
|
||||
### Inspect Node State
|
||||
|
||||
```javascript
|
||||
// In browser console
|
||||
const node = NoodlRuntime.instance.getNodeById('node-id');
|
||||
console.log(node._internal);
|
||||
```
|
||||
|
||||
### Check Event Flow
|
||||
|
||||
```javascript
|
||||
// Add listener to see all events
|
||||
model.on('*', (event, data) => {
|
||||
console.log('Event:', event, data);
|
||||
});
|
||||
```
|
||||
|
||||
## Error Messages
|
||||
|
||||
### "Maximum call stack size exceeded"
|
||||
|
||||
**Cause**: Infinite recursion or circular dependency
|
||||
|
||||
**Fix**:
|
||||
1. Check for circular imports
|
||||
2. Add base case to recursive functions
|
||||
3. Break dependency cycles
|
||||
|
||||
### "Cannot access before initialization"
|
||||
|
||||
**Cause**: Temporal dead zone with `let`/`const`
|
||||
|
||||
**Fix**:
|
||||
1. Check import order
|
||||
2. Move declaration before usage
|
||||
3. Check for circular imports
|
||||
|
||||
### "Unexpected token"
|
||||
|
||||
**Cause**: Syntax error or wrong file type
|
||||
|
||||
**Fix**:
|
||||
1. Check file extension matches content
|
||||
2. Verify JSON is valid
|
||||
3. Check for missing brackets/quotes
|
||||
|
||||
### "ENOENT: no such file or directory"
|
||||
|
||||
**Cause**: Missing file or wrong path
|
||||
|
||||
**Fix**:
|
||||
1. Verify file exists
|
||||
2. Check path is correct (case-sensitive)
|
||||
3. Ensure build step completed
|
||||
|
||||
## Getting Help
|
||||
|
||||
1. Search this document first
|
||||
2. Check existing task documentation
|
||||
3. Search codebase for similar patterns
|
||||
4. Check GitHub issues
|
||||
5. Ask in community channels
|
||||
|
||||
## Contributing Solutions
|
||||
|
||||
Found a solution not listed here? Add it!
|
||||
|
||||
1. Edit this file
|
||||
2. Follow the format: Symptom → Solutions
|
||||
3. Include specific commands when helpful
|
||||
4. Submit PR with your addition
|
||||
446
dev-docs/reference/NODE-PATTERNS.md
Normal file
446
dev-docs/reference/NODE-PATTERNS.md
Normal file
@@ -0,0 +1,446 @@
|
||||
# Node Patterns Reference
|
||||
|
||||
How to create and modify nodes in OpenNoodl.
|
||||
|
||||
## Node Types
|
||||
|
||||
There are two main types of nodes:
|
||||
|
||||
1. **Runtime Nodes** (`noodl-runtime`) - Logic, data, utilities
|
||||
2. **Visual Nodes** (`noodl-viewer-react`) - React components for UI
|
||||
|
||||
## Basic Node Structure
|
||||
|
||||
### Runtime Node (JavaScript)
|
||||
|
||||
Location: `packages/noodl-runtime/src/nodes/`
|
||||
|
||||
```javascript
|
||||
'use strict';
|
||||
|
||||
const MyNode = {
|
||||
// === METADATA ===
|
||||
name: 'My.Custom.Node', // Unique identifier
|
||||
displayName: 'My Custom Node', // Shown in UI
|
||||
category: 'Custom', // Node picker category
|
||||
color: 'data', // Node color theme
|
||||
docs: 'https://docs.example.com', // Documentation link
|
||||
|
||||
// === INITIALIZATION ===
|
||||
initialize() {
|
||||
// Called when node is created
|
||||
this._internal.myValue = '';
|
||||
this._internal.callbacks = [];
|
||||
},
|
||||
|
||||
// === INPUTS ===
|
||||
inputs: {
|
||||
// Simple input
|
||||
textInput: {
|
||||
type: 'string',
|
||||
displayName: 'Text Input',
|
||||
group: 'General',
|
||||
default: '',
|
||||
set(value) {
|
||||
this._internal.textInput = value;
|
||||
this.flagOutputDirty('result');
|
||||
}
|
||||
},
|
||||
|
||||
// Number with validation
|
||||
numberInput: {
|
||||
type: 'number',
|
||||
displayName: 'Number',
|
||||
group: 'General',
|
||||
default: 0,
|
||||
set(value) {
|
||||
if (typeof value !== 'number') return;
|
||||
this._internal.numberInput = value;
|
||||
this.flagOutputDirty('result');
|
||||
}
|
||||
},
|
||||
|
||||
// Signal input (trigger)
|
||||
doAction: {
|
||||
type: 'signal',
|
||||
displayName: 'Do Action',
|
||||
group: 'Actions',
|
||||
valueChangedToTrue() {
|
||||
// Called when signal received
|
||||
this.performAction();
|
||||
}
|
||||
},
|
||||
|
||||
// Boolean toggle
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
displayName: 'Enabled',
|
||||
group: 'General',
|
||||
default: true,
|
||||
set(value) {
|
||||
this._internal.enabled = value;
|
||||
}
|
||||
},
|
||||
|
||||
// Dropdown/enum
|
||||
mode: {
|
||||
type: {
|
||||
name: 'enum',
|
||||
enums: [
|
||||
{ value: 'mode1', label: 'Mode 1' },
|
||||
{ value: 'mode2', label: 'Mode 2' }
|
||||
]
|
||||
},
|
||||
displayName: 'Mode',
|
||||
group: 'General',
|
||||
default: 'mode1',
|
||||
set(value) {
|
||||
this._internal.mode = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// === OUTPUTS ===
|
||||
outputs: {
|
||||
// Value output
|
||||
result: {
|
||||
type: 'string',
|
||||
displayName: 'Result',
|
||||
group: 'General',
|
||||
getter() {
|
||||
return this._internal.result;
|
||||
}
|
||||
},
|
||||
|
||||
// Signal output
|
||||
completed: {
|
||||
type: 'signal',
|
||||
displayName: 'Completed',
|
||||
group: 'Events'
|
||||
},
|
||||
|
||||
// Error output
|
||||
error: {
|
||||
type: 'string',
|
||||
displayName: 'Error',
|
||||
group: 'Error',
|
||||
getter() {
|
||||
return this._internal.error;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// === METHODS ===
|
||||
methods: {
|
||||
performAction() {
|
||||
if (!this._internal.enabled) return;
|
||||
|
||||
try {
|
||||
// Do something
|
||||
this._internal.result = 'Success';
|
||||
this.flagOutputDirty('result');
|
||||
this.sendSignalOnOutput('completed');
|
||||
} catch (e) {
|
||||
this._internal.error = e.message;
|
||||
this.flagOutputDirty('error');
|
||||
}
|
||||
},
|
||||
|
||||
// Called when node is deleted
|
||||
_onNodeDeleted() {
|
||||
// Cleanup
|
||||
this._internal.callbacks = [];
|
||||
}
|
||||
},
|
||||
|
||||
// === INSPECTOR (Debug Panel) ===
|
||||
getInspectInfo() {
|
||||
return {
|
||||
type: 'text',
|
||||
value: `Current: ${this._internal.result}`
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
node: MyNode
|
||||
};
|
||||
```
|
||||
|
||||
### Visual Node (React)
|
||||
|
||||
Location: `packages/noodl-viewer-react/src/nodes/`
|
||||
|
||||
```javascript
|
||||
'use strict';
|
||||
|
||||
const { Node } = require('@noodl/noodl-runtime');
|
||||
|
||||
const MyVisualNode = {
|
||||
name: 'My.Visual.Node',
|
||||
displayName: 'My Visual Node',
|
||||
category: 'UI Elements',
|
||||
|
||||
// Visual nodes need these
|
||||
allowChildren: true, // Can have child nodes
|
||||
allowChildrenWithCategory: ['UI Elements'], // Restrict child types
|
||||
|
||||
getReactComponent() {
|
||||
return MyReactComponent;
|
||||
},
|
||||
|
||||
// Frame updates for animations
|
||||
frame: {
|
||||
// Called every frame if registered
|
||||
update(context) {
|
||||
// Animation logic
|
||||
}
|
||||
},
|
||||
|
||||
inputs: {
|
||||
// Standard style inputs
|
||||
backgroundColor: {
|
||||
type: 'color',
|
||||
displayName: 'Background Color',
|
||||
group: 'Style',
|
||||
default: 'transparent',
|
||||
set(value) {
|
||||
this.props.style.backgroundColor = value;
|
||||
this.forceUpdate();
|
||||
}
|
||||
},
|
||||
|
||||
// Dimension with units
|
||||
width: {
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px', '%', 'vw'],
|
||||
defaultUnit: 'px'
|
||||
},
|
||||
displayName: 'Width',
|
||||
group: 'Dimensions',
|
||||
set(value) {
|
||||
this.props.style.width = value.value + value.unit;
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
// DOM event outputs
|
||||
onClick: {
|
||||
type: 'signal',
|
||||
displayName: 'Click',
|
||||
group: 'Events'
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
// Called when mounted
|
||||
didMount() {
|
||||
// Setup
|
||||
},
|
||||
|
||||
// Called when unmounted
|
||||
willUnmount() {
|
||||
// Cleanup
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// React component
|
||||
function MyReactComponent(props) {
|
||||
const handleClick = () => {
|
||||
props.noodlNode.sendSignalOnOutput('onClick');
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={props.style} onClick={handleClick}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
node: MyVisualNode
|
||||
};
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Scheduled Updates
|
||||
|
||||
Batch multiple input changes before processing:
|
||||
|
||||
```javascript
|
||||
inputs: {
|
||||
value1: {
|
||||
set(value) {
|
||||
this._internal.value1 = value;
|
||||
this.scheduleProcess();
|
||||
}
|
||||
},
|
||||
value2: {
|
||||
set(value) {
|
||||
this._internal.value2 = value;
|
||||
this.scheduleProcess();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
scheduleProcess() {
|
||||
if (this._internal.scheduled) return;
|
||||
this._internal.scheduled = true;
|
||||
|
||||
this.scheduleAfterInputsHaveUpdated(() => {
|
||||
this._internal.scheduled = false;
|
||||
this.processValues();
|
||||
});
|
||||
},
|
||||
processValues() {
|
||||
// Process both values together
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Async Operations
|
||||
|
||||
Handle promises and async work:
|
||||
|
||||
```javascript
|
||||
inputs: {
|
||||
fetch: {
|
||||
type: 'signal',
|
||||
valueChangedToTrue() {
|
||||
this.doFetch();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async doFetch() {
|
||||
try {
|
||||
const response = await fetch(this._internal.url);
|
||||
const data = await response.json();
|
||||
|
||||
this._internal.result = data;
|
||||
this.flagOutputDirty('result');
|
||||
this.sendSignalOnOutput('success');
|
||||
} catch (error) {
|
||||
this._internal.error = error.message;
|
||||
this.flagOutputDirty('error');
|
||||
this.sendSignalOnOutput('failure');
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Collection/Model Binding
|
||||
|
||||
Work with Noodl's data system:
|
||||
|
||||
```javascript
|
||||
const Collection = require('../../../collection');
|
||||
const Model = require('../../../model');
|
||||
|
||||
inputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
set(value) {
|
||||
this.bindCollection(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
bindCollection(collection) {
|
||||
// Unbind previous
|
||||
if (this._internal.collection) {
|
||||
this._internal.collection.off('change', this._internal.onChange);
|
||||
}
|
||||
|
||||
this._internal.collection = collection;
|
||||
|
||||
if (collection) {
|
||||
this._internal.onChange = () => {
|
||||
this.flagOutputDirty('count');
|
||||
};
|
||||
collection.on('change', this._internal.onChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Dynamic Ports
|
||||
|
||||
Add ports based on configuration:
|
||||
|
||||
```javascript
|
||||
inputs: {
|
||||
properties: {
|
||||
type: { name: 'stringlist', allowEditOnly: true },
|
||||
displayName: 'Properties',
|
||||
set(value) {
|
||||
// Register dynamic inputs/outputs based on list
|
||||
value.forEach(prop => {
|
||||
if (!this.hasInput('prop-' + prop)) {
|
||||
this.registerInput('prop-' + prop, {
|
||||
set(val) {
|
||||
this._internal.values[prop] = val;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Input Types Reference
|
||||
|
||||
| Type | Description | Example |
|
||||
|------|-------------|---------|
|
||||
| `string` | Text input | `type: 'string'` |
|
||||
| `number` | Numeric input | `type: 'number'` |
|
||||
| `boolean` | Toggle | `type: 'boolean'` |
|
||||
| `color` | Color picker | `type: 'color'` |
|
||||
| `signal` | Trigger/event | `type: 'signal'` |
|
||||
| `array` | Array/collection | `type: 'array'` |
|
||||
| `object` | Object/model | `type: 'object'` |
|
||||
| `component` | Component reference | `type: 'component'` |
|
||||
| `enum` | Dropdown selection | `type: { name: 'enum', enums: [...] }` |
|
||||
| `stringlist` | Editable list | `type: { name: 'stringlist' }` |
|
||||
| `number` with units | Dimension | `type: { name: 'number', units: [...] }` |
|
||||
|
||||
## Node Colors
|
||||
|
||||
Available color themes for nodes:
|
||||
|
||||
- `data` - Blue (data operations)
|
||||
- `logic` - Purple (logic/control)
|
||||
- `visual` - Green (UI elements)
|
||||
- `component` - Orange (component utilities)
|
||||
- `default` - Gray
|
||||
|
||||
## Registering Nodes
|
||||
|
||||
Add to the node library export:
|
||||
|
||||
```javascript
|
||||
// In packages/noodl-runtime/src/nodelibraryexport.js
|
||||
const MyNode = require('./nodes/my-node');
|
||||
|
||||
// Add to appropriate category in coreNodes array
|
||||
```
|
||||
|
||||
## Testing Nodes
|
||||
|
||||
```javascript
|
||||
// Example test structure
|
||||
describe('MyNode', () => {
|
||||
it('should process input correctly', () => {
|
||||
const node = createNode('My.Custom.Node');
|
||||
node.setInput('textInput', 'hello');
|
||||
|
||||
expect(node.getOutput('result')).toBe('HELLO');
|
||||
});
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user