UNPKG

@nocobase/flow-engine

Version:

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

485 lines (463 loc) 16.9 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 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 !important; } > .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 });