@nocobase/flow-engine
Version:
A standalone flow engine for NocoBase, managing workflows, models, and actions.
485 lines (463 loc) • 16.9 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 FlowsFloatContextMenu_exports = {};
__export(FlowsFloatContextMenu_exports, {
FlowsFloatContextMenu: () => FlowsFloatContextMenu
});
module.exports = __toCommonJS(FlowsFloatContextMenu_exports);
var import_react = __toESM(require("react"));
var import_antd = require("antd");
var import_react2 = require("@formily/react");
var import_css = require("@emotion/css");
var import_hooks = require("../../../../hooks");
var import_provider = require("../../../../provider");
var import_utils = require("../../../../utils");
const detectButtonInDOM = /* @__PURE__ */ __name((container) => {
if (!container) return false;
const directChildren = container.children;
for (let i = 0; i < directChildren.length; i++) {
const child = directChildren[i];
if (child.tagName === "BUTTON" || child.getAttribute("role") === "button" || child.classList.contains("ant-btn")) {
return true;
}
}
return false;
}, "detectButtonInDOM");
const renderToolbarItems = /* @__PURE__ */ __name((model, showDeleteButton, showCopyUidButton, flowEngine, settingsMenuLevel, extraToolbarItems) => {
var _a, _b;
const toolbarItems = ((_b = (_a = flowEngine == null ? void 0 : flowEngine.flowSettings) == null ? void 0 : _a.getToolbarItems) == null ? void 0 : _b.call(_a)) || [];
const allToolbarItems = [...toolbarItems, ...extraToolbarItems || []];
allToolbarItems.sort((a, b) => (a.sort || 0) - (b.sort || 0)).reverse();
return allToolbarItems.filter((itemConfig) => {
return itemConfig.visible ? itemConfig.visible(model) : true;
}).map((itemConfig) => {
const ItemComponent = itemConfig.component;
if (itemConfig.key === "settings-menu") {
return /* @__PURE__ */ import_react.default.createElement(
ItemComponent,
{
key: itemConfig.key,
model,
id: model.uid,
showDeleteButton,
showCopyUidButton,
menuLevels: settingsMenuLevel
}
);
}
return /* @__PURE__ */ import_react.default.createElement(ItemComponent, { key: itemConfig.key, model });
});
}, "renderToolbarItems");
const floatContainerStyles = /* @__PURE__ */ __name(({ showBackground, showBorder, ctx, toolbarPosition }) => import_css.css`
position: relative;
display: inline;
/* 当检测到button时使用inline-block */
&.has-button-child {
display: inline-block;
}
/* 正常的hover行为 - 添加延迟显示 */
&:hover > .nb-toolbar-container {
opacity: 1;
transition-delay: 0.1s;
.nb-toolbar-container-icons {
display: block;
}
}
/* 当有.hide-parent-menu类时隐藏菜单 */
&.hide-parent-menu > .nb-toolbar-container {
opacity: 0 ;
}
> .nb-toolbar-container {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
opacity: 0;
background: ${showBackground ? "var(--colorBgSettingsHover)" : ""};
border: ${showBorder ? "2px solid var(--colorBorderSettingsHover)" : ""};
border-radius: ${ctx.themeToken.borderRadiusLG}px;
pointer-events: none;
&.nb-in-template {
background: var(--colorTemplateBgSettingsHover);
}
> .nb-toolbar-container-title {
pointer-events: none;
position: absolute;
font-size: 12px;
padding: 0;
line-height: 16px;
height: 16px;
border-bottom-right-radius: 2px;
border-radius: 2px;
top: 2px;
left: 2px;
.title-tag {
padding: 0 3px;
border-radius: 2px;
background: var(--colorSettings);
color: #fff;
display: block;
}
}
> .nb-toolbar-container-icons {
display: none; // 防止遮挡其它 icons
position: absolute;
right: 2px;
top: ${toolbarPosition === "above" ? "0px" : "2px"};
${toolbarPosition === "above" ? "transform: translateY(-100%);" : ""}
${toolbarPosition === "above" ? "padding-bottom: 2px;" : ""}
${toolbarPosition === "above" ? "margin-bottom: -2px;" : ""}
line-height: 16px;
pointer-events: all;
.ant-space-item {
background-color: var(--colorSettings);
color: #fff;
line-height: 16px;
width: 16px;
height: 16px;
padding: 2px;
display: flex;
align-items: center;
justify-content: center;
}
}
/* 拖拽把手样式 - 参考 AirTable 样式 */
> .resize-handle {
position: absolute;
pointer-events: all;
background: var(--colorSettings);
opacity: 0.6;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
&:hover {
opacity: 0.9;
background: var(--colorSettingsHover, var(--colorSettings));
}
&::before {
content: '';
position: absolute;
background: rgba(255, 255, 255, 0.9);
border-radius: 50%;
}
&::after {
content: '';
position: absolute;
background: rgba(255, 255, 255, 0.9);
border-radius: 50%;
}
}
> .resize-handle-left {
left: -4px;
top: 50%;
transform: translateY(-50%);
width: 6px;
height: 20px;
cursor: ew-resize;
&::before {
width: 2px;
height: 2px;
top: 6px;
left: 50%;
transform: translateX(-50%);
box-shadow:
0 4px 0 rgba(255, 255, 255, 0.9),
0 8px 0 rgba(255, 255, 255, 0.9);
}
}
> .resize-handle-right {
right: -4px;
top: 50%;
transform: translateY(-50%);
width: 6px;
height: 20px;
cursor: ew-resize;
&::before {
width: 2px;
height: 2px;
top: 6px;
left: 50%;
transform: translateX(-50%);
box-shadow:
0 4px 0 rgba(255, 255, 255, 0.9),
0 8px 0 rgba(255, 255, 255, 0.9);
}
}
> .resize-handle-bottom {
bottom: -4px;
left: 50%;
transform: translateX(-50%);
width: 20px;
height: 6px;
cursor: ns-resize;
&::before {
width: 2px;
height: 2px;
left: 6px;
top: 50%;
transform: translateY(-50%);
box-shadow:
4px 0 0 rgba(255, 255, 255, 0.9),
8px 0 0 rgba(255, 255, 255, 0.9);
}
}
}
`, "floatContainerStyles");
const isModelByIdProps = /* @__PURE__ */ __name((props) => {
return "uid" in props && "modelClassName" in props && Boolean(props.uid) && Boolean(props.modelClassName);
}, "isModelByIdProps");
const FlowsFloatContextMenu = (0, import_react2.observer)((props) => {
var _a;
const flowEngine = (0, import_provider.useFlowEngine)();
if (!((_a = flowEngine.flowSettings) == null ? void 0 : _a.enabled)) {
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, props.children);
}
if (isModelByIdProps(props)) {
return /* @__PURE__ */ import_react.default.createElement(FlowsFloatContextMenuWithModelById, { ...props });
} else {
return /* @__PURE__ */ import_react.default.createElement(FlowsFloatContextMenuWithModel, { ...props });
}
});
const ResizeHandles = /* @__PURE__ */ __name((props) => {
const isDraggingRef = (0, import_react.useRef)(false);
const dragTypeRef = (0, import_react.useRef)(null);
const dragStartPosRef = (0, import_react.useRef)({ x: 0, y: 0 });
const { onDragStart, onDragEnd } = props;
const handleDragMove = (0, import_react.useCallback)(
(e) => {
if (!isDraggingRef.current || !dragTypeRef.current) return;
const deltaX = e.clientX - dragStartPosRef.current.x;
const deltaY = e.clientY - dragStartPosRef.current.y;
let resizeDistance = 0;
switch (dragTypeRef.current) {
case "left":
resizeDistance = -deltaX;
props.model.parent.emitter.emit("onResizeLeft", { resizeDistance, model: props.model });
break;
case "right":
resizeDistance = deltaX;
props.model.parent.emitter.emit("onResizeRight", { resizeDistance, model: props.model });
break;
case "bottom":
resizeDistance = deltaY;
props.model.parent.emitter.emit("onResizeBottom", { resizeDistance, model: props.model });
break;
case "corner": {
const widthDelta = deltaX;
const heightDelta = deltaY;
props.model.parent.emitter.emit("onResizeCorner", { widthDelta, heightDelta, model: props.model });
break;
}
}
},
[props.model]
);
const handleDragEnd = (0, import_react.useCallback)(() => {
isDraggingRef.current = false;
dragTypeRef.current = null;
dragStartPosRef.current = { x: 0, y: 0 };
document.removeEventListener("mousemove", handleDragMove);
document.removeEventListener("mouseup", handleDragEnd);
props.model.parent.emitter.emit("onResizeEnd");
onDragEnd == null ? void 0 : onDragEnd();
}, [handleDragMove, props.model, onDragEnd]);
const handleDragStart = (0, import_react.useCallback)(
(e, type) => {
e.preventDefault();
e.stopPropagation();
isDraggingRef.current = true;
dragTypeRef.current = type;
dragStartPosRef.current = { x: e.clientX, y: e.clientY };
document.addEventListener("mousemove", handleDragMove);
document.addEventListener("mouseup", handleDragEnd);
onDragStart == null ? void 0 : onDragStart();
},
[handleDragMove, handleDragEnd, onDragStart]
);
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(
"div",
{
className: "resize-handle resize-handle-left",
title: "\u62D6\u62FD\u8C03\u8282\u5BBD\u5EA6",
onMouseDown: (e) => handleDragStart(e, "left")
}
), /* @__PURE__ */ import_react.default.createElement(
"div",
{
className: "resize-handle resize-handle-right",
title: "\u62D6\u62FD\u8C03\u8282\u5BBD\u5EA6",
onMouseDown: (e) => handleDragStart(e, "right")
}
));
}, "ResizeHandles");
const FlowsFloatContextMenuWithModel = (0, import_react2.observer)(
({
model,
children,
enabled = true,
showDeleteButton = true,
showCopyUidButton = true,
containerStyle,
className,
showBackground = true,
showBorder = true,
showTitle = false,
showDragHandle = false,
settingsMenuLevel,
extraToolbarItems,
toolbarStyle,
toolbarPosition = "inside"
}) => {
const [hideMenu, setHideMenu] = (0, import_react.useState)(false);
const [hasButton, setHasButton] = (0, import_react.useState)(false);
const containerRef = (0, import_react.useRef)(null);
const flowEngine = (0, import_provider.useFlowEngine)();
const [style, setStyle] = (0, import_react.useState)({});
const toolbarContainerRef = (0, import_react.useRef)(null);
const toolbarContainerStyle = (0, import_react.useMemo)(() => ({ ...toolbarStyle, ...style }), [style, toolbarStyle]);
(0, import_react.useEffect)(() => {
if (containerRef.current) {
const hasButtonElement = detectButtonInDOM(containerRef.current);
setHasButton(hasButtonElement);
}
}, [children]);
(0, import_react.useEffect)(() => {
if (!containerRef.current) return;
const observer2 = new MutationObserver(() => {
if (containerRef.current) {
const hasButtonElement = detectButtonInDOM(containerRef.current);
setHasButton(hasButtonElement);
}
});
observer2.observe(containerRef.current, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["class", "role"]
});
return () => {
observer2.disconnect();
};
}, []);
const handleChildHover = (0, import_react.useCallback)((e) => {
const target = e.target;
const childWithMenu = target.closest("[data-has-float-menu]");
if (childWithMenu && childWithMenu !== containerRef.current) {
setHideMenu(true);
} else {
setHideMenu(false);
}
}, []);
if (!model) {
const t = (0, import_utils.getT)(model || {});
return /* @__PURE__ */ import_react.default.createElement(import_antd.Alert, { message: t("Invalid model provided"), type: "error" });
}
if (!enabled || !children) {
return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, children);
}
return /* @__PURE__ */ import_react.default.createElement(
"div",
{
ref: containerRef,
className: `${floatContainerStyles({ showBackground, showBorder, ctx: model.context, toolbarPosition })} ${hideMenu ? "hide-parent-menu" : ""} ${hasButton ? "has-button-child" : ""} ${className || ""}`,
style: containerStyle,
"data-has-float-menu": "true",
onMouseMove: handleChildHover
},
children,
/* @__PURE__ */ import_react.default.createElement("div", { ref: toolbarContainerRef, className: "nb-toolbar-container", style: toolbarContainerStyle }, showTitle && model.title && /* @__PURE__ */ import_react.default.createElement("div", { className: "nb-toolbar-container-title" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "title-tag" }, model.title)), /* @__PURE__ */ import_react.default.createElement("div", { className: "nb-toolbar-container-icons" }, /* @__PURE__ */ import_react.default.createElement(import_antd.Space, { size: 3, align: "center" }, renderToolbarItems(
model,
showDeleteButton,
showCopyUidButton,
flowEngine,
settingsMenuLevel,
extraToolbarItems
))), showDragHandle && /* @__PURE__ */ import_react.default.createElement(ResizeHandles, { model, onDragStart: () => setStyle({ opacity: 1 }), onDragEnd: () => setStyle({}) }))
);
},
{
displayName: "FlowsFloatContextMenuWithModel"
}
);
const FlowsFloatContextMenuWithModelById = (0, import_react2.observer)(
({
uid,
modelClassName,
children,
enabled = true,
showDeleteButton = true,
showCopyUidButton = true,
containerStyle,
className,
showTitle = false,
settingsMenuLevel,
extraToolbarItems,
toolbarPosition
}) => {
const model = (0, import_hooks.useFlowModelById)(uid, modelClassName);
const flowEngine = (0, import_provider.useFlowEngine)();
if (!model) {
return /* @__PURE__ */ import_react.default.createElement(import_antd.Alert, { message: flowEngine.translate("Model with ID {{uid}} not found", { uid }), type: "error" });
}
return /* @__PURE__ */ import_react.default.createElement(
FlowsFloatContextMenuWithModel,
{
model,
enabled,
showDeleteButton,
showCopyUidButton,
containerStyle,
className,
showTitle,
settingsMenuLevel,
extraToolbarItems,
toolbarPosition
},
children
);
},
{
displayName: "FlowsFloatContextMenuWithModelById"
}
);
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
FlowsFloatContextMenu
});