@bernierllc/generic-workflow-ui
Version:
Generic, reusable workflow UI components with linear and graph visualization
161 lines (159 loc) • 8.74 kB
JavaScript
"use strict";
/*
Copyright (c) 2025 Bernier LLC
This file is licensed to the client under a limited-use license.
The client may use and modify this code *only within the scope of the project it was delivered for*.
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.GenericWorkflowBuilderV2 = GenericWorkflowBuilderV2;
const react_1 = __importStar(require("react"));
const tamagui_1 = require("tamagui");
const react_2 = require("@xyflow/react");
const workflow_converter_1 = require("../json/workflow-converter");
const GenericWorkflowCanvas_1 = require("../canvas/GenericWorkflowCanvas");
const GenericWorkflowStepper_1 = require("../components/GenericWorkflowStepper");
// ============================================================================
// COMPONENT
// ============================================================================
/**
* Enhanced workflow builder with linear and graph visualization modes
*/
function GenericWorkflowBuilderV2({ initialWorkflow, initialMode = 'linear', config = {}, onSave, onWorkflowChange, readOnly = false, }) {
const { allowJSONImport = true, allowJSONExport = true, showJSONView = true, allowModeToggle = true, canvasConfig = {}, } = config;
// Initialize workflow
const [workflow, setWorkflow] = (0, react_1.useState)(() => {
if (!initialWorkflow) {
return {
id: `workflow-${Date.now()}`,
name: 'New Workflow',
stages: [],
transitions: [],
};
}
return initialWorkflow;
});
// Mode state
const [mode, setMode] = (0, react_1.useState)(initialMode);
// JSON view state
const [showJSON, setShowJSON] = (0, react_1.useState)(false);
// Current stage for visualization
const [currentStageId, setCurrentStageId] = (0, react_1.useState)(workflow.stages[0]?.id);
// Handle workflow change
const handleWorkflowChange = (0, react_1.useCallback)((updatedWorkflow) => {
setWorkflow(updatedWorkflow);
if (onWorkflowChange) {
onWorkflowChange(updatedWorkflow);
}
}, [onWorkflowChange]);
// Handle save
const handleSave = (0, react_1.useCallback)(() => {
const json = (0, workflow_converter_1.workflowToJSON)(workflow);
if (onSave) {
onSave({ generic: workflow, json });
}
}, [workflow, onSave]);
// Handle export
const handleExport = (0, react_1.useCallback)(() => {
const json = (0, workflow_converter_1.workflowToJSON)(workflow);
const blob = new Blob([JSON.stringify(json, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${workflow.name}.json`;
a.click();
URL.revokeObjectURL(url);
}, [workflow]);
// Handle import
const handleImport = (0, react_1.useCallback)(() => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'application/json';
input.onchange = (e) => {
const file = e.target.files?.[0];
if (!file)
return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const json = JSON.parse(event.target?.result);
const importedWorkflow = (0, workflow_converter_1.jsonToWorkflow)(json);
setWorkflow(importedWorkflow);
if (onWorkflowChange) {
onWorkflowChange(importedWorkflow);
}
}
catch (error) {
// Handle import error silently or show user feedback
if (error instanceof Error) {
// Could show toast notification here
}
}
};
reader.readAsText(file);
};
input.click();
}, [onWorkflowChange]);
return (react_1.default.createElement(tamagui_1.Card, { padding: "$4", elevate: true },
react_1.default.createElement(tamagui_1.YStack, { gap: "$4" },
react_1.default.createElement(tamagui_1.XStack, { justifyContent: "space-between", alignItems: "center" },
react_1.default.createElement(tamagui_1.Text, { fontSize: "$6", fontWeight: "bold" }, "Workflow Builder"),
react_1.default.createElement(tamagui_1.XStack, { gap: "$2" },
allowModeToggle && (react_1.default.createElement(tamagui_1.XStack, { gap: "$2", borderWidth: 1, borderColor: "$borderColor", borderRadius: "$2", padding: "$1" },
react_1.default.createElement(tamagui_1.Button, { size: "$3", variant: mode === 'linear' ? 'outlined' : 'ghost', onPress: () => setMode('linear'), disabled: readOnly }, "Linear"),
react_1.default.createElement(tamagui_1.Button, { size: "$3", variant: mode === 'graph' ? 'outlined' : 'ghost', onPress: () => setMode('graph'), disabled: readOnly }, "Graph"))),
showJSONView && (react_1.default.createElement(tamagui_1.Button, { size: "$3", variant: "outlined", onPress: () => setShowJSON(!showJSON) },
showJSON ? 'Hide' : 'Show',
" JSON")),
allowJSONImport && (react_1.default.createElement(tamagui_1.Button, { size: "$3", variant: "outlined", onPress: handleImport, disabled: readOnly }, "Import")),
allowJSONExport && (react_1.default.createElement(tamagui_1.Button, { size: "$3", variant: "outlined", onPress: handleExport }, "Export")),
onSave && (react_1.default.createElement(tamagui_1.Button, { size: "$3", onPress: handleSave, disabled: readOnly }, "Save")))),
mode === 'linear' ? (react_1.default.createElement(GenericWorkflowStepper_1.GenericWorkflowStepper, { workflow: workflow, currentStageId: currentStageId || '', config: {
enabled: true,
orientation: 'horizontal',
showDescriptions: true,
showIcons: false,
showTimestamps: false,
showUsers: false,
clickableStages: !readOnly,
}, onStageChange: (stageId) => setCurrentStageId(stageId) })) : (react_1.default.createElement(react_2.ReactFlowProvider, null,
react_1.default.createElement(GenericWorkflowCanvas_1.GenericWorkflowCanvas, { workflow: workflow, currentStageId: currentStageId, config: canvasConfig, onWorkflowChange: handleWorkflowChange, onNodeClick: (stageId) => setCurrentStageId(stageId), readOnly: readOnly }))),
showJSON && (react_1.default.createElement(tamagui_1.Card, { padding: "$4", backgroundColor: "$gray2" },
react_1.default.createElement(tamagui_1.Text, { fontFamily: "monospace", fontSize: "$2" },
react_1.default.createElement("pre", null, JSON.stringify((0, workflow_converter_1.workflowToJSON)(workflow), null, 2))))))));
}
GenericWorkflowBuilderV2.displayName = 'GenericWorkflowBuilderV2';
exports.default = GenericWorkflowBuilderV2;