UNPKG

@nocobase/flow-engine

Version:

A standalone flow engine for NocoBase, managing workflows, models, and actions.

416 lines (414 loc) 15.4 kB
/** * This file is part of the NocoBase (R) project. * Copyright (c) 2020-2024 NocoBase Co., Ltd. * Authors: NocoBase Team. * * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. * For more information, please refer to: https://www.nocobase.com/agreement. */ var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var AddSubModelButton_exports = {}; __export(AddSubModelButton_exports, { AddSubModelButton: () => AddSubModelButton2, mergeSubModelItems: () => mergeSubModelItems, transformItems: () => transformItems }); module.exports = __toCommonJS(AddSubModelButton_exports); var import_antd = require("antd"); var import_lodash = __toESM(require("lodash")); var import_react = __toESM(require("react")); var import_withFlowDesignMode = require("../common/withFlowDesignMode"); var import_LazyDropdown = __toESM(require("./LazyDropdown")); var import_utils = require("./utils"); const SWITCH_CONTAINER_STYLE = { display: "flex", justifyContent: "space-between", alignItems: "center", width: "100%", padding: "0" }; const SWITCH_STYLE = { marginLeft: 8, pointerEvents: "none" }; const validateCreateModelOptions = /* @__PURE__ */ __name((createOpts) => { if (!createOpts) { console.warn("No createModelOptions found for item"); return false; } if (!createOpts.use) { console.warn('createModelOptions must specify "use" property:', createOpts); return false; } return true; }, "validateCreateModelOptions"); const handleModelCreationError = /* @__PURE__ */ __name(async (error, addedModel) => { console.error("Failed to add sub model:", error); if (addedModel && typeof addedModel.destroy === "function") { try { await addedModel.destroy(); } catch (destroyError) { console.error("Failed to destroy model after creation error:", destroyError); } } }, "handleModelCreationError"); const getCreateModelOptions = /* @__PURE__ */ __name(async (item, ctx) => { let createOpts = item.createModelOptions; if (typeof createOpts === "function") { createOpts = await createOpts(ctx); } return { use: item.useModel, ...createOpts }; }, "getCreateModelOptions"); function mergeSubModelItems(sources, options = {}) { const { addDividers = false } = options; const validSources = sources.filter((source) => source !== void 0 && source !== null); if (validSources.length === 0) return []; if (validSources.length === 1) return validSources[0]; return async (ctx) => { const result = []; for (let i = 0; i < validSources.length; i++) { const source = validSources[i]; const items = Array.isArray(source) ? source : await source(ctx); if (i > 0 && addDividers && items.length > 0) { result.push({ key: `divider-${i}`, type: "divider" }); } result.push(...items); } return result; }; } __name(mergeSubModelItems, "mergeSubModelItems"); const createSwitchLabel = /* @__PURE__ */ __name((originalLabel, isToggled) => /* @__PURE__ */ import_react.default.createElement("div", { style: SWITCH_CONTAINER_STYLE }, /* @__PURE__ */ import_react.default.createElement("span", null, originalLabel), /* @__PURE__ */ import_react.default.createElement(import_antd.Switch, { size: "small", checked: isToggled, style: SWITCH_STYLE })), "createSwitchLabel"); const hasToggleItems = /* @__PURE__ */ __name((items) => { const walk = /* @__PURE__ */ __name((nodes) => { for (const node of nodes) { if (!node) continue; if (!node.children && (node.toggleDetector || node.toggleable)) return true; if (Array.isArray(node.children)) { if (walk(node.children)) return true; } else if (typeof node.children === "function") { return true; } } return false; }, "walk"); return walk(items); }, "hasToggleItems"); const transformSubModelItems = /* @__PURE__ */ __name(async (items, model, subModelKey, subModelType) => { if (items.length === 0) return []; const toggleItems = []; for (let i = 0; i < items.length; i++) { const item = items[i]; if (item.toggleable && item.useModel) { item.toggleDetector = (ctx) => { const C = ctx.engine.getModelClass(item.useModel); const r = ctx.model.findSubModel(subModelKey, (m) => { if (item.toggleable === true) { return m.constructor === C; } else if (typeof item.toggleable === "function") { return item.toggleable(m); } }); return !!r; }; item.customRemove = async (ctx, item2) => { const C = ctx.engine.getModelClass(item2.useModel); const r = ctx.model.findSubModel(subModelKey, (m) => { if (item2.toggleable === true) { return m.constructor === C; } else if (typeof item2.toggleable === "function") { return item2.toggleable(m); } }); if (r) { await r.destroy(); } }; } if (item.toggleDetector && !item.children) { toggleItems.push({ item, index: i }); } } const toggleResults = await Promise.allSettled( toggleItems.map(({ item }) => item.toggleDetector ? item.toggleDetector(model.context) : Promise.resolve(false)) ); const toggleMap = /* @__PURE__ */ new Map(); toggleItems.forEach(({ index }, i) => { const result = toggleResults[i]; toggleMap.set(index, result.status === "fulfilled" ? result.value : false); }); const transformPromises = items.map(async (item, index) => { const transformedItem = { key: item.key, label: item.label, type: item.type, disabled: item.disabled, icon: item.icon, searchable: item.searchable, searchPlaceholder: item.searchPlaceholder, keepDropdownOpen: item.keepDropdownOpen, originalItem: item }; if (item.children) { if (typeof item.children === "function") { transformedItem.children = async () => { const childrenFn = item.children; const childrenResult = await childrenFn(model.context); return transformSubModelItems(childrenResult, model, subModelKey, subModelType); }; } else { const staticChildren = item.children; const childHasToggle = hasToggleItems(staticChildren); if (childHasToggle) { transformedItem.children = async () => transformSubModelItems(staticChildren, model, subModelKey, subModelType); } else { transformedItem.children = await transformSubModelItems(staticChildren, model, subModelKey, subModelType); } } } if (item.toggleDetector && !item.children) { const isToggled = toggleMap.get(index) || false; const originalLabel = model.translate(item.label) || ""; transformedItem.label = createSwitchLabel(originalLabel, isToggled); transformedItem.isToggled = isToggled; transformedItem.keepDropdownOpen = item.keepDropdownOpen ?? true; } return transformedItem; }); return Promise.all(transformPromises); }, "transformSubModelItems"); const transformItems = /* @__PURE__ */ __name((items, model, subModelKey, subModelType) => { if (typeof items === "function") { return async () => { const result = await items(model.context); return transformSubModelItems(result, model, subModelKey, subModelType); }; } const hasToggle = hasToggleItems(items); if (hasToggle) { return () => transformSubModelItems(items, model, subModelKey, subModelType); } else { let cachedResult = null; return async () => { if (!cachedResult) { cachedResult = await transformSubModelItems(items, model, subModelKey, subModelType); } return cachedResult; }; } }, "transformItems"); const createDefaultRemoveHandler = /* @__PURE__ */ __name((config) => { return async (item, parentModel) => { const { model, subModelKey, subModelType } = config; if (subModelType === "array") { const subModels = model.subModels[subModelKey]; if (Array.isArray(subModels)) { const createOpts = await getCreateModelOptions(item, parentModel.context); const targetModel = subModels.find((subModel) => { if (createOpts == null ? void 0 : createOpts.use) { try { const modelClass = config.model.flowEngine.getModelClass(createOpts.use); if (modelClass && subModel instanceof modelClass) { return true; } } catch (error) { } return subModel.uid.includes(createOpts.use); } return false; }); if (targetModel) { await targetModel.destroy(); const index = subModels.indexOf(targetModel); if (index > -1) subModels.splice(index, 1); return targetModel; } } } else { const subModel = model.subModels[subModelKey]; if (subModel) { await subModel.destroy(); model.subModels[subModelKey] = void 0; return subModel; } } return null; }; }, "createDefaultRemoveHandler"); const AddSubModelButtonCore = /* @__PURE__ */ __name(function AddSubModelButton({ model, items, subModelBaseClass, subModelBaseClasses, subModelType = "array", subModelKey, afterSubModelInit, afterSubModelAdd, afterSubModelRemove, children = "Add", keepDropdownOpen = false }) { const persistKey = (0, import_react.useMemo)(() => { try { const id = model && model.uid || "unknown-model"; return `asmb:${id}:${subModelKey}:${subModelType}`; } catch (e) { return `asmb:unknown:${subModelKey}:${subModelType}`; } }, [model, subModelKey, subModelType]); const [refreshTick, setRefreshTick] = import_react.default.useState(0); const [refreshKeys, setRefreshKeys] = import_react.default.useState(void 0); const finalItems = (0, import_react.useMemo)(() => { const sources = []; if (items) sources.push(items); if (subModelBaseClass) sources.push((0, import_utils.buildItems)(subModelBaseClass)); if (subModelBaseClasses && subModelBaseClasses.length > 0) { sources.push((0, import_utils.buildSubModelGroups)(subModelBaseClasses)); } return mergeSubModelItems(sources, { addDividers: true }); }, [items, subModelBaseClass, subModelBaseClasses]); const removeHandler = (0, import_react.useMemo)( () => createDefaultRemoveHandler({ model, subModelKey, subModelType }), [model, subModelKey, subModelType] ); const onClick = /* @__PURE__ */ __name(async (info) => { const clickedItem = info.originalItem || info; const item = clickedItem.originalItem || clickedItem; const isToggled = clickedItem.isToggled; const clickedKey = info == null ? void 0 : info.key; if (clickedKey) { const extras = (item == null ? void 0 : item.refreshTargets) || []; setRefreshKeys([clickedKey, ...extras]); } if (item.toggleDetector && isToggled) { try { let removedModel = null; try { const C = model.flowEngine.getModelClass(item.useModel); if (C) { removedModel = model.findSubModel(subModelKey, (m) => m.constructor === C); } } catch (e) { } if (item.customRemove) { await item.customRemove(model.context, item); } else { removedModel = await removeHandler(item, model) || removedModel; } if (afterSubModelRemove && removedModel) { await afterSubModelRemove(removedModel); } setRefreshTick((x) => x + 1); } catch (error) { console.error("Failed to remove sub model:", error); } return; } const createOpts = await getCreateModelOptions(item, model.context); if (!validateCreateModelOptions(createOpts)) { return; } let addedModel; try { addedModel = model.flowEngine.createModel({ ...import_lodash.default.cloneDeep(createOpts), parentId: model.uid, subKey: subModelKey, subType: subModelType }); addedModel.isNew = true; addedModel.setParent(model); const toAdd = /* @__PURE__ */ __name(async () => { try { if (afterSubModelInit) { await afterSubModelInit(addedModel); } if (subModelType === "array") { model.addSubModel(subModelKey, addedModel); } else { model.setSubModel(subModelKey, addedModel); } if (afterSubModelAdd) { await afterSubModelAdd(addedModel); } await addedModel.afterAddAsSubModel(); await addedModel.save(); addedModel.isNew = false; setRefreshTick((x) => x + 1); } catch (error) { await handleModelCreationError(error, addedModel); } }, "toAdd"); const opened = await addedModel.openFlowSettings({ preset: true, onSaved: /* @__PURE__ */ __name(async () => { await toAdd(); }, "onSaved") }); if (!opened) { await toAdd(); } } catch (error) { await handleModelCreationError(error, addedModel); } }, "onClick"); const itemsFactory = (0, import_react.useMemo)( () => transformItems(finalItems, model, subModelKey, subModelType), [finalItems, model, subModelKey, subModelType] ); return /* @__PURE__ */ import_react.default.createElement( import_LazyDropdown.default, { menu: { items: itemsFactory, onClick, keepDropdownOpen, persistKey, stateVersion: refreshTick, refreshKeys } }, children ); }, "AddSubModelButton"); const AddSubModelButton2 = (0, import_withFlowDesignMode.withFlowDesignMode)(AddSubModelButtonCore); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { AddSubModelButton, mergeSubModelItems, transformItems });