@nocobase/flow-engine
Version:
A standalone flow engine for NocoBase, managing workflows, models, and actions.
416 lines (414 loc) • 15.4 kB
JavaScript
/**
* 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
});