Workflow Editor UI for n8n - a comprehensive Vue.js-based visual workflow editor with drag-and-drop functionality.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Vue 3 composables providing reusable functionality for canvas operations, workflow helpers, UI interactions, and common utilities.
Composables for canvas and node operations.
/**
* Access canvas node data within node components
*/
function useCanvasNode(): UseCanvasNodeReturn;
interface UseCanvasNodeReturn {
node: InjectedCanvasNode;
id: ComputedRef<string>;
name: ComputedRef<string>;
label: ComputedRef<string>;
subtitle: ComputedRef<string>;
inputs: ComputedRef<CanvasConnectionPort[]>;
outputs: ComputedRef<CanvasConnectionPort[]>;
connections: ComputedRef<INodeConnections>;
isDisabled: ComputedRef<boolean>;
isReadOnly: ComputedRef<boolean>;
isSelected: ComputedRef<boolean>;
pinnedDataCount: ComputedRef<number>;
hasPinnedData: ComputedRef<boolean>;
runDataIterations: ComputedRef<number>;
runDataOutputMap: ComputedRef<Record<string, any>>;
hasRunData: ComputedRef<boolean>;
issues: ComputedRef<string[]>;
hasIssues: ComputedRef<boolean>;
executionStatus: ComputedRef<ExecutionStatus | undefined>;
executionWaiting: ComputedRef<string | undefined>;
executionWaitingForNext: ComputedRef<boolean>;
executionRunning: ComputedRef<boolean>;
render: ComputedRef<CanvasNodeRender>;
eventBus: ComputedRef<EventBus | undefined>;
}
/**
* Canvas operations for node manipulation
*/
function useCanvasOperations(): CanvasOperationsReturn;
interface CanvasOperationsReturn {
addNodes(nodes: AddedNode[], connections?: AddedNodeConnection[]): void;
deleteNode(id: string): void;
copyNodes(ids: string[]): void;
pasteNodes(position?: XYPosition): void;
createConnection(source: CanvasConnectionPort, target: CanvasConnectionPort): void;
updateNodePosition(id: string, position: XYPosition): void;
}
/**
* Canvas layout and arrangement
*/
function useCanvasLayout(): CanvasLayoutReturn;
interface CanvasLayoutReturn {
layout(target: 'all' | 'selection'): void;
arrangeNodes(nodes: INodeUi[], direction?: 'horizontal' | 'vertical'): INodeUi[];
}Composables for workflow operations and management.
/**
* Workflow helper functions
*/
function useWorkflowHelpers(): WorkflowHelpersReturn;
interface WorkflowHelpersReturn {
getWorkflowDataToSave(): IWorkflowDataUpdate;
saveCurrentWorkflow(options?: { tags?: string[] }): Promise<void>;
getCurrentWorkflow(copyData?: boolean): Workflow;
initializeWorkflow(): void;
resolveRequiredParameters(node: INodeUi, nodeType: INodeTypeDescription): INodeParameters;
}
/**
* Workflow execution helpers
*/
function useRunWorkflow(): RunWorkflowReturn;
interface RunWorkflowReturn {
runWorkflowData: Ref<IStartRunData | null>;
isExecutionPreview: Ref<boolean>;
runWorkflow(data: IStartRunData): Promise<IExecutionPushResponse>;
stopCurrentExecution(): Promise<void>;
stopExecution(executionId: string): Promise<void>;
}
/**
* Workflow saving functionality
*/
function useWorkflowSaving(): WorkflowSavingReturn;
interface WorkflowSavingReturn {
isSaving: Ref<boolean>;
hasUnsavedChanges: Ref<boolean>;
saveWorkflow(options?: { tags?: string[]; redirect?: boolean }): Promise<void>;
saveAsNewWorkflow(name: string): Promise<void>;
}Node-specific helper composables.
/**
* Node helper functions
*/
function useNodeHelpers(): NodeHelpersReturn;
interface NodeHelpersReturn {
getNodeSubtitle(node: INodeUi, nodeType?: INodeTypeDescription, workflow?: Workflow): string;
hasNodeCredential(node: INodeUi, credentialType: string): boolean;
assignNodeId(node: INodeUi): string;
getNodeIssues(node: INodeUi, workflow: Workflow): INodeIssues | null;
updateNodeParameterIssues(node: INodeUi, issues?: INodeIssues): void;
}
/**
* Node type information
*/
function useNodeType(params: { node: Ref<INodeUi | null> }): NodeTypeReturn;
interface NodeTypeReturn {
nodeType: ComputedRef<INodeTypeDescription | null>;
isSubNode: ComputedRef<boolean>;
isTriggerNode: ComputedRef<boolean>;
isConfigNode: ComputedRef<boolean>;
hasMultipleOutputs: ComputedRef<boolean>;
}
/**
* Node connections management
*/
function useNodeConnections(): NodeConnectionsReturn;
interface NodeConnectionsReturn {
getNodeConnections(nodeName: string): { input: IConnection[][]; output: IConnection[][] };
getChildNodes(nodeName: string): string[];
getParentNodes(nodeName: string): string[];
hasInputConnections(nodeName: string): boolean;
hasOutputConnections(nodeName: string): boolean;
}Composables for user interface interactions.
/**
* Keyboard shortcuts management
*/
function useKeybindings(
keymap: MaybeRefOrGetter<KeyMap>,
options?: KeybindingOptions
): void;
interface KeyMap {
[key: string]: (event: KeyboardEvent) => void | boolean;
}
interface KeybindingOptions {
disabled?: MaybeRefOrGetter<boolean>;
preventDefault?: boolean;
stopPropagation?: boolean;
}
/**
* Toast notifications
*/
function useToast(): ToastReturn;
interface ToastReturn {
showMessage(config: NotificationOptions): void;
showError(error: Error | string, title?: string): void;
showSuccess(message: string, title?: string): void;
showWarning(message: string, title?: string): void;
showInfo(message: string, title?: string): void;
}
/**
* Loading states management
*/
function useLoadingService(): LoadingServiceReturn;
interface LoadingServiceReturn {
startLoading(text?: string): void;
stopLoading(): void;
setLoadingText(text: string): void;
}
/**
* Clipboard operations
*/
function useClipboard(): ClipboardReturn;
interface ClipboardReturn {
copy(text: string): Promise<void>;
paste(): Promise<string>;
isSupported: boolean;
}Composables for data processing and management.
/**
* Data schema inference and handling
*/
function useDataSchema(): DataSchemaReturn;
interface DataSchemaReturn {
getSchemaForData(data: INodeExecutionData[]): Schema[];
inferSchema(value: unknown, path?: string): Schema;
validateDataAgainstSchema(data: unknown, schema: Schema[]): ValidationResult;
}
/**
* Pinned data management
*/
function usePinnedData(node: Ref<INodeUi>): PinnedDataReturn;
interface PinnedDataReturn {
pinnedData: ComputedRef<IDataObject[] | undefined>;
hasPinnedData: ComputedRef<boolean>;
setPinnedData(data: IDataObject[]): void;
removePinnedData(): void;
canPinData: ComputedRef<boolean>;
}
/**
* Expression editor functionality
*/
function useExpressionEditor(): ExpressionEditorReturn;
interface ExpressionEditorReturn {
segments: Ref<Segment[]>;
selection: Ref<Range>;
insertText(text: string): void;
insertExpression(expression: string): void;
getValue(): string;
setValue(value: string): void;
}General utility composables for common functionality.
/**
* Debounced reactive values
*/
function useDebounce<T>(value: Ref<T>, delay: number): {
debouncedValue: Ref<T>;
flush(): void;
cancel(): void;
};
/**
* Document title management
*/
function useDocumentTitle(): DocumentTitleReturn;
interface DocumentTitleReturn {
title: Ref<string>;
setTitle(newTitle: string): void;
resetTitle(): void;
}
/**
* Local storage with reactivity
*/
function useN8nLocalStorage<T>(
key: string,
defaultValue: T
): [Ref<T>, (value: T) => void];
/**
* Message handling
*/
function useMessage(): MessageReturn;
interface MessageReturn {
confirm(config: MessageBoxConfig): Promise<boolean>;
alert(config: MessageBoxConfig): Promise<void>;
prompt(config: MessageBoxConfig): Promise<string>;
}
/**
* External hooks integration
*/
function useExternalHooks(): ExternalHooksReturn;
interface ExternalHooksReturn {
run(hookName: string, ...args: any[]): Promise<void>;
runWithReturn<T>(hookName: string, ...args: any[]): Promise<T>;
}Canvas Node Composable:
<script setup lang="ts">
import { useCanvasNode } from '@/composables/useCanvasNode';
// Access node data within canvas node component
const {
node,
name,
label,
inputs,
outputs,
isSelected,
hasIssues,
executionRunning
} = useCanvasNode();
// Reactive node properties
watchEffect(() => {
if (hasIssues.value) {
console.warn(`Node ${name.value} has issues`);
}
});
</script>Keyboard Bindings:
import { useKeybindings } from '@/composables/useKeybindings';
// Define keyboard shortcuts
const keyMap = {
'ctrl+s': () => saveWorkflow(),
'ctrl+z': () => undo(),
'ctrl+y': () => redo(),
'delete': () => deleteSelectedNodes(),
'ctrl+c': () => copyNodes(),
'ctrl+v': () => pasteNodes(),
'escape': () => deselectAll()
};
// Apply keybindings with options
useKeybindings(keyMap, {
disabled: computed(() => isModalOpen.value),
preventDefault: true
});Toast Notifications:
import { useToast } from '@/composables/useToast';
const toast = useToast();
// Show different types of notifications
const handleSuccess = () => {
toast.showSuccess('Workflow saved successfully!');
};
const handleError = (error: Error) => {
toast.showError(error, 'Failed to save workflow');
};
const handleCustomMessage = () => {
toast.showMessage({
message: 'Custom notification',
type: 'info',
duration: 5000,
showClose: true
});
};Data Schema Usage:
import { useDataSchema } from '@/composables/useDataSchema';
const { getSchemaForData, inferSchema } = useDataSchema();
// Infer schema from execution data
const executionData = [
{ json: { id: 1, name: 'John', active: true } },
{ json: { id: 2, name: 'Jane', active: false } }
];
const schema = getSchemaForData(executionData);
console.log(schema); // Inferred schema structureinterface Segment {
kind: SegmentKind;
content: string;
from: number;
to: number;
}
enum SegmentKind {
Text = 'text',
Expression = 'expression',
Resolvable = 'resolvable'
}
interface Range {
from: number;
to: number;
}
interface Schema {
type: SchemaType;
key?: string;
value: string | Schema[];
path: string;
}
type SchemaType =
| 'string'
| 'number'
| 'boolean'
| 'array'
| 'object'
| 'null'
| 'undefined';
interface ValidationResult {
isValid: boolean;
errors: string[];
}
interface NotificationOptions {
message: string;
title?: string;
type?: 'success' | 'warning' | 'info' | 'error';
duration?: number;
showClose?: boolean;
offset?: number;
onClose?: () => void;
}
interface MessageBoxConfig {
title?: string;
message: string;
type?: 'success' | 'warning' | 'info' | 'error';
confirmButtonText?: string;
cancelButtonText?: string;
showCancelButton?: boolean;
dangerouslyUseHTMLString?: boolean;
}
type MaybeRefOrGetter<T> = T | Ref<T> | (() => T);