From c37cb4afbac5adcaaaa34cb3b5f0664bfffee357 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 25 Jul 2025 12:14:50 +0800 Subject: [PATCH 01/25] refactor: clean code --- internal/certificate/service.go | 8 +++----- internal/domain/settings.go | 10 ++++++++++ internal/workflow/service.go | 8 +++----- ui/src/pages/dashboard/Dashboard.tsx | 6 +++--- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/internal/certificate/service.go b/internal/certificate/service.go index 4df15799c..c844b17f9 100644 --- a/internal/certificate/service.go +++ b/internal/certificate/service.go @@ -4,7 +4,6 @@ import ( "archive/zip" "bytes" "context" - "encoding/json" "fmt" "strings" "time" @@ -49,12 +48,11 @@ func (s *CertificateService) InitSchedule(ctx context.Context) error { return } - var settingsContent *domain.SettingsContentAsPersistence - json.Unmarshal([]byte(settings.Content), &settingsContent) - if settingsContent != nil && settingsContent.ExpiredCertificatesMaxDaysRetention != 0 { + persistenceSettings, _ := settings.UnmarshalContentAsPersistence() + if persistenceSettings != nil && persistenceSettings.ExpiredCertificatesMaxDaysRetention != 0 { ret, err := s.certificateRepo.DeleteWhere( context.Background(), - dbx.NewExp(fmt.Sprintf("validityNotAfter {
From bc268dfb325af7211773b3a00559e453e3d5644c Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Fri, 25 Jul 2025 19:10:26 +0800 Subject: [PATCH 02/25] feat(ui): new workflow routers --- ui/src/i18n/locales/en/nls.workflow.json | 27 +- ui/src/i18n/locales/zh/nls.workflow.json | 25 +- ui/src/pages/settings/Settings.tsx | 23 +- ui/src/pages/workflows/WorkflowDetail.tsx | 257 ++++-------------- .../pages/workflows/WorkflowDetailDesign.tsx | 193 +++++++++++++ .../workflows/WorkflowDetailRuns.tsx} | 97 ++++--- ui/src/pages/workflows/WorkflowList.tsx | 41 +-- ui/src/pages/workflows/WorkflowNew.tsx | 140 +++------- ui/src/routers/index.tsx | 12 + ui/src/stores/workflow/index.ts | 8 +- 10 files changed, 399 insertions(+), 424 deletions(-) create mode 100644 ui/src/pages/workflows/WorkflowDetailDesign.tsx rename ui/src/{components/workflow/WorkflowRuns.tsx => pages/workflows/WorkflowDetailRuns.tsx} (80%) diff --git a/ui/src/i18n/locales/en/nls.workflow.json b/ui/src/i18n/locales/en/nls.workflow.json index 426a58f09..39acce5bf 100644 --- a/ui/src/i18n/locales/en/nls.workflow.json +++ b/ui/src/i18n/locales/en/nls.workflow.json @@ -21,16 +21,16 @@ "workflow.action.enable.button": "Activate", "workflow.action.enable.errmsg.uncompleted": "Please complete the orchestration and publish the changes first", "workflow.action.disable.button": "Deactivate", - "workflow.action.release.button": "Release", - "workflow.action.release.modal.title": "Release changes", - "workflow.action.release.modal.content": "Are you sure to release your changes?", - "workflow.action.release.errmsg.uncompleted": "Please complete the orchestration first", - "workflow.action.discard.button": "Discard", - "workflow.action.discard.modal.title": "Discard changes", - "workflow.action.discard.modal.content": "Are you sure to discard your changes?", + "workflow.action.publish.button": "Publish", + "workflow.action.publish.modal.title": "Publish changes", + "workflow.action.publish.modal.content": "Are you sure to publish your changes?", + "workflow.action.publish.errmsg.uncompleted": "Please complete the orchestration first", + "workflow.action.rollback.button": "Rollback", + "workflow.action.rollback.modal.title": "Rollback changes", + "workflow.action.rollback.modal.content": "Are you sure to rollback your changes to the last published version?", "workflow.action.run.button": "Run", "workflow.action.run.modal.title": "Run workflow", - "workflow.action.run.modal.content": "You have unreleased changes. Do you really want to run this workflow based on the latest released version?", + "workflow.action.run.modal.content": "You have unpublished changes. Do you really want to run this workflow based on the last published version?", "workflow.action.run.prompt": "Running... Please check the history later", "workflow.props.name": "Name", @@ -55,18 +55,15 @@ "workflow.new.templates.template.certtest.description": "A monitoring operating procedure that includes monitoring, and notification steps.", "workflow.new.templates.template.empty.title": "Empty template", "workflow.new.templates.template.empty.description": "Customize all the contents of the workflow from the beginning.", - "workflow.new.modal.title": "Create workflow", - "workflow.new.modal.form.name.label": "Name", - "workflow.new.modal.form.name.placeholder": "Please enter workflow name", - "workflow.new.modal.form.description.label": "Description (Optional)", - "workflow.new.modal.form.description.placeholder": "Please enter workflow description", + "workflow.new.templates.default_name": "Untitled workflow", + "workflow.new.templates.default_description": "Created at {{date}}", "workflow.detail.baseinfo.modal.title": "Workflow base information", "workflow.detail.baseinfo.form.name.label": "Name", "workflow.detail.baseinfo.form.name.placeholder": "Please enter workflow name", "workflow.detail.baseinfo.form.description.label": "Description (Optional)", "workflow.detail.baseinfo.form.description.placeholder": "Please enter workflow description", - "workflow.detail.orchestration.tab": "Orchestration", - "workflow.detail.orchestration.draft.alert": "The orchestration is not released yet.", + "workflow.detail.design.tab": "Orchestration", + "workflow.detail.design.draft.alert": "The orchestration is not released yet.", "workflow.detail.runs.tab": "History runs" } diff --git a/ui/src/i18n/locales/zh/nls.workflow.json b/ui/src/i18n/locales/zh/nls.workflow.json index ca74474b9..5ff7c4d2f 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.json +++ b/ui/src/i18n/locales/zh/nls.workflow.json @@ -21,13 +21,13 @@ "workflow.action.enable.button": "启用", "workflow.action.enable.errmsg.uncompleted": "请先完成流程编排并发布更改", "workflow.action.disable.button": "停用", - "workflow.action.release.button": "发布更改", - "workflow.action.release.modal.title": "发布更改", - "workflow.action.release.modal.content": "确定要发布更改吗?", - "workflow.action.release.errmsg.uncompleted": "流程编排未完成,请检查是否有节点未配置", - "workflow.action.discard.button": "撤销更改", - "workflow.action.discard.modal.title": "撤销更改", - "workflow.action.discard.modal.content": "确定要撤销更改并回退到最近一次发布的版本吗?", + "workflow.action.publish.button": "发布更改", + "workflow.action.publish.modal.title": "发布更改", + "workflow.action.publish.modal.content": "确定要发布更改吗?", + "workflow.action.publish.errmsg.uncompleted": "流程编排未完成,请检查是否有节点未配置", + "workflow.action.rollback.button": "回退更改", + "workflow.action.rollback.modal.title": "回退更改", + "workflow.action.rollback.modal.content": "确定要回退到最近一次发布的版本吗?", "workflow.action.run.button": "执行", "workflow.action.run.modal.title": "执行工作流", "workflow.action.run.modal.content": "你有尚未发布的更改。确定要以最近一次发布的版本继续执行吗?", @@ -55,18 +55,15 @@ "workflow.new.templates.template.certtest.description": "一个包含证书监控 + 消息通知步骤的工作流程。", "workflow.new.templates.template.empty.title": "空白模板", "workflow.new.templates.template.empty.description": "从零开始自定义工作流的任务内容。", - "workflow.new.modal.title": "新建工作流", - "workflow.new.modal.form.name.label": "名称", - "workflow.new.modal.form.name.placeholder": "请输入工作流名称", - "workflow.new.modal.form.description.label": "描述(可选)", - "workflow.new.modal.form.description.placeholder": "请输入工作流描述", + "workflow.new.templates.default_name": "未命名工作流", + "workflow.new.templates.default_description": "于 {{date}} 创建", "workflow.detail.baseinfo.modal.title": "编辑基本信息", "workflow.detail.baseinfo.form.name.label": "名称", "workflow.detail.baseinfo.form.name.placeholder": "请输入工作流名称", "workflow.detail.baseinfo.form.description.label": "描述(可选)", "workflow.detail.baseinfo.form.description.placeholder": "请输入工作流描述", - "workflow.detail.orchestration.tab": "流程编排", - "workflow.detail.orchestration.draft.alert": "当前编排有未发布的更改。", + "workflow.detail.design.tab": "流程编排", + "workflow.detail.design.draft.alert": "当前编排有未发布的更改。", "workflow.detail.runs.tab": "执行历史" } diff --git a/ui/src/pages/settings/Settings.tsx b/ui/src/pages/settings/Settings.tsx index 0c22af811..82fc6115b 100644 --- a/ui/src/pages/settings/Settings.tsx +++ b/ui/src/pages/settings/Settings.tsx @@ -16,17 +16,22 @@ const Settings = () => { ["ssl-provider", "settings.sslprovider.tab", ], ["persistence", "settings.persistence.tab", ], ] satisfies [string, string, React.ReactElement][]; - const [menuKey, setMenuValue] = useState("account"); + const [menuKey, setMenuValue] = useState(menus[0][0]); useEffect(() => { - const path = location.pathname.split("/")[2]; - if (!path) { + const subpath = location.pathname.split("/")[2]; + if (!subpath) { navigate("/settings/account"); return; } - setMenuValue(path); + setMenuValue(subpath); }, [location.pathname]); + const handleMenuClick = ({ key }: { key: string }) => { + setMenuValue(key); + navigate(`/settings/${key}`); + }; + return (
@@ -46,10 +51,7 @@ const Settings = () => { ), }))} - onClick={({ key }) => { - setMenuValue(key); - navigate(`/settings/${key}`); - }} + onClick={handleMenuClick} />
@@ -68,10 +70,7 @@ const Settings = () => { ), }))} - onClick={({ key }) => { - setMenuValue(key); - navigate(`/settings/${key}`); - }} + onClick={handleMenuClick} />
diff --git a/ui/src/pages/workflows/WorkflowDetail.tsx b/ui/src/pages/workflows/WorkflowDetail.tsx index 66e404080..aad56f363 100644 --- a/ui/src/pages/workflows/WorkflowDetail.tsx +++ b/ui/src/pages/workflows/WorkflowDetail.tsx @@ -1,25 +1,21 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useNavigate, useParams } from "react-router-dom"; -import { IconArrowBackUp, IconChevronDown, IconDots, IconHistory, IconPlayerPlay, IconRobot, IconTrash } from "@tabler/icons-react"; -import { Alert, App, Button, Card, Dropdown, Flex, Form, Input, Segmented, Space } from "antd"; +import { Outlet, useLocation, useNavigate, useParams } from "react-router-dom"; +import { IconChevronDown, IconHistory, IconRobot, IconTrash } from "@tabler/icons-react"; +import { App, Button, Dropdown, Flex, Form, Input, Segmented, Skeleton } from "antd"; import { createSchemaFieldRule } from "antd-zod"; -import { isEqual } from "radash"; import { z } from "zod"; -import { startRun as startWorkflowRun } from "@/api/workflows"; import ModalForm from "@/components/ModalForm"; import Show from "@/components/Show"; -import WorkflowElementsContainer from "@/components/workflow/WorkflowElementsContainer"; -import WorkflowRuns from "@/components/workflow/WorkflowRuns"; import { isAllNodesValidated } from "@/domain/workflow"; -import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun"; import { useAntdForm, useZustandShallowSelector } from "@/hooks"; -import { remove as removeWorkflow, subscribe as subscribeWorkflow, unsubscribe as unsubscribeWorkflow } from "@/repository/workflow"; +import { remove as removeWorkflow } from "@/repository/workflow"; import { useWorkflowStore } from "@/stores/workflow"; import { getErrMsg } from "@/utils/error"; const WorkflowDetail = () => { + const location = useLocation(); const navigate = useNavigate(); const { t } = useTranslation(); @@ -27,9 +23,7 @@ const WorkflowDetail = () => { const { message, modal, notification } = App.useApp(); const { id: workflowId } = useParams(); - const { workflow, initialized, ...workflowState } = useWorkflowStore( - useZustandShallowSelector(["workflow", "initialized", "init", "destroy", "setEnabled", "release", "discard"]) - ); + const { workflow, initialized, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "initialized", "init", "destroy", "setEnabled"])); useEffect(() => { workflowState.init(workflowId!); @@ -38,43 +32,27 @@ const WorkflowDetail = () => { }; }, [workflowId]); - const [tabValue, setTabValue] = useState<"orchestration" | "runs">("orchestration"); - - const [isPendingOrRunning, setIsPendingOrRunning] = useState(false); - const lastRunStatus = useMemo(() => workflow.lastRunStatus, [workflow]); - - const [allowDiscard, setAllowDiscard] = useState(false); - const [allowRelease, setAllowRelease] = useState(false); - const [allowRun, setAllowRun] = useState(false); - + const tabs = [ + ["design", "workflow.detail.design.tab", ], + ["runs", "workflow.detail.runs.tab", ], + ] satisfies [string, string, React.ReactElement][]; + const [tabValue, setTabValue] = useState(tabs[0][0]); useEffect(() => { - setIsPendingOrRunning(lastRunStatus == WORKFLOW_RUN_STATUSES.PENDING || lastRunStatus == WORKFLOW_RUN_STATUSES.RUNNING); - }, [lastRunStatus]); - - useEffect(() => { - if (!!workflowId && isPendingOrRunning) { - subscribeWorkflow(workflowId, (cb) => { - if (cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.PENDING && cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) { - setIsPendingOrRunning(false); - unsubscribeWorkflow(workflowId); - } - }); - - return () => { - unsubscribeWorkflow(workflowId); - }; + const subpath = location.pathname.split("/")[3]; + if (!tabs.map((tab) => tab[0]).includes(subpath)) { + navigate(`/workflows/${workflowId}/${tabs[0][0]}`, { replace: true }); + return; } - }, [workflowId, isPendingOrRunning]); - useEffect(() => { - const hasReleased = !!workflow.content; - const hasChanges = workflow.hasDraft! || !isEqual(workflow.draft, workflow.content); - setAllowDiscard(!isPendingOrRunning && hasReleased && hasChanges); - setAllowRelease(!isPendingOrRunning && hasChanges); - setAllowRun(hasReleased); - }, [workflow.content, workflow.draft, workflow.hasDraft, isPendingOrRunning]); + setTabValue(subpath); + }, [location.pathname, workflowId]); - const handleEnableChange = async () => { + const handleTabChange = (value: string) => { + setTabValue(value); + navigate(`/workflows/${workflowId}/${value}`); + }; + + const handleEnableClick = async () => { if (!workflow.enabled && (!workflow.content || !isAllNodesValidated(workflow.content))) { message.warning(t("workflow.action.enable.errmsg.uncompleted")); return; @@ -113,85 +91,6 @@ const WorkflowDetail = () => { }); }; - const handleDiscardClick = () => { - modal.confirm({ - title: t("workflow.action.discard.modal.title"), - content: t("workflow.action.discard.modal.content"), - onOk: async () => { - try { - await workflowState.discard(); - - message.success(t("common.text.operation_succeeded")); - } catch (err) { - console.error(err); - notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); - } - }, - }); - }; - - const handleReleaseClick = () => { - if (!isAllNodesValidated(workflow.draft!)) { - message.warning(t("workflow.action.release.errmsg.uncompleted")); - return; - } - - modal.confirm({ - title: t("workflow.action.release.modal.title"), - content: t("workflow.action.release.modal.content"), - onOk: async () => { - try { - await workflowState.release(); - - message.success(t("common.text.operation_succeeded")); - } catch (err) { - console.error(err); - notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); - } - }, - }); - }; - - const handleRunClick = () => { - const { promise, resolve, reject } = Promise.withResolvers(); - if (workflow.hasDraft) { - modal.confirm({ - title: t("workflow.action.run.modal.title"), - content: t("workflow.action.run.modal.content"), - onOk: () => resolve(void 0), - onCancel: () => reject(), - }); - } else { - resolve(void 0); - } - - promise.then(async () => { - let unsubscribeFn: Awaited> | undefined = undefined; - - try { - setIsPendingOrRunning(true); - - // subscribe before running workflow - unsubscribeFn = await subscribeWorkflow(workflowId!, (e) => { - if (e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.PENDING && e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) { - setIsPendingOrRunning(false); - unsubscribeFn?.(); - } - }); - - await startWorkflowRun(workflowId!); - - message.info(t("workflow.action.run.prompt")); - } catch (err) { - setIsPendingOrRunning(false); - unsubscribeFn?.(); - - console.error(err); - message.warning(t("common.text.operation_failed")); - } - }); - }; - return (
@@ -205,7 +104,7 @@ const WorkflowDetail = () => { {initialized ? [ {t("common.button.edit")}} />, - , { items: [ { key: "delete", - label: t("common.button.delete"), + label: t("workflow.action.delete.button"), danger: true, icon: , onClick: () => { @@ -237,98 +136,36 @@ const WorkflowDetail = () => {
{t("workflow.detail.orchestration.tab")}, - icon: ( - - - - ), - }, - { - value: "runs", - label: {t("workflow.detail.runs.tab")}, - icon: ( - - - - ), - }, - ]} + options={tabs.map(([key, label, icon]) => ({ + value: key, + label: {t(label)}, + icon: ( + + {icon} + + ), + }))} size="large" value={tabValue} defaultValue="orchestration" - onChange={(value) => { - setTabValue(value as typeof tabValue); - }} + onChange={handleTabChange} />
- -
- -
-
- - {t("workflow.detail.orchestration.draft.alert")}
} showIcon type="warning" /> - -
-
- - - - - , - onClick: handleDiscardClick, - }, - ], - }} - trigger={["click"]} - > -
+
+ +
- - -
-
-
- - -
-
- -
-
-
+ } + > + + + ); }; diff --git a/ui/src/pages/workflows/WorkflowDetailDesign.tsx b/ui/src/pages/workflows/WorkflowDetailDesign.tsx new file mode 100644 index 000000000..1876a2ba5 --- /dev/null +++ b/ui/src/pages/workflows/WorkflowDetailDesign.tsx @@ -0,0 +1,193 @@ +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { IconArrowBackUp, IconDots, IconPlayerPlay } from "@tabler/icons-react"; +import { Alert, App, Button, Card, Dropdown, Space } from "antd"; +import { isEqual } from "radash"; + +import { startRun as startWorkflowRun } from "@/api/workflows"; +import Show from "@/components/Show"; +import WorkflowElementsContainer from "@/components/workflow/WorkflowElementsContainer"; +import { isAllNodesValidated } from "@/domain/workflow"; +import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun"; +import { useZustandShallowSelector } from "@/hooks"; +import { subscribe as subscribeWorkflow, unsubscribe as unsubscribeWorkflow } from "@/repository/workflow"; +import { useWorkflowStore } from "@/stores/workflow"; +import { getErrMsg } from "@/utils/error"; + +const WorkflowDetailDesign = () => { + const { t } = useTranslation(); + + const { message, modal, notification } = App.useApp(); + + const { workflow, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "publish", "rollback"])); + + const [isPendingOrRunning, setIsPendingOrRunning] = useState(false); + const lastRunStatus = useMemo(() => workflow.lastRunStatus, [workflow]); + + const [allowRollback, setAllowRollback] = useState(false); + const [allowPublish, setAllowPublish] = useState(false); + const [allowRun, setAllowRun] = useState(false); + + useEffect(() => { + setIsPendingOrRunning(lastRunStatus == WORKFLOW_RUN_STATUSES.PENDING || lastRunStatus == WORKFLOW_RUN_STATUSES.RUNNING); + }, [lastRunStatus]); + + useEffect(() => { + if (isPendingOrRunning) { + subscribeWorkflow(workflow.id, (cb) => { + if (cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.PENDING && cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) { + setIsPendingOrRunning(false); + unsubscribeWorkflow(workflow.id); + } + }); + + return () => { + unsubscribeWorkflow(workflow.id); + }; + } + }, [workflow.id, isPendingOrRunning]); + + useEffect(() => { + const hasContent = !!workflow.content; + const hasChanges = workflow.hasDraft! || !isEqual(workflow.draft, workflow.content); + setAllowRollback(!isPendingOrRunning && hasContent && hasChanges); + setAllowPublish(!isPendingOrRunning && hasChanges); + setAllowRun(hasContent); + }, [workflow.content, workflow.draft, workflow.hasDraft, isPendingOrRunning]); + + const handleRollbackClick = () => { + modal.confirm({ + title: t("workflow.action.rollback.modal.title"), + content: t("workflow.action.rollback.modal.content"), + onOk: async () => { + try { + await workflowState.rollback(); + + message.success(t("common.text.operation_succeeded")); + } catch (err) { + console.error(err); + notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + } + }, + }); + }; + + const handlePublishClick = () => { + if (!isAllNodesValidated(workflow.draft!)) { + message.warning(t("workflow.action.publish.errmsg.uncompleted")); + return; + } + + modal.confirm({ + title: t("workflow.action.publish.modal.title"), + content: t("workflow.action.publish.modal.content"), + onOk: async () => { + try { + await workflowState.publish(); + + message.success(t("common.text.operation_succeeded")); + } catch (err) { + console.error(err); + notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + } + }, + }); + }; + + const handleRunClick = () => { + const { promise, resolve, reject } = Promise.withResolvers(); + if (workflow.hasDraft) { + modal.confirm({ + title: t("workflow.action.run.modal.title"), + content: t("workflow.action.run.modal.content"), + onOk: () => resolve(void 0), + onCancel: () => reject(), + }); + } else { + resolve(void 0); + } + + promise.then(async () => { + let unsubscribeFn: Awaited> | undefined = undefined; + + try { + setIsPendingOrRunning(true); + + // subscribe before running workflow + unsubscribeFn = await subscribeWorkflow(workflow.id, (e) => { + if (e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.PENDING && e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) { + setIsPendingOrRunning(false); + unsubscribeFn?.(); + } + }); + + await startWorkflowRun(workflow.id); + + message.info(t("workflow.action.run.prompt")); + } catch (err) { + setIsPendingOrRunning(false); + unsubscribeFn?.(); + + console.error(err); + message.warning(t("common.text.operation_failed")); + } + }); + }; + + return ( +
+ +
+
+
+ + {t("workflow.detail.design.draft.alert")}
} showIcon type="warning" /> + +
+
+ + + + + , + onClick: handleRollbackClick, + }, + ], + }} + trigger={["click"]} + > +
+
+ + +
+ + + ); +}; + +export default WorkflowDetailDesign; diff --git a/ui/src/components/workflow/WorkflowRuns.tsx b/ui/src/pages/workflows/WorkflowDetailRuns.tsx similarity index 80% rename from ui/src/components/workflow/WorkflowRuns.tsx rename to ui/src/pages/workflows/WorkflowDetailRuns.tsx index 72892bc57..9f597bab2 100644 --- a/ui/src/components/workflow/WorkflowRuns.tsx +++ b/ui/src/pages/workflows/WorkflowDetailRuns.tsx @@ -8,32 +8,29 @@ import { ClientResponseError } from "pocketbase"; import { cancelRun as cancelWorkflowRun } from "@/api/workflows"; import Empty from "@/components/Empty"; +import WorkflowRunDetailDrawer from "@/components/workflow/WorkflowRunDetailDrawer"; import WorkflowStatusTag from "@/components/workflow/WorkflowStatusTag"; import { WORKFLOW_TRIGGERS } from "@/domain/workflow"; import { WORKFLOW_RUN_STATUSES, type WorkflowRunModel } from "@/domain/workflowRun"; -import { useAppSettings } from "@/hooks"; +import { useAppSettings, useZustandShallowSelector } from "@/hooks"; import { list as listWorkflowRuns, remove as removeWorkflowRun, subscribe as subscribeWorkflowRun, unsubscribe as unsubscribeWorkflowRun, } from "@/repository/workflowRun"; +import { useWorkflowStore } from "@/stores/workflow"; import { getErrMsg } from "@/utils/error"; -import WorkflowRunDetailDrawer from "./WorkflowRunDetailDrawer"; -export interface WorkflowRunsProps { - className?: string; - style?: React.CSSProperties; - workflowId: string; -} - -const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => { +const WorkflowDetailRuns = () => { const { t } = useTranslation(); const { modal, notification } = App.useApp(); const { appSettings: globalAppSettings } = useAppSettings(); + const { workflow } = useWorkflowStore(useZustandShallowSelector(["workflow"])); + const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(globalAppSettings.defaultPerPage!); @@ -167,13 +164,13 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => { } = useRequest( () => { return listWorkflowRuns({ - workflowId: workflowId, + workflowId: workflow.id, page: page, perPage: pageSize, }); }, { - refreshDeps: [workflowId, page, pageSize], + refreshDeps: [workflow.id, page, pageSize], onSuccess: (res) => { setTableData(res.items); setTableTotal(res.totalItems); @@ -238,7 +235,7 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => { content: t("workflow_run.action.cancel.modal.content"), onOk: async () => { try { - const resp = await cancelWorkflowRun(workflowId, workflowRun.id); + const resp = await cancelWorkflowRun(workflow.id, workflowRun.id); if (resp) { refreshData(); } @@ -277,46 +274,48 @@ const WorkflowRuns = ({ className, style, workflowId }: WorkflowRunsProps) => { }; return ( -
- } showIcon type="info" /> - } showIcon type="info" /> +
+
+ } showIcon type="info" /> + } showIcon type="info" /> - - columns={tableColumns} - dataSource={tableData} - loading={loading} - locale={{ - emptyText: loading ? ( - - ) : ( - } - /> - ), - }} - pagination={{ - current: page, - pageSize: pageSize, - total: tableTotal, - showSizeChanger: true, - onChange: handlePaginationChange, - onShowSizeChange: handlePaginationChange, - }} - rowClassName="cursor-pointer" - rowKey={(record) => record.id} - scroll={{ x: "max(100%, 960px)" }} - onRow={(record) => ({ - onClick: () => { - handleRecordDetailClick(record); - }, - })} - /> + + columns={tableColumns} + dataSource={tableData} + loading={loading} + locale={{ + emptyText: loading ? ( + + ) : ( + } + /> + ), + }} + pagination={{ + current: page, + pageSize: pageSize, + total: tableTotal, + showSizeChanger: true, + onChange: handlePaginationChange, + onShowSizeChange: handlePaginationChange, + }} + rowClassName="cursor-pointer" + rowKey={(record) => record.id} + scroll={{ x: "max(100%, 960px)" }} + onRow={(record) => ({ + onClick: () => { + handleRecordDetailClick(record); + }, + })} + /> - + +
); }; -export default WorkflowRuns; +export default WorkflowDetailRuns; diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx index 9f9e93f46..97373a875 100644 --- a/ui/src/pages/workflows/WorkflowList.tsx +++ b/ui/src/pages/workflows/WorkflowList.tsx @@ -79,22 +79,22 @@ const WorkflowList = () => { title: t("workflow.props.state"), defaultFilteredValue: searchParams.has("state") ? [searchParams.get("state") as string] : undefined, render: (_, record) => { - const enabled = record.enabled; return ( -
{ - e.stopPropagation(); + { + handleRecordActiveChange(record); }} - > - { - handleRecordActiveChange(record); - }} - /> -
+ /> ); }, + onCell: () => { + return { + onClick: (e) => { + e.stopPropagation(); + }, + }; + }, }, { key: "lastRun", @@ -102,12 +102,17 @@ const WorkflowList = () => { sorter: true, sortOrder: sorter.columnKey === "lastRun" ? sorter.order : undefined, render: (_, record) => { - return ( - - - {record.lastRunTime ? dayjs(record.lastRunTime!).format("YYYY-MM-DD HH:mm:ss") : ""} - - ); + const { lastRunStatus, lastRunTime } = record; + if (!lastRunStatus) { + return <>; + } else { + return ( + + + {lastRunTime ? dayjs(record.lastRunTime!).format("YYYY-MM-DD HH:mm:ss") : ""} + + ); + } }, }, { diff --git a/ui/src/pages/workflows/WorkflowNew.tsx b/ui/src/pages/workflows/WorkflowNew.tsx index 7a1583e27..6524fb1f3 100644 --- a/ui/src/pages/workflows/WorkflowNew.tsx +++ b/ui/src/pages/workflows/WorkflowNew.tsx @@ -1,18 +1,15 @@ -import { useEffect, useRef, useState } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; -import { App, Card, Col, Form, Input, type InputRef, Row, Spin, Typography } from "antd"; -import { createSchemaFieldRule } from "antd-zod"; -import { z } from "zod"; +import { App, Card, Col, Row, Spin, Typography } from "antd"; +import dayjs from "dayjs"; -import ModalForm from "@/components/ModalForm"; import { type WorkflowModel, initWorkflow } from "@/domain/workflow"; -import { useAntdForm } from "@/hooks"; import { save as saveWorkflow } from "@/repository/workflow"; import { getErrMsg } from "@/utils/error"; const TEMPLATE_KEY_STANDARD = "standard" as const; -const TEMPLATE_KEY_CERTTEST = "monitor" as const; +const TEMPLATE_KEY_CERTTEST = "certtest" as const; const TEMPLATE_KEY_EMPTY = "empty" as const; type TemplateKeys = typeof TEMPLATE_KEY_EMPTY | typeof TEMPLATE_KEY_CERTTEST | typeof TEMPLATE_KEY_STANDARD; @@ -32,83 +29,45 @@ const WorkflowNew = () => { }; const [templateSelectKey, setTemplateSelectKey] = useState(); - const formSchema = z.object({ - name: z - .string() - .min(1, t("workflow.new.modal.form.name.placeholder")) - .max(64, t("common.errmsg.string_max", { max: 64 })), - description: z - .string() - .max(256, t("common.errmsg.string_max", { max: 256 })) - .nullish(), - }); - const formRule = createSchemaFieldRule(formSchema); - const { - form: formInst, - formPending, - formProps, - submit: submitForm, - } = useAntdForm>({ - initialValues: { - name: "", - description: "", - }, - onSubmit: async (values) => { - try { - let workflow: WorkflowModel; + const [pending, setPending] = useState(false); - switch (templateSelectKey) { - case TEMPLATE_KEY_EMPTY: - workflow = initWorkflow(); - break; + const handleTemplateClick = async (key: TemplateKeys) => { + if (pending) return; - case TEMPLATE_KEY_STANDARD: - workflow = initWorkflow({ template: "standard" }); - break; - - case TEMPLATE_KEY_CERTTEST: - workflow = initWorkflow({ template: "certtest" }); - break; - - default: - throw "Invalid state: `templateSelectKey`"; - } - - workflow.name = values.name?.trim() ?? workflow.name; - workflow.description = values.description?.trim() ?? workflow.description; - workflow = await saveWorkflow(workflow); - navigate(`/workflows/${workflow.id}`, { replace: true }); - } catch (err) { - notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); - - throw err; - } - }, - }); - const [formModalOpen, setFormModalOpen] = useState(false); - - useEffect(() => { - if (formModalOpen) { - setTimeout(() => inputRef.current?.focus({ cursor: "end" }), 1); - } else { - setTemplateSelectKey(undefined); - formInst.resetFields(); - } - }, [formModalOpen]); - - const inputRef = useRef(null); - - const handleTemplateClick = (key: TemplateKeys) => { setTemplateSelectKey(key); - setFormModalOpen(true); - }; - const handleModalOpenChange = (open: boolean) => { - setFormModalOpen(open); - }; + try { + let workflow: WorkflowModel; - const handleModalFormFinish = () => { - return submitForm(); + switch (key) { + case TEMPLATE_KEY_EMPTY: + workflow = initWorkflow(); + break; + + case TEMPLATE_KEY_STANDARD: + workflow = initWorkflow({ template: "standard" }); + break; + + case TEMPLATE_KEY_CERTTEST: + workflow = initWorkflow({ template: "certtest" }); + break; + + default: + throw "Invalid state: `templateSelectKey`"; + } + + workflow.name = t("workflow.new.templates.default_name"); + workflow.description = t("workflow.new.templates.default_description", { date: dayjs().format("YYYY-MM-DD HH:mm") }); + workflow = await saveWorkflow(workflow); + navigate(`/workflows/${workflow.id}`, { replace: true }); + } catch (err) { + notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + + throw err; + } finally { + setPending(false); + setTemplateSelectKey(undefined); + } }; return ( @@ -179,29 +138,6 @@ const WorkflowNew = () => {
- - - - - - - - - - ); }; diff --git a/ui/src/routers/index.tsx b/ui/src/routers/index.tsx index 78ff91261..5e192a1e7 100644 --- a/ui/src/routers/index.tsx +++ b/ui/src/routers/index.tsx @@ -14,6 +14,8 @@ import SettingsAppearance from "@/pages/settings/SettingsAppearance"; import SettingsPersistence from "@/pages/settings/SettingsPersistence"; import SettingsSSLProvider from "@/pages/settings/SettingsSSLProvider"; import WorkflowDetail from "@/pages/workflows/WorkflowDetail"; +import WorkflowDetailDesign from "@/pages/workflows/WorkflowDetailDesign"; +import WorkflowDetailRuns from "@/pages/workflows/WorkflowDetailRuns"; import WorkflowList from "@/pages/workflows/WorkflowList"; import WorkflowNew from "@/pages/workflows/WorkflowNew"; @@ -49,6 +51,16 @@ export const router = createHashRouter([ { path: "/workflows/:id", element: , + children: [ + { + path: "/workflows/:id/design", + element: , + }, + { + path: "/workflows/:id/runs", + element: , + }, + ], }, { path: "/settings", diff --git a/ui/src/stores/workflow/index.ts b/ui/src/stores/workflow/index.ts index 7a0867083..405a5bb20 100644 --- a/ui/src/stores/workflow/index.ts +++ b/ui/src/stores/workflow/index.ts @@ -23,8 +23,8 @@ export type WorkflowState = { init(id: string): void; setBaseInfo: (name: string, description: string) => void; setEnabled(enabled: boolean): void; - release(): void; - discard(): void; + publish(): void; + rollback(): void; destroy(): void; addNode: (node: WorkflowNode, previousNodeId: string) => void; @@ -95,7 +95,7 @@ export const useWorkflowStore = create((set, get) => ({ }); }, - release: async () => { + publish: async () => { if (!get().initialized) throw "Workflow not initialized yet"; const root = get().workflow.draft!; @@ -121,7 +121,7 @@ export const useWorkflowStore = create((set, get) => ({ }); }, - discard: async () => { + rollback: async () => { if (!get().initialized) throw "Workflow not initialized yet"; const root = get().workflow.content!; From e20cf8fad320f5b871f06ffffe4f0e74887433c5 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sat, 26 Jul 2025 13:09:30 +0800 Subject: [PATCH 03/25] fix(ui): missing bgcolor --- ui/src/components/workflow/WorkflowStatusTag.tsx | 2 +- ui/src/i18n/locales/zh/nls.workflow.runs.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/components/workflow/WorkflowStatusTag.tsx b/ui/src/components/workflow/WorkflowStatusTag.tsx index e80a434f8..12a645d9b 100644 --- a/ui/src/components/workflow/WorkflowStatusTag.tsx +++ b/ui/src/components/workflow/WorkflowStatusTag.tsx @@ -43,7 +43,7 @@ const WorkflowStatusTag = ({ className, style, status }: WorkflowStatusTagProps) ); case WORKFLOW_RUN_STATUSES.CANCELED: return ( - + {t("workflow_run.props.status.canceled")} ); diff --git a/ui/src/i18n/locales/zh/nls.workflow.runs.json b/ui/src/i18n/locales/zh/nls.workflow.runs.json index af5ba358c..bc02aa1c1 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.runs.json +++ b/ui/src/i18n/locales/zh/nls.workflow.runs.json @@ -2,7 +2,7 @@ "workflow_run.action.view.button": "查看详情", "workflow_run.action.cancel.button": "取消执行", "workflow_run.action.cancel.modal.title": "取消执行", - "workflow_run.action.cancel.modal.content": "确定要取消此执行吗?此操作仅中止流程,但不会回滚已执行的节点。", + "workflow_run.action.cancel.modal.content": "确定要取消此执行吗?取消后仅会中止流程,但不会回滚已执行的节点。", "workflow_run.action.delete.button": "删除执行", "workflow_run.action.delete.modal.title": "删除执行「{{name}}」", "workflow_run.action.delete.modal.content": "确定要删除该工作流执行记录吗?删除后仅会清除日志历史,但不会影响签发的证书。
注意此操作不可撤销,请谨慎操作。", From 7d633135bcd6a0db0d6d1b0e6d222b7931e322ee Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Sat, 26 Jul 2025 13:10:18 +0800 Subject: [PATCH 04/25] fix(ui): unsynchronized running status after WorkflowDetail subpage changed --- ui/src/domain/workflow.ts | 4 ++++ ui/src/pages/settings/Settings.tsx | 2 +- ui/src/pages/workflows/WorkflowDetail.tsx | 6 ++++-- .../pages/workflows/WorkflowDetailDesign.tsx | 20 ++++++++++--------- ui/src/pages/workflows/WorkflowList.tsx | 5 +++-- ui/src/repository/workflow.ts | 3 +++ ui/src/repository/workflowRun.ts | 18 ++++++++++++++--- 7 files changed, 41 insertions(+), 17 deletions(-) diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts index ac2e2b19d..c4a73f0e6 100644 --- a/ui/src/domain/workflow.ts +++ b/ui/src/domain/workflow.ts @@ -3,6 +3,7 @@ import { Immer, produce } from "immer"; import { nanoid } from "nanoid"; import i18n from "@/i18n"; +import { type WorkflowRunModel } from "./workflowRun"; export interface WorkflowModel extends BaseModel { name: string; @@ -16,6 +17,9 @@ export interface WorkflowModel extends BaseModel { lastRunRef?: string; lastRunStatus?: string; lastRunTime?: string; + expand?: { + lastRunRef?: WorkflowRunModel; + }; } export const WORKFLOW_TRIGGERS = Object.freeze({ diff --git a/ui/src/pages/settings/Settings.tsx b/ui/src/pages/settings/Settings.tsx index 82fc6115b..6de4be1ed 100644 --- a/ui/src/pages/settings/Settings.tsx +++ b/ui/src/pages/settings/Settings.tsx @@ -16,7 +16,7 @@ const Settings = () => { ["ssl-provider", "settings.sslprovider.tab", ], ["persistence", "settings.persistence.tab", ], ] satisfies [string, string, React.ReactElement][]; - const [menuKey, setMenuValue] = useState(menus[0][0]); + const [menuKey, setMenuValue] = useState(); useEffect(() => { const subpath = location.pathname.split("/")[2]; if (!subpath) { diff --git a/ui/src/pages/workflows/WorkflowDetail.tsx b/ui/src/pages/workflows/WorkflowDetail.tsx index aad56f363..07b2d120a 100644 --- a/ui/src/pages/workflows/WorkflowDetail.tsx +++ b/ui/src/pages/workflows/WorkflowDetail.tsx @@ -36,10 +36,10 @@ const WorkflowDetail = () => { ["design", "workflow.detail.design.tab", ], ["runs", "workflow.detail.runs.tab", ], ] satisfies [string, string, React.ReactElement][]; - const [tabValue, setTabValue] = useState(tabs[0][0]); + const [tabValue, setTabValue] = useState(); useEffect(() => { const subpath = location.pathname.split("/")[3]; - if (!tabs.map((tab) => tab[0]).includes(subpath)) { + if (!subpath) { navigate(`/workflows/${workflowId}/${tabs[0][0]}`, { replace: true }); return; } @@ -50,6 +50,8 @@ const WorkflowDetail = () => { const handleTabChange = (value: string) => { setTabValue(value); navigate(`/workflows/${workflowId}/${value}`); + + workflowState.init(workflow.id); // reload state }; const handleEnableClick = async () => { diff --git a/ui/src/pages/workflows/WorkflowDetailDesign.tsx b/ui/src/pages/workflows/WorkflowDetailDesign.tsx index 1876a2ba5..5bd694e74 100644 --- a/ui/src/pages/workflows/WorkflowDetailDesign.tsx +++ b/ui/src/pages/workflows/WorkflowDetailDesign.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { IconArrowBackUp, IconDots, IconPlayerPlay } from "@tabler/icons-react"; import { Alert, App, Button, Card, Dropdown, Space } from "antd"; @@ -10,7 +10,7 @@ import WorkflowElementsContainer from "@/components/workflow/WorkflowElementsCon import { isAllNodesValidated } from "@/domain/workflow"; import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun"; import { useZustandShallowSelector } from "@/hooks"; -import { subscribe as subscribeWorkflow, unsubscribe as unsubscribeWorkflow } from "@/repository/workflow"; +import { subscribe as subscribeWorkflow } from "@/repository/workflow"; import { useWorkflowStore } from "@/stores/workflow"; import { getErrMsg } from "@/utils/error"; @@ -19,30 +19,32 @@ const WorkflowDetailDesign = () => { const { message, modal, notification } = App.useApp(); - const { workflow, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "publish", "rollback"])); + const { workflow, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "init", "publish", "rollback"])); const [isPendingOrRunning, setIsPendingOrRunning] = useState(false); - const lastRunStatus = useMemo(() => workflow.lastRunStatus, [workflow]); - const [allowRollback, setAllowRollback] = useState(false); const [allowPublish, setAllowPublish] = useState(false); const [allowRun, setAllowRun] = useState(false); useEffect(() => { - setIsPendingOrRunning(lastRunStatus == WORKFLOW_RUN_STATUSES.PENDING || lastRunStatus == WORKFLOW_RUN_STATUSES.RUNNING); - }, [lastRunStatus]); + const pending = workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.PENDING || workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.RUNNING; + setIsPendingOrRunning(pending); + }, [workflow]); useEffect(() => { if (isPendingOrRunning) { + let unsubscribeFn: Awaited> | undefined = undefined; subscribeWorkflow(workflow.id, (cb) => { if (cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.PENDING && cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) { setIsPendingOrRunning(false); - unsubscribeWorkflow(workflow.id); + unsubscribeFn?.(); } + }).then((res) => { + unsubscribeFn = res; }); return () => { - unsubscribeWorkflow(workflow.id); + unsubscribeFn?.(); }; } }, [workflow.id, isPendingOrRunning]); diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx index 97373a875..88641169a 100644 --- a/ui/src/pages/workflows/WorkflowList.tsx +++ b/ui/src/pages/workflows/WorkflowList.tsx @@ -108,8 +108,8 @@ const WorkflowList = () => { } else { return ( - - {lastRunTime ? dayjs(record.lastRunTime!).format("YYYY-MM-DD HH:mm:ss") : ""} + + {lastRunTime ? dayjs(lastRunTime).format("YYYY-MM-DD HH:mm:ss") : ""} ); } @@ -231,6 +231,7 @@ const WorkflowList = () => { sort: sort, page: page, perPage: pageSize, + expand: true, }); }, { diff --git a/ui/src/repository/workflow.ts b/ui/src/repository/workflow.ts index 9ce71f26e..60ccc557e 100644 --- a/ui/src/repository/workflow.ts +++ b/ui/src/repository/workflow.ts @@ -9,6 +9,7 @@ export type ListRequest = { sort?: string; page?: number; perPage?: number; + expand?: boolean; }; export const list = async (request: ListRequest) => { @@ -28,6 +29,7 @@ export const list = async (request: ListRequest) => { const perPage = request.perPage || 10; return await pb.collection(COLLECTION_NAME_WORKFLOW).getList(page, perPage, { + expand: request.expand ? "lastRunRef" : undefined, filter: filters.join(" && "), sort: sort, requestKey: null, @@ -36,6 +38,7 @@ export const list = async (request: ListRequest) => { export const get = async (id: string) => { return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW).getOne(id, { + expand: "lastRunRef", requestKey: null, }); }; diff --git a/ui/src/repository/workflowRun.ts b/ui/src/repository/workflowRun.ts index 2fdc011f2..4f2082f16 100644 --- a/ui/src/repository/workflowRun.ts +++ b/ui/src/repository/workflowRun.ts @@ -22,15 +22,27 @@ export const list = async (request: ListRequest) => { const page = request.page || 1; const perPage = request.perPage || 10; return await pb.collection(COLLECTION_NAME_WORKFLOW_RUN).getList(page, perPage, { + expand: request.expand ? "workflowRef" : undefined, filter: filters.join(" && "), sort: "-created", requestKey: null, - expand: request.expand ? "workflowRef" : undefined, }); }; -export const remove = async (record: MaybeModelRecordWithId) => { - return await getPocketBase().collection(COLLECTION_NAME_WORKFLOW_RUN).delete(record.id); +export const remove = async (record: MaybeModelRecordWithId | MaybeModelRecordWithId[]) => { + const pb = getPocketBase(); + + if (Array.isArray(record)) { + const batch = pb.createBatch(); + for (const item of record) { + batch.collection(COLLECTION_NAME_WORKFLOW_RUN).delete(item.id); + } + const res = await batch.send(); + return res.every((e) => e.status === 200); + } else { + await pb.collection(COLLECTION_NAME_WORKFLOW_RUN).delete(record.id!); + return true; + } }; export const subscribe = async (id: string, cb: (e: RecordSubscription) => void) => { From 3308bcb01dc56f5b63c855e781cb68fa4af35654 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 28 Jul 2025 12:59:20 +0800 Subject: [PATCH 05/25] dev --- ui/package-lock.json | 1276 +++++++++++++++++++++++++-- ui/package.json | 13 +- ui/src/components/TextFileInput.tsx | 4 +- ui/src/utils/file.ts | 2 +- 4 files changed, 1202 insertions(+), 93 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 8822b6dbf..2f5392d09 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -13,6 +13,7 @@ "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language": "^6.11.2", "@codemirror/legacy-modes": "^6.5.1", + "@flowgram.ai/fixed-layout-editor": "^0.2.28", "@tabler/icons-react": "^3.34.1", "@uiw/codemirror-extensions-basic-setup": "^4.24.1", "@uiw/codemirror-theme-vscode": "^4.24.1", @@ -27,19 +28,19 @@ "i18next-browser-languagedetector": "^8.2.0", "immer": "^10.1.1", "nanoid": "^5.1.5", - "pocketbase": "^0.26.1", + "pocketbase": "^0.26.2", "radash": "^12.1.1", "react": "^18.3.1", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.3.1", "react-i18next": "^15.6.1", - "react-router-dom": "^7.7.0", + "react-router-dom": "^7.7.1", "tailwind-merge": "^3.3.1", - "zod": "^4.0.5", + "zod": "^4.0.10", "zustand": "^5.0.6" }, "devDependencies": { - "@eslint/js": "^9.31.0", + "@eslint/js": "^9.32.0", "@tailwindcss/postcss": "^4.1.11", "@tailwindcss/vite": "^4.1.11", "@types/file-saver": "^2.0.7", @@ -50,10 +51,10 @@ "@types/react-dom": "^18.3.7", "@vitejs/plugin-legacy": "^6.1.1", "@vitejs/plugin-react": "^4.7.0", - "eslint": "^9.31.0", + "eslint": "^9.32.0", "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.4", - "eslint-plugin-better-tailwindcss": "^3.7.1", + "eslint-plugin-better-tailwindcss": "^3.7.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-prettier": "^5.5.3", "eslint-plugin-react-hooks": "^5.2.0", @@ -82,7 +83,6 @@ "version": "2.3.0", "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -460,7 +460,6 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", @@ -474,7 +473,6 @@ "version": "7.27.5", "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.27.5.tgz", "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -483,7 +481,6 @@ "version": "7.28.0", "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.28.0.tgz", "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", - "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -513,7 +510,6 @@ "version": "6.3.1", "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -522,7 +518,6 @@ "version": "7.28.0", "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.28.0.tgz", "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", - "dev": true, "dependencies": { "@babel/parser": "^7.28.0", "@babel/types": "^7.28.0", @@ -538,7 +533,6 @@ "version": "7.25.9", "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", - "dev": true, "dependencies": { "@babel/types": "^7.25.9" }, @@ -550,7 +544,6 @@ "version": "7.27.2", "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", @@ -566,7 +559,6 @@ "version": "6.3.1", "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -647,7 +639,6 @@ "version": "7.28.0", "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -669,7 +660,6 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" @@ -682,7 +672,6 @@ "version": "7.27.3", "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", - "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", @@ -711,7 +700,6 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -767,7 +755,6 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -776,7 +763,6 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -785,7 +771,6 @@ "version": "7.27.1", "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -808,7 +793,6 @@ "version": "7.27.6", "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.27.6.tgz", "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", - "dev": true, "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.6" @@ -821,7 +805,6 @@ "version": "7.28.0", "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.0.tgz", "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", - "dev": true, "dependencies": { "@babel/types": "^7.28.0" }, @@ -953,6 +936,20 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", @@ -1904,7 +1901,6 @@ "version": "7.27.2", "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", @@ -1918,7 +1914,6 @@ "version": "7.28.0", "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.28.0.tgz", "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", @@ -1936,7 +1931,6 @@ "version": "7.28.1", "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.1.tgz", "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", - "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -2178,6 +2172,24 @@ "resolved": "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz", "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmmirror.com/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, "node_modules/@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz", @@ -2738,9 +2750,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.31.0", - "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.31.0.tgz", - "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "version": "9.32.0", + "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-9.32.0.tgz", + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2759,9 +2771,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.3", - "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", - "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "version": "0.3.4", + "resolved": "https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, "dependencies": { "@eslint/core": "^0.15.1", @@ -2771,6 +2783,870 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@flowgram.ai/background-plugin": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/background-plugin/-/background-plugin-0.2.28.tgz", + "integrity": "sha512-GEH0e1TioFQuKTZli7UO0yz171xs2T2XAnoWdxGhuWTcZ3PVyJSwJSF6BPDxfWGgN3kL9JeghbCPgElhwEOKGg==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/command": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/command/-/command-0.2.28.tgz", + "integrity": "sha512-/OMswJnXKm6aHIjwOQfWzCeYCr19x6gA8YNY8z9BpWJa3mPpPMUKjigldvOZVq/LZ3UPLj4EtbW7u4yyfsNCgA==", + "dependencies": { + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/core": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/core/-/core-0.2.28.tgz", + "integrity": "sha512-iVHIGHZTjoI0OJCf2m2ch70jkopWu6P+G1wy4cYMWqWI89lz2grtAEd7Qg8vRHQameLAdoQnAHuSkAXpIIKcaA==", + "dependencies": { + "@flowgram.ai/command": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "@phosphor/messaging": "^1.3.0", + "@tweenjs/tween.js": "^18", + "clsx": "^1.1.1", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "nanoid": "^4.0.2", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/core/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@flowgram.ai/core/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/document": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/document/-/document-0.2.28.tgz", + "integrity": "sha512-HzEG5EZNdtooAaGjCCd4k9cjxoUaMs0j2dowVA2/z8CO42ylqLnk+GJqr8jE+gPxS4muwjxdzEa/OXcgsrlqhw==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "nanoid": "^4.0.2", + "reflect-metadata": "~0.2.2" + } + }, + "node_modules/@flowgram.ai/document/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/editor": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/editor/-/editor-0.2.28.tgz", + "integrity": "sha512-QMSuOfFXZHHtnjuuX/pDqoGn/iCKjD6I05wcKUn0LqqbFgyYKp49Ju77IS9rGBs6R1Cj0jqFPl3OLW50sFqLEg==", + "dependencies": { + "@flowgram.ai/background-plugin": "0.2.28", + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/form": "0.2.28", + "@flowgram.ai/form-core": "0.2.28", + "@flowgram.ai/group-plugin": "0.2.28", + "@flowgram.ai/history": "0.2.28", + "@flowgram.ai/history-node-plugin": "0.2.28", + "@flowgram.ai/i18n-plugin": "0.2.28", + "@flowgram.ai/materials-plugin": "0.2.28", + "@flowgram.ai/node": "0.2.28", + "@flowgram.ai/node-core-plugin": "0.2.28", + "@flowgram.ai/node-variable-plugin": "0.2.28", + "@flowgram.ai/playground-react": "0.2.28", + "@flowgram.ai/redux-devtool-plugin": "0.2.28", + "@flowgram.ai/renderer": "0.2.28", + "@flowgram.ai/shortcuts-plugin": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/variable-plugin": "0.2.28", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/fixed-drag-plugin": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-drag-plugin/-/fixed-drag-plugin-0.2.28.tgz", + "integrity": "sha512-dNrFBd6G1uFgkhf0LpAC9/7vV8kIQCBuBAmbeUi6EQZkmWsp9rVd4SAdKGqhNLrs6klt8n5oMltv6WJsMCRS6Q==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/renderer": "0.2.28", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/fixed-history-plugin": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-history-plugin/-/fixed-history-plugin-0.2.28.tgz", + "integrity": "sha512-s9ckjS6NS3ds+FfmvTIvO/02N8rSh+EcSDyKwLaGcrYk6X6Fs1T5h/PyBgGTryJ7EzseqlLTFVBqYXzgbymDtg==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/form-core": "0.2.28", + "@flowgram.ai/history": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "reflect-metadata": "~0.2.2" + } + }, + "node_modules/@flowgram.ai/fixed-layout-core": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-layout-core/-/fixed-layout-core-0.2.28.tgz", + "integrity": "sha512-pIa7vaksAmBZm3aaDMkpxB8RsS+fHGntDjGBWuP5tKJkjMKSXyPDzA7vGOwcVLyX3OrYiMD8ypuCDOGPruAr2A==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/renderer": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/fixed-layout-editor": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-layout-editor/-/fixed-layout-editor-0.2.28.tgz", + "integrity": "sha512-PpLQXWjC2L7eqVzNdt7Z4DxEh6GcIJyTrOQE0FjUB3XuS2IW7ciHogs3gEfAVpoFbZGj8tzIcs6IMUu1GxjeqQ==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/editor": "0.2.28", + "@flowgram.ai/fixed-drag-plugin": "0.2.28", + "@flowgram.ai/fixed-history-plugin": "0.2.28", + "@flowgram.ai/fixed-layout-core": "0.2.28", + "@flowgram.ai/history": "0.2.28", + "@flowgram.ai/reactive": "0.2.28", + "@flowgram.ai/select-box-plugin": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/form": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/form/-/form-0.2.28.tgz", + "integrity": "sha512-OgMDayBmHDUEjb6xWZeDmyn8BKZZazEsas3WJKlm2r6zaobygxE5K5NzJ/oy3OyLTgMtpYq3JOWZPgGu16ZRHg==", + "dependencies": { + "@flowgram.ai/reactive": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "fast-equals": "^2.0.0", + "lodash": "^4.17.21", + "nanoid": "^4.0.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/form-core": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/form-core/-/form-core-0.2.28.tgz", + "integrity": "sha512-JpapezHkKEl4hgNnhBo/5mBUcgAv7aUQpp3ypZ0g/0L9qLd9vB3PjBw7mL+K4t+954ckJMLW0AHJyqeWKXpO4g==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/form/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/free-layout-core": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/free-layout-core/-/free-layout-core-0.2.28.tgz", + "integrity": "sha512-oGLr4dTggl1e7h/usm0Hs4uYjiAKOU8ZY6W9xpEgvXJkRngiGDgh1+S3Sh7WfxvDQeHkaQhGRgcKNqECGqL1LQ==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/form-core": "0.2.28", + "@flowgram.ai/node": "0.2.28", + "@flowgram.ai/reactive": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "lodash-es": "^4.17.21", + "nanoid": "^4.0.2", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/free-layout-core/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/group-plugin": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/group-plugin/-/group-plugin-0.2.28.tgz", + "integrity": "sha512-Gk4A5oCOxF45Htf68OIR2yZyQR2416HMDsDISWCuAZiV/LafGDSNwRWiUv/6tpyUpwT1uHSipkTFSRwAWApO8g==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/renderer": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/history": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/history/-/history-0.2.28.tgz", + "integrity": "sha512-Em5yq2ZmP5lRglrdIfHe54zt3PSMM3BUj95jge5rC1uxNes7vhu5jaKhVIK6aehu9m4pOY8ZJ7/Dudr3w04aJQ==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "nanoid": "^4.0.2", + "reflect-metadata": "~0.2.2" + } + }, + "node_modules/@flowgram.ai/history-node-plugin": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/history-node-plugin/-/history-node-plugin-0.2.28.tgz", + "integrity": "sha512-NnFt6ocTvGwWjgluUuvJL+Xivj75LrPEFPqhC+GcmciiNmD0HkhW/ojO0aBKv8/Ppemfjo840dhSyxAxyInPsw==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/form": "0.2.28", + "@flowgram.ai/form-core": "0.2.28", + "@flowgram.ai/history": "0.2.28", + "@flowgram.ai/node": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8", + "styled-components": ">=4" + } + }, + "node_modules/@flowgram.ai/history/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/i18n": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/i18n/-/i18n-0.2.28.tgz", + "integrity": "sha512-FsFPlLa834zTBhLKgw6PWm+SoLvsPp2xL9XiphXdjD3kDgLvAsotiO/mdGGuwC+nNMByo2MsUgf7gS7BIVMuTw==", + "dependencies": { + "@flowgram.ai/utils": "0.2.28", + "i18n-js": "^4.5.1" + } + }, + "node_modules/@flowgram.ai/i18n-plugin": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/i18n-plugin/-/i18n-plugin-0.2.28.tgz", + "integrity": "sha512-U0AjDZekzx7Z8ttxSI5JZWE6A2rzQncILYQ4ALTPpaS6nFqna7ju2qEedcUAIf/xo4gZiYugcQc511lvqtnhLg==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/i18n": "0.2.28" + } + }, + "node_modules/@flowgram.ai/materials-plugin": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/materials-plugin/-/materials-plugin-0.2.28.tgz", + "integrity": "sha512-2s50E0MZwHnE4QVS564d2abd29iMv70r7cnRWeLtc6w91Y+mKHQWM5noeoi/diggIWUmj6w4pFYDnSOcN4DeGA==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/form-core": "0.2.28", + "@flowgram.ai/renderer": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8", + "styled-components": ">=4" + } + }, + "node_modules/@flowgram.ai/node": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/node/-/node-0.2.28.tgz", + "integrity": "sha512-XZ+kKjZdOFYdC8XL2L+BCYAa/wVLnb2dD/lfedMd9Fnf0QaJaQupwhwPnLtmL4HcdB8ro8uBICSIgVGXED66Uw==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/form": "0.2.28", + "@flowgram.ai/form-core": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "nanoid": "^4.0.2", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/node-core-plugin": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/node-core-plugin/-/node-core-plugin-0.2.28.tgz", + "integrity": "sha512-72R6APzlW2Yy1od2QibIhSW3TW+xz44s0+k4rzJ+EZW0gHEwtmo+OCQLo19WM0CrBFjAomTyK/2J5L6a1mp9OA==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/form-core": "0.2.28", + "@flowgram.ai/node": "0.2.28", + "@flowgram.ai/renderer": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8", + "styled-components": ">=4" + } + }, + "node_modules/@flowgram.ai/node-variable-plugin": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/node-variable-plugin/-/node-variable-plugin-0.2.28.tgz", + "integrity": "sha512-3GpLS6LG1w6Y24TJrXnxSa8BnMhs5sgjqv9VgBdx5DFQRynNtRV52bd3X+eNarNFMMcpEqTGiybrvRqmf84K/w==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/form-core": "0.2.28", + "@flowgram.ai/node": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/variable-plugin": "0.2.28", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8", + "styled-components": ">=4" + } + }, + "node_modules/@flowgram.ai/node/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/playground-react": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/playground-react/-/playground-react-0.2.28.tgz", + "integrity": "sha512-VvUewy+Av1XHeUa5CBPumkSZR3J/M0m4ME9na6hTfzs7bigKjU8rk+X7397JOWswUBBxGod3Lrw+ujttxxvNag==", + "dependencies": { + "@flowgram.ai/background-plugin": "0.2.28", + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/shortcuts-plugin": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/reactive": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/reactive/-/reactive-0.2.28.tgz", + "integrity": "sha512-WC8FWI4iBhrrip8If1ghFUhqBxGcHExE4rc6Erx1QQqN7YNFII5/cKF1wAaEgQ99rSVGFxIxjkwrUOjNXnnS6g==", + "dependencies": { + "@flowgram.ai/utils": "0.2.28" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/redux-devtool-plugin": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/redux-devtool-plugin/-/redux-devtool-plugin-0.2.28.tgz", + "integrity": "sha512-D8C0sWzGAFpFElacEHwONRr2vezcLa3jT6SRtYEbQ0poSA6HLPPYSigRjug8A19jd9NayCVulPm1M59GpuZF3Q==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/variable-core": "0.2.28", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2", + "styled-components": "^5" + } + }, + "node_modules/@flowgram.ai/redux-devtool-plugin/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@flowgram.ai/redux-devtool-plugin/node_modules/styled-components": { + "version": "5.3.11", + "resolved": "https://registry.npmmirror.com/styled-components/-/styled-components-5.3.11.tgz", + "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/@flowgram.ai/redux-devtool-plugin/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@flowgram.ai/renderer": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/renderer/-/renderer-0.2.28.tgz", + "integrity": "sha512-7AVVZqsUFeNvq339UfUSohk0ByVDq9ocllODScbeIAVWGZyNjG0tyJyaIEoCYrSqh2l+78C+AsaaBJvOPZG5bg==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/i18n": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/select-box-plugin": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/select-box-plugin/-/select-box-plugin-0.2.28.tgz", + "integrity": "sha512-xIPLKpT3hUtlHzvSOqeu8yqhVJDL7En8cRawKWwJjBs0nrFXEGryPnMCrsmbF1JwTFZrvKrznJacocqmiLwI3A==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/renderer": "0.2.28", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/shortcuts-plugin": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/shortcuts-plugin/-/shortcuts-plugin-0.2.28.tgz", + "integrity": "sha512-S3XkBmZnQ4FZ+A7sD56v6L2UFOhMSwqTmA4S1G8XqMvmbfHjUjR/OxojfQw/MSd5QNygD4ikBesPoj6FMP45FQ==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2", + "styled-components": "^5" + } + }, + "node_modules/@flowgram.ai/shortcuts-plugin/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@flowgram.ai/shortcuts-plugin/node_modules/styled-components": { + "version": "5.3.11", + "resolved": "https://registry.npmmirror.com/styled-components/-/styled-components-5.3.11.tgz", + "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/@flowgram.ai/shortcuts-plugin/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@flowgram.ai/utils": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/utils/-/utils-0.2.28.tgz", + "integrity": "sha512-HG4LpVo3gUcTp/tJCe72vZ9nByRXgkvp2wYyFFD3ZnGvEL8V3PIRwHGvBMP0UqgYBPHOZyqOZhOenP73msvkwg==", + "dependencies": { + "clsx": "^1.1.1", + "inversify": "^6.0.1", + "nanoid": "^4.0.2", + "reflect-metadata": "~0.2.2" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/utils/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@flowgram.ai/utils/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/variable-core": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-core/-/variable-core-0.2.28.tgz", + "integrity": "sha512-Au6UCupM1d044w++GMv2HC4YGc2nsDNBdem71gjxcDLowGo9FDgKF1Fxh2E+UfCv9QfAcShJzCg0R4EtCpEsuQ==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "fast-equals": "^2.0.0", + "inversify": "^6.0.1", + "lodash": "^4.17.21", + "nanoid": "^4.0.2", + "reflect-metadata": "~0.2.2", + "rxjs": "^7.8.1" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@flowgram.ai/variable-core/node_modules/nanoid": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-4.0.2.tgz", + "integrity": "sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^14 || ^16 || >=18" + } + }, + "node_modules/@flowgram.ai/variable-layout": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-layout/-/variable-layout-0.2.28.tgz", + "integrity": "sha512-31mxxnLJFanmdMkMvl6WzGV+CO15gPT0jnMsf0Mqp+s7qadtwuHcwzsDEUU9RKR+d/oXuGBpNGrdtQJzBh2JMQ==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/free-layout-core": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/variable-core": "0.2.28", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2", + "styled-components": "^5" + } + }, + "node_modules/@flowgram.ai/variable-layout/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@flowgram.ai/variable-layout/node_modules/styled-components": { + "version": "5.3.11", + "resolved": "https://registry.npmmirror.com/styled-components/-/styled-components-5.3.11.tgz", + "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/@flowgram.ai/variable-layout/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@flowgram.ai/variable-plugin": { + "version": "0.2.28", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-plugin/-/variable-plugin-0.2.28.tgz", + "integrity": "sha512-lPxcLNClR865dQMGAGy+NSbC+80mWbVXRolbN1ScIj7on9ypTtDs86VTdFGlKMzP8BSWIXM1Rjb6oSEShCxuJA==", + "dependencies": { + "@flowgram.ai/core": "0.2.28", + "@flowgram.ai/document": "0.2.28", + "@flowgram.ai/free-layout-core": "0.2.28", + "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/variable-core": "0.2.28", + "@flowgram.ai/variable-layout": "0.2.28", + "inversify": "^6.0.1", + "reflect-metadata": "~0.2.2", + "styled-components": "^5" + } + }, + "node_modules/@flowgram.ai/variable-plugin/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@flowgram.ai/variable-plugin/node_modules/styled-components": { + "version": "5.3.11", + "resolved": "https://registry.npmmirror.com/styled-components/-/styled-components-5.3.11.tgz", + "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/@flowgram.ai/variable-plugin/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz", @@ -2828,6 +3704,28 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@inversifyjs/common": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/@inversifyjs/common/-/common-1.4.0.tgz", + "integrity": "sha512-qfRJ/3iOlCL/VfJq8+4o5X4oA14cZSBbpAmHsYj8EsIit1xDndoOl0xKOyglKtQD4u4gdNVxMHx4RWARk/I4QA==" + }, + "node_modules/@inversifyjs/core": { + "version": "1.3.5", + "resolved": "https://registry.npmmirror.com/@inversifyjs/core/-/core-1.3.5.tgz", + "integrity": "sha512-B4MFXabhNTAmrfgB+yeD6wd/GIvmvWC6IQ8Rh/j2C3Ix69kmqwz9pr8Jt3E+Nho9aEHOQCZaGmrALgtqRd+oEQ==", + "dependencies": { + "@inversifyjs/common": "1.4.0", + "@inversifyjs/reflect-metadata-utils": "0.2.4" + } + }, + "node_modules/@inversifyjs/reflect-metadata-utils": { + "version": "0.2.4", + "resolved": "https://registry.npmmirror.com/@inversifyjs/reflect-metadata-utils/-/reflect-metadata-utils-0.2.4.tgz", + "integrity": "sha512-u95rV3lKfG+NT2Uy/5vNzoDujos8vN8O18SSA5UyhxsGYd4GLQn/eUsGXfOsfa7m34eKrDelTKRUX1m/BcNX5w==", + "peerDependencies": { + "reflect-metadata": "0.2.2" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmmirror.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -2844,7 +3742,6 @@ "version": "0.3.12", "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", - "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" @@ -2854,7 +3751,6 @@ "version": "3.1.2", "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "engines": { "node": ">=6.0.0" } @@ -2873,14 +3769,12 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.29", "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", - "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -2979,6 +3873,28 @@ "node": ">= 8" } }, + "node_modules/@phosphor/algorithm": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@phosphor/algorithm/-/algorithm-1.2.0.tgz", + "integrity": "sha512-C9+dnjXyU2QAkWCW6QVDGExk4hhwxzAKf5/FIuYlHAI9X5vFv99PYm0EREDxX1PbMuvfFBZhPNu0PvuSDQ7sFA==" + }, + "node_modules/@phosphor/collections": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/@phosphor/collections/-/collections-1.2.0.tgz", + "integrity": "sha512-T9/0EjSuY6+ga2LIFRZ0xupciOR3Qnyy8Q95lhGTC0FXZUFwC8fl9e8On6IcwasCszS+1n8dtZUWSIynfgdpzw==", + "dependencies": { + "@phosphor/algorithm": "^1.2.0" + } + }, + "node_modules/@phosphor/messaging": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/@phosphor/messaging/-/messaging-1.3.0.tgz", + "integrity": "sha512-k0JE+BTMKlkM335S2AmmJxoYYNRwOdW5jKBqLgjJdGRvUQkM0+2i60ahM45+J23atGJDv9esKUUBINiKHFhLew==", + "dependencies": { + "@phosphor/algorithm": "^1.2.0", + "@phosphor/collections": "^1.2.0" + } + }, "node_modules/@pkgr/core": { "version": "0.2.9", "resolved": "https://registry.npmmirror.com/@pkgr/core/-/core-0.2.9.tgz", @@ -3702,6 +4618,11 @@ "vite": "^5.2.0 || ^6 || ^7" } }, + "node_modules/@tweenjs/tween.js": { + "version": "18.6.4", + "resolved": "https://registry.npmmirror.com/@tweenjs/tween.js/-/tween.js-18.6.4.tgz", + "integrity": "sha512-lB9lMjuqjtuJrx7/kOkqQBtllspPIN+96OvTCeJ2j5FEzinoAXTdAMFnDAQT1KVPRlnYfBrqxtqP66vDM40xxQ==" + }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -3839,6 +4760,12 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", + "peer": true + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.38.0", "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", @@ -4805,12 +5732,35 @@ "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/babel-plugin-styled-components": { + "version": "2.1.4", + "resolved": "https://registry.npmmirror.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", + "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "lodash": "^4.17.21", + "picomatch": "^2.3.1" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "engines": { + "node": "*" + } + }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -4836,7 +5786,6 @@ "version": "4.24.4", "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.24.4.tgz", "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -4945,11 +5894,18 @@ "node": ">=6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001706", "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001706.tgz", "integrity": "sha512-3ZczoTApMAZwPKYWmwVbQMFpXBDds3/0VciVoUwPUbldlYyVLmRVuRs/PcUZtHpbLRpzzDvrvnFuREsGt6lUug==", - "dev": true, "funding": [ { "type": "opencollective", @@ -5049,8 +6005,7 @@ "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/cookie": { "version": "1.0.2", @@ -5122,6 +6077,24 @@ "node": ">= 8" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", @@ -5187,7 +6160,6 @@ "version": "4.4.1", "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, "dependencies": { "ms": "^2.1.3" }, @@ -5274,8 +6246,7 @@ "node_modules/electron-to-chromium": { "version": "1.5.74", "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.74.tgz", - "integrity": "sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==", - "dev": true + "integrity": "sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==" }, "node_modules/enhanced-resolve": { "version": "5.18.2", @@ -5476,15 +6447,14 @@ "version": "3.2.0", "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "engines": { "node": ">=6" } }, "node_modules/eslint": { - "version": "9.31.0", - "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.31.0.tgz", - "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "version": "9.32.0", + "resolved": "https://registry.npmmirror.com/eslint/-/eslint-9.32.0.tgz", + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -5493,8 +6463,8 @@ "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.31.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/js": "9.32.0", + "@eslint/plugin-kit": "^0.3.4", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -5661,9 +6631,9 @@ } }, "node_modules/eslint-plugin-better-tailwindcss": { - "version": "3.7.1", - "resolved": "https://registry.npmmirror.com/eslint-plugin-better-tailwindcss/-/eslint-plugin-better-tailwindcss-3.7.1.tgz", - "integrity": "sha512-ROTCf93dM9PcSiXVxXpfN/CK8WTAwjImi//c1DUjBfgRbMyXjkp5JPy/CAf+gJIElzSPkDPvMaFrg08NtXJ1Qg==", + "version": "3.7.2", + "resolved": "https://registry.npmmirror.com/eslint-plugin-better-tailwindcss/-/eslint-plugin-better-tailwindcss-3.7.2.tgz", + "integrity": "sha512-uJnoDGmUPtxHHcIW5njEoh1CYlq39E4J2pWq+D7REDGhW41arSCeeaiiwAT4Ok04kXNvmFBiYmC6Jdd6Bz7o2Q==", "dev": true, "dependencies": { "@eslint/css-tree": "^3.6.2", @@ -5976,6 +6946,11 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, + "node_modules/fast-equals": { + "version": "2.0.4", + "resolved": "https://registry.npmmirror.com/fast-equals/-/fast-equals-2.0.4.tgz", + "integrity": "sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", @@ -6174,7 +7149,6 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -6393,6 +7367,19 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/html-parse-stringify": { "version": "3.0.1", "resolved": "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", @@ -6401,6 +7388,16 @@ "void-elements": "3.1.0" } }, + "node_modules/i18n-js": { + "version": "4.5.1", + "resolved": "https://registry.npmmirror.com/i18n-js/-/i18n-js-4.5.1.tgz", + "integrity": "sha512-n7jojFj1WC0tztgr0I8jqTXuIlY1xNzXnC3mjKX/YjJhimdM+jXM8vOmn9d3xQFNC6qDHJ4ovhdrGXrRXLIGkA==", + "dependencies": { + "bignumber.js": "*", + "lodash": "*", + "make-plural": "*" + } + }, "node_modules/i18next": { "version": "25.3.2", "resolved": "https://registry.npmmirror.com/i18next/-/i18next-25.3.2.tgz", @@ -6501,6 +7498,18 @@ "resolved": "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz", "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==" }, + "node_modules/inversify": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/inversify/-/inversify-6.2.2.tgz", + "integrity": "sha512-KB836KHbZ9WrUnB8ax5MtadOwnqQYa+ZJO3KWbPFgcr4RIEnHM621VaqFZzOZd9+U7ln6upt9n0wJei7x2BNqw==", + "dependencies": { + "@inversifyjs/common": "1.4.0", + "@inversifyjs/core": "1.3.5" + }, + "peerDependencies": { + "reflect-metadata": "~0.2.2" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -6913,7 +7922,6 @@ "version": "3.1.0", "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "bin": { "jsesc": "bin/jsesc" }, @@ -6951,7 +7959,6 @@ "version": "2.2.3", "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "bin": { "json5": "lib/cli.js" }, @@ -7272,7 +8279,6 @@ "version": "5.1.1", "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, "dependencies": { "yallist": "^3.0.2" } @@ -7294,6 +8300,11 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/make-plural": { + "version": "7.4.0", + "resolved": "https://registry.npmmirror.com/make-plural/-/make-plural-7.4.0.tgz", + "integrity": "sha512-4/gC9KVNTV6pvYg2gFeQYTW3mWaoJt7WZE5vrp1KnQDgW92JtYZnzmZT81oj/dUTqAIu0ufI2x3dkgu3bB1tYg==" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7406,8 +8417,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nanoid": { "version": "5.1.5", @@ -7450,8 +8460,7 @@ "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" }, "node_modules/object-assign": { "version": "4.1.1", @@ -7663,14 +8672,12 @@ "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -7688,9 +8695,9 @@ } }, "node_modules/pocketbase": { - "version": "0.26.1", - "resolved": "https://registry.npmmirror.com/pocketbase/-/pocketbase-0.26.1.tgz", - "integrity": "sha512-fjcPDpxyqTZCwqGUTPUV7vssIsNMqHxk9GxbhxYHPEf18RqX2d9cpSqbbHk7aas30jqkgptuKfG7aY/Mytjj3g==" + "version": "0.26.2", + "resolved": "https://registry.npmmirror.com/pocketbase/-/pocketbase-0.26.2.tgz", + "integrity": "sha512-WA8EOBc3QnSJh8rJ3iYoi9DmmPOMFIgVfAmIGux7wwruUEIzXgvrO4u0W2htfQjGIcyezJkdZOy5Xmh7SxAftw==" }, "node_modules/possible-typed-array-names": { "version": "1.0.0", @@ -7749,8 +8756,7 @@ "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, "node_modules/postcss/node_modules/nanoid": { "version": "3.3.11", @@ -8515,9 +9521,9 @@ } }, "node_modules/react-router": { - "version": "7.7.0", - "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.7.0.tgz", - "integrity": "sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw==", + "version": "7.7.1", + "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.7.1.tgz", + "integrity": "sha512-jVKHXoWRIsD/qS6lvGveckwb862EekvapdHJN/cGmzw40KnJH5gg53ujOJ4qX6EKIK9LSBfFed/xiQ5yeXNrUA==", "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -8536,11 +9542,11 @@ } }, "node_modules/react-router-dom": { - "version": "7.7.0", - "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-7.7.0.tgz", - "integrity": "sha512-wwGS19VkNBkneVh9/YD0pK3IsjWxQUVMDD6drlG7eJpo1rXBtctBqDyBm/k+oKHRAm1x9XWT3JFC82QI9YOXXA==", + "version": "7.7.1", + "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-7.7.1.tgz", + "integrity": "sha512-bavdk2BA5r3MYalGKZ01u8PGuDBloQmzpBZVhDLrOOv1N943Wq6dcM9GhB3x8b7AbqPMEezauv4PeGkAJfy7FQ==", "dependencies": { - "react-router": "7.7.0" + "react-router": "7.7.1" }, "engines": { "node": ">=20.0.0" @@ -8567,6 +9573,11 @@ "pify": "^2.3.0" } }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.9", "resolved": "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.9.tgz", @@ -8801,6 +9812,14 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -9075,7 +10094,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9200,6 +10218,98 @@ "resolved": "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.2.tgz", "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" }, + "node_modules/styled-components": { + "version": "6.1.19", + "resolved": "https://registry.npmmirror.com/styled-components/-/styled-components-6.1.19.tgz", + "integrity": "sha512-1v/e3Dl1BknC37cXMhwGomhO8AkYmN41CqyX9xhUDxry1ns3BFQy2lLDRQXJRdVVWB9OHemv/53xaStimvWyuA==", + "peer": true, + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.49", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "peer": true + }, + "node_modules/styled-components/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.49", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "peer": true + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "peer": true + }, "node_modules/stylis": { "version": "4.3.4", "resolved": "https://registry.npmmirror.com/stylis/-/stylis-4.3.4.tgz", @@ -9726,7 +10836,6 @@ "version": "1.1.1", "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -10008,8 +11117,7 @@ "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "node_modules/yaml": { "version": "2.5.0", @@ -10038,9 +11146,9 @@ } }, "node_modules/zod": { - "version": "4.0.5", - "resolved": "https://registry.npmmirror.com/zod/-/zod-4.0.5.tgz", - "integrity": "sha512-/5UuuRPStvHXu7RS+gmvRf4NXrNxpSllGwDnCBcJZtQsKrviYXm54yDGV2KYNLT5kq0lHGcl7lqWJLgSaG+tgA==", + "version": "4.0.10", + "resolved": "https://registry.npmmirror.com/zod/-/zod-4.0.10.tgz", + "integrity": "sha512-3vB+UU3/VmLL2lvwcY/4RV2i9z/YU0DTV/tDuYjrwmx5WeJ7hwy+rGEEx8glHp6Yxw7ibRbKSaIFBgReRPe5KA==", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/ui/package.json b/ui/package.json index 385077524..8150cac20 100644 --- a/ui/package.json +++ b/ui/package.json @@ -15,6 +15,7 @@ "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language": "^6.11.2", "@codemirror/legacy-modes": "^6.5.1", + "@flowgram.ai/fixed-layout-editor": "^0.2.28", "@tabler/icons-react": "^3.34.1", "@uiw/codemirror-extensions-basic-setup": "^4.24.1", "@uiw/codemirror-theme-vscode": "^4.24.1", @@ -29,19 +30,19 @@ "i18next-browser-languagedetector": "^8.2.0", "immer": "^10.1.1", "nanoid": "^5.1.5", - "pocketbase": "^0.26.1", + "pocketbase": "^0.26.2", "radash": "^12.1.1", "react": "^18.3.1", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.3.1", "react-i18next": "^15.6.1", - "react-router-dom": "^7.7.0", + "react-router-dom": "^7.7.1", "tailwind-merge": "^3.3.1", - "zod": "^4.0.5", + "zod": "^4.0.10", "zustand": "^5.0.6" }, "devDependencies": { - "@eslint/js": "^9.31.0", + "@eslint/js": "^9.32.0", "@tailwindcss/postcss": "^4.1.11", "@tailwindcss/vite": "^4.1.11", "@types/file-saver": "^2.0.7", @@ -52,10 +53,10 @@ "@types/react-dom": "^18.3.7", "@vitejs/plugin-legacy": "^6.1.1", "@vitejs/plugin-react": "^4.7.0", - "eslint": "^9.31.0", + "eslint": "^9.32.0", "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.4", - "eslint-plugin-better-tailwindcss": "^3.7.1", + "eslint-plugin-better-tailwindcss": "^3.7.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-prettier": "^5.5.3", "eslint-plugin-react-hooks": "^5.2.0", diff --git a/ui/src/components/TextFileInput.tsx b/ui/src/components/TextFileInput.tsx index 2adc8ba3a..61fcda2ac 100644 --- a/ui/src/components/TextFileInput.tsx +++ b/ui/src/components/TextFileInput.tsx @@ -4,7 +4,7 @@ import { IconFileImport } from "@tabler/icons-react"; import { Button, type ButtonProps, Input, type UploadProps } from "antd"; import { type TextAreaProps } from "antd/es/input/TextArea"; -import { readFileContent } from "@/utils/file"; +import { readFileAsText } from "@/utils/file"; export interface TextFileInputProps extends Omit { accept?: UploadProps["accept"]; @@ -27,7 +27,7 @@ const TextFileInput = ({ className, style, accept, disabled, readOnly, uploadTex const handleFileChange = async (e: ChangeEvent) => { const { files } = e.target as HTMLInputElement; if (files?.length) { - const value = await readFileContent(files[0]); + const value = await readFileAsText(files[0]); onChange?.(value); } }; diff --git a/ui/src/utils/file.ts b/ui/src/utils/file.ts index 16edf7aff..2b7fc7a90 100644 --- a/ui/src/utils/file.ts +++ b/ui/src/utils/file.ts @@ -1,4 +1,4 @@ -export function readFileContent(file: File): Promise { +export function readFileAsText(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader(); From 2861a61df479405b26e889c5970bdac858825d60 Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 28 Jul 2025 13:28:45 +0800 Subject: [PATCH 06/25] feat: support configuring node name on deployment to 1panel site --- internal/deployer/providers.go | 1 + .../providers/1panel-site/1panel_site.go | 23 +++++++++++++++---- pkg/sdk3rd/1panel/v2/client.go | 14 +++++++++++ .../DeployNodeConfigForm1PanelSiteConfig.tsx | 11 +++++++++ .../i18n/locales/en/nls.workflow.nodes.json | 5 +++- .../i18n/locales/zh/nls.workflow.nodes.json | 3 +++ 6 files changed, 51 insertions(+), 6 deletions(-) diff --git a/internal/deployer/providers.go b/internal/deployer/providers.go index 103240f4d..92b42c783 100644 --- a/internal/deployer/providers.go +++ b/internal/deployer/providers.go @@ -147,6 +147,7 @@ func createSSLDeployerProvider(options *deployerProviderOptions) (core.SSLDeploy ApiVersion: access.ApiVersion, ApiKey: access.ApiKey, AllowInsecureConnections: access.AllowInsecureConnections, + NodeName: xmaps.GetString(options.ProviderServiceConfig, "nodeName"), ResourceType: p1PanelSite.ResourceType(xmaps.GetOrDefaultString(options.ProviderServiceConfig, "resourceType", string(p1PanelSite.RESOURCE_TYPE_WEBSITE))), WebsiteId: xmaps.GetInt64(options.ProviderServiceConfig, "websiteId"), CertificateId: xmaps.GetInt64(options.ProviderServiceConfig, "certificateId"), diff --git a/pkg/core/ssl-deployer/providers/1panel-site/1panel_site.go b/pkg/core/ssl-deployer/providers/1panel-site/1panel_site.go index 985809d7f..ffce4d261 100644 --- a/pkg/core/ssl-deployer/providers/1panel-site/1panel_site.go +++ b/pkg/core/ssl-deployer/providers/1panel-site/1panel_site.go @@ -24,6 +24,9 @@ type SSLDeployerProviderConfig struct { ApiKey string `json:"apiKey"` // 是否允许不安全的连接。 AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` + // 子节点名称。 + // 选填。 + NodeName string `json:"nodeName,omitempty"` // 部署资源类型。 ResourceType ResourceType `json:"resourceType"` // 网站 ID。 @@ -48,7 +51,7 @@ func NewSSLDeployerProvider(config *SSLDeployerProviderConfig) (*SSLDeployerProv return nil, errors.New("the configuration of the ssl deployer provider is nil") } - client, err := createSDKClient(config.ServerUrl, config.ApiVersion, config.ApiKey, config.AllowInsecureConnections) + client, err := createSDKClient(config.ServerUrl, config.ApiVersion, config.ApiKey, config.AllowInsecureConnections, config.NodeName) if err != nil { return nil, fmt.Errorf("could not create sdk client: %w", err) } @@ -244,7 +247,7 @@ const ( sdkVersionV2 = "v2" ) -func createSDKClient(serverUrl, apiVersion, apiKey string, skipTlsVerify bool) (any, error) { +func createSDKClient(serverUrl, apiVersion, apiKey string, skipTlsVerify bool, nodeName string) (any, error) { if apiVersion == sdkVersionV1 { client, err := onepanelsdk.NewClient(serverUrl, apiKey) if err != nil { @@ -257,9 +260,19 @@ func createSDKClient(serverUrl, apiVersion, apiKey string, skipTlsVerify bool) ( return client, nil } else if apiVersion == sdkVersionV2 { - client, err := onepanelsdkv2.NewClient(serverUrl, apiKey) - if err != nil { - return nil, err + var client *onepanelsdkv2.Client + if nodeName == "" { + temp, err := onepanelsdkv2.NewClient(serverUrl, apiKey) + if err != nil { + return nil, err + } + client = temp + } else { + temp, err := onepanelsdkv2.NewClientWithNode(serverUrl, apiKey, nodeName) + if err != nil { + return nil, err + } + client = temp } if skipTlsVerify { diff --git a/pkg/sdk3rd/1panel/v2/client.go b/pkg/sdk3rd/1panel/v2/client.go index 3a74c5bf5..64b604bff 100644 --- a/pkg/sdk3rd/1panel/v2/client.go +++ b/pkg/sdk3rd/1panel/v2/client.go @@ -47,6 +47,20 @@ func NewClient(serverUrl, apiKey string) (*Client, error) { return &Client{client}, nil } +func NewClientWithNode(serverUrl, apiKey, node string) (*Client, error) { + client, err := NewClient(serverUrl, apiKey) + if err != nil { + return nil, err + } + + if node == "" { + node = "local" + } + client.client.SetHeader("CurrentNode", node) + + return client, nil +} + func (c *Client) SetTimeout(timeout time.Duration) *Client { c.client.SetTimeout(timeout) return c diff --git a/ui/src/components/workflow/node/DeployNodeConfigForm1PanelSiteConfig.tsx b/ui/src/components/workflow/node/DeployNodeConfigForm1PanelSiteConfig.tsx index 912ba4407..77bc27d88 100644 --- a/ui/src/components/workflow/node/DeployNodeConfigForm1PanelSiteConfig.tsx +++ b/ui/src/components/workflow/node/DeployNodeConfigForm1PanelSiteConfig.tsx @@ -6,6 +6,7 @@ import { z } from "zod"; import Show from "@/components/Show"; type DeployNodeConfigForm1PanelSiteConfigFieldValues = Nullish<{ + nodeName?: string; resourceType: string; websiteId?: string | number; certificateId?: string | number; @@ -38,6 +39,7 @@ const DeployNodeConfigForm1PanelSiteConfig = ({ const { t } = useTranslation(); const formSchema = z.object({ + nodeName: z.string().nullish(), resourceType: z.literal([RESOURCE_TYPE_WEBSITE, RESOURCE_TYPE_CERTIFICATE], t("workflow_node.deploy.form.1panel_site_resource_type.placeholder")), websiteId: z .union([z.string(), z.number().int()]) @@ -71,6 +73,15 @@ const DeployNodeConfigForm1PanelSiteConfig = ({ name={formName} onValuesChange={handleFormChange} > + } + > + + + - + {workflow.name || "\u00A0"} + + + -
+
{ return (

@@ -158,6 +213,8 @@ const WorkflowDetailBaseName = () => { { const handleValueConfirm = async (value: string) => { value = value.trim(); - if (value === (workflow.description || "")) { + if (!value || value === (workflow.description || "")) { setEditing(false); return; } @@ -235,6 +292,8 @@ const WorkflowDetailBaseDescription = () => { handleValueConfirm(e.target.value)} diff --git a/ui/src/pages/workflows/WorkflowDetailDesign.tsx b/ui/src/pages/workflows/WorkflowDetailDesign.tsx index 5bd694e74..dd64fec48 100644 --- a/ui/src/pages/workflows/WorkflowDetailDesign.tsx +++ b/ui/src/pages/workflows/WorkflowDetailDesign.tsx @@ -1,16 +1,14 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { IconArrowBackUp, IconDots, IconPlayerPlay } from "@tabler/icons-react"; +import { IconArrowBackUp, IconDots } from "@tabler/icons-react"; import { Alert, App, Button, Card, Dropdown, Space } from "antd"; import { isEqual } from "radash"; -import { startRun as startWorkflowRun } from "@/api/workflows"; import Show from "@/components/Show"; import WorkflowElementsContainer from "@/components/workflow/WorkflowElementsContainer"; import { isAllNodesValidated } from "@/domain/workflow"; import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun"; import { useZustandShallowSelector } from "@/hooks"; -import { subscribe as subscribeWorkflow } from "@/repository/workflow"; import { useWorkflowStore } from "@/stores/workflow"; import { getErrMsg } from "@/utils/error"; @@ -24,37 +22,17 @@ const WorkflowDetailDesign = () => { const [isPendingOrRunning, setIsPendingOrRunning] = useState(false); const [allowRollback, setAllowRollback] = useState(false); const [allowPublish, setAllowPublish] = useState(false); - const [allowRun, setAllowRun] = useState(false); useEffect(() => { const pending = workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.PENDING || workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.RUNNING; setIsPendingOrRunning(pending); }, [workflow]); - useEffect(() => { - if (isPendingOrRunning) { - let unsubscribeFn: Awaited> | undefined = undefined; - subscribeWorkflow(workflow.id, (cb) => { - if (cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.PENDING && cb.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) { - setIsPendingOrRunning(false); - unsubscribeFn?.(); - } - }).then((res) => { - unsubscribeFn = res; - }); - - return () => { - unsubscribeFn?.(); - }; - } - }, [workflow.id, isPendingOrRunning]); - useEffect(() => { const hasContent = !!workflow.content; const hasChanges = workflow.hasDraft! || !isEqual(workflow.draft, workflow.content); setAllowRollback(!isPendingOrRunning && hasContent && hasChanges); setAllowPublish(!isPendingOrRunning && hasChanges); - setAllowRun(hasContent); }, [workflow.content, workflow.draft, workflow.hasDraft, isPendingOrRunning]); const handleRollbackClick = () => { @@ -96,46 +74,6 @@ const WorkflowDetailDesign = () => { }); }; - const handleRunClick = () => { - const { promise, resolve, reject } = Promise.withResolvers(); - if (workflow.hasDraft) { - modal.confirm({ - title: t("workflow.action.run.modal.title"), - content: t("workflow.action.run.modal.content"), - onOk: () => resolve(void 0), - onCancel: () => reject(), - }); - } else { - resolve(void 0); - } - - promise.then(async () => { - let unsubscribeFn: Awaited> | undefined = undefined; - - try { - setIsPendingOrRunning(true); - - // subscribe before running workflow - unsubscribeFn = await subscribeWorkflow(workflow.id, (e) => { - if (e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.PENDING && e.record.lastRunStatus !== WORKFLOW_RUN_STATUSES.RUNNING) { - setIsPendingOrRunning(false); - unsubscribeFn?.(); - } - }); - - await startWorkflowRun(workflow.id); - - message.info(t("workflow.action.run.prompt")); - } catch (err) { - setIsPendingOrRunning(false); - unsubscribeFn?.(); - - console.error(err); - message.warning(t("common.text.operation_failed")); - } - }); - }; - return (
{ }, }} > -
+
@@ -156,32 +94,27 @@ const WorkflowDetailDesign = () => {
- - - - - , - onClick: handleRollbackClick, - }, - ], - }} - trigger={["click"]} - > -
diff --git a/ui/src/stores/access/index.ts b/ui/src/stores/access/index.ts index 207fc1fea..c51fa3bf7 100644 --- a/ui/src/stores/access/index.ts +++ b/ui/src/stores/access/index.ts @@ -4,18 +4,9 @@ import { create } from "zustand"; import { type AccessModel } from "@/domain/access"; import { list as listAccesses, remove as removeAccess, save as saveAccess } from "@/repository/access"; -export interface AccessesState { - accesses: AccessModel[]; - loading: boolean; - loadedAtOnce: boolean; +import { type AccessesState, type AccessesStore } from "./types"; - fetchAccesses: () => Promise; - createAccess: (access: MaybeModelRecord) => Promise; - updateAccess: (access: MaybeModelRecordWithId) => Promise; - deleteAccess: (access: MaybeModelRecordWithId | MaybeModelRecordWithId[]) => Promise; -} - -export const useAccessesStore = create((set) => { +export const useAccessesStore = create((set) => { let fetcher: Promise | null = null; // 防止多次重复请求 return { diff --git a/ui/src/stores/access/types.ts b/ui/src/stores/access/types.ts new file mode 100644 index 000000000..2c987beb6 --- /dev/null +++ b/ui/src/stores/access/types.ts @@ -0,0 +1,16 @@ +import { type AccessModel } from "@/domain/access"; + +export interface AccessesState { + accesses: AccessModel[]; + loading: boolean; + loadedAtOnce: boolean; +} + +export interface AccessesActions { + fetchAccesses: () => Promise; + createAccess: (access: MaybeModelRecord) => Promise; + updateAccess: (access: MaybeModelRecordWithId) => Promise; + deleteAccess: (access: MaybeModelRecordWithId | MaybeModelRecordWithId[]) => Promise; +} + +export interface AccessesStore extends AccessesState, AccessesActions {} diff --git a/ui/src/stores/contact/index.ts b/ui/src/stores/contact/index.ts index ae8e25d20..e554bfb8b 100644 --- a/ui/src/stores/contact/index.ts +++ b/ui/src/stores/contact/index.ts @@ -4,18 +4,9 @@ import { create } from "zustand"; import { type EmailsSettingsContent, SETTINGS_NAMES, type SettingsModel } from "@/domain/settings"; import { get as getSettings, save as saveSettings } from "@/repository/settings"; -export interface ContactEmailsState { - emails: string[]; - loading: boolean; - loadedAtOnce: boolean; +import { type ContactEmailsState, type ContactEmailsStore } from "./types"; - fetchEmails: () => Promise; - setEmails: (emails: string[]) => Promise; - addEmail: (email: string) => Promise; - removeEmail: (email: string) => Promise; -} - -export const useContactEmailsStore = create((set, get) => { +export const useContactEmailsStore = create((set, get) => { let fetcher: Promise> | null = null; // 防止多次重复请求 let settings: SettingsModel; // 记录当前设置的其他字段,保存回数据库时用 diff --git a/ui/src/stores/contact/types.ts b/ui/src/stores/contact/types.ts new file mode 100644 index 000000000..a1ee603ce --- /dev/null +++ b/ui/src/stores/contact/types.ts @@ -0,0 +1,14 @@ +export interface ContactEmailsState { + emails: string[]; + loading: boolean; + loadedAtOnce: boolean; +} + +export interface ContactEmailsActions { + fetchEmails: () => Promise; + setEmails: (emails: string[]) => Promise; + addEmail: (email: string) => Promise; + removeEmail: (email: string) => Promise; +} + +export interface ContactEmailsStore extends ContactEmailsState, ContactEmailsActions {} diff --git a/ui/src/stores/workflow/index.ts b/ui/src/stores/workflow/index.ts index 405a5bb20..962ec6a97 100644 --- a/ui/src/stores/workflow/index.ts +++ b/ui/src/stores/workflow/index.ts @@ -3,7 +3,6 @@ import { create } from "zustand"; import { type WorkflowModel, - type WorkflowNode, type WorkflowNodeConfigForStart, addBranch, addNode, @@ -14,280 +13,277 @@ import { removeNode, updateNode, } from "@/domain/workflow"; -import { get as getWorkflow, save as saveWorkflow } from "@/repository/workflow"; +import { get as getWorkflow, save as saveWorkflow, subscribe as subscribeWorkflow } from "@/repository/workflow"; -export type WorkflowState = { - workflow: WorkflowModel; - initialized: boolean; +import { type WorkflowState, type WorkflowStore } from "./types"; - init(id: string): void; - setBaseInfo: (name: string, description: string) => void; - setEnabled(enabled: boolean): void; - publish(): void; - rollback(): void; - destroy(): void; - - addNode: (node: WorkflowNode, previousNodeId: string) => void; - duplicateNode: (node: WorkflowNode) => void; - updateNode: (node: WorkflowNode) => void; - removeNode: (node: WorkflowNode) => void; - - addBranch: (branchId: string) => void; - duplicateBranch: (branchId: string, index: number) => void; - removeBranch: (branchId: string, index: number) => void; - - getWorkflowOuptutBeforeId: (nodeId: string, typeFilter?: string | string[]) => WorkflowNode[]; -}; - -export const useWorkflowStore = create((set, get) => ({ - workflow: {} as WorkflowModel, - initialized: false, - - init: async (id: string) => { - const data = await getWorkflow(id); - - set({ - workflow: data, - initialized: true, - }); - }, - - destroy: () => { - set({ - workflow: {} as WorkflowModel, - initialized: false, - }); - }, - - setBaseInfo: async (name: string, description: string) => { +export const useWorkflowStore = create((set, get) => { + const ensureInitialized = () => { if (!get().initialized) throw "Workflow not initialized yet"; + }; - const resp = await saveWorkflow({ - id: get().workflow.id!, - name: name || "", - description: description || "", - }); + let unsubscriber: (() => void) | undefined = undefined; - set((state: WorkflowState) => { - return { - workflow: produce(state.workflow, (draft) => { - draft.name = resp.name; - draft.description = resp.description; - }), - }; - }); - }, + return { + workflow: {} as WorkflowModel, + initialized: false, - setEnabled: async (enabled: boolean) => { - if (!get().initialized) throw "Workflow not initialized yet"; + init: async (id: string) => { + const data = await getWorkflow(id); + set({ + workflow: data, + initialized: true, + }); - const resp = await saveWorkflow({ - id: get().workflow.id!, - enabled: enabled, - }); + unsubscriber ??= await subscribeWorkflow(id, (cb) => { + if (cb.record.id !== get().workflow.id) return; - set((state: WorkflowState) => { - return { - workflow: produce(state.workflow, (draft) => { - draft.enabled = resp.enabled; - }), - }; - }); - }, + set({ + workflow: cb.record, + }); + }); + }, - publish: async () => { - if (!get().initialized) throw "Workflow not initialized yet"; + destroy: () => { + unsubscriber?.(); + unsubscriber = undefined; - const root = get().workflow.draft!; - const startConfig = root.config as WorkflowNodeConfigForStart; - const resp = await saveWorkflow({ - id: get().workflow.id!, - trigger: startConfig.trigger, - triggerCron: startConfig.triggerCron, - content: root, - hasDraft: false, - }); + set({ + workflow: {} as WorkflowModel, + initialized: false, + }); + }, - set((state: WorkflowState) => { - return { - workflow: produce(state.workflow, (draft) => { - draft.trigger = resp.trigger; - draft.triggerCron = resp.triggerCron; - draft.content = resp.content; - draft.draft = resp.draft; - draft.hasDraft = resp.hasDraft; - }), - }; - }); - }, + setBaseInfo: async (name, description) => { + ensureInitialized(); - rollback: async () => { - if (!get().initialized) throw "Workflow not initialized yet"; + const resp = await saveWorkflow({ + id: get().workflow.id!, + name: name || "", + description: description || "", + }); - const root = get().workflow.content!; - const startConfig = root.config as WorkflowNodeConfigForStart; - const resp = await saveWorkflow({ - id: get().workflow.id!, - draft: root, - hasDraft: false, - trigger: startConfig.trigger, - triggerCron: startConfig.triggerCron, - }); + set((state: WorkflowState) => { + return { + workflow: produce(state.workflow, (draft) => { + draft.name = resp.name; + draft.description = resp.description; + }), + }; + }); + }, - set((state: WorkflowState) => { - return { - workflow: produce(state.workflow, (draft) => { - draft.trigger = resp.trigger; - draft.triggerCron = resp.triggerCron; - draft.content = resp.content; - draft.draft = resp.draft; - draft.hasDraft = resp.hasDraft; - }), - }; - }); - }, + setEnabled: async (enabled) => { + ensureInitialized(); - addNode: async (node: WorkflowNode, previousNodeId: string) => { - if (!get().initialized) throw "Workflow not initialized yet"; + const resp = await saveWorkflow({ + id: get().workflow.id!, + enabled: enabled, + }); - const root = addNode(get().workflow.draft!, node, previousNodeId); - const resp = await saveWorkflow({ - id: get().workflow.id!, - draft: root, - hasDraft: true, - }); + set((state: WorkflowState) => { + return { + workflow: produce(state.workflow, (draft) => { + draft.enabled = resp.enabled; + }), + }; + }); + }, - set((state: WorkflowState) => { - return { - workflow: produce(state.workflow, (draft) => { - draft.draft = resp.draft; - draft.hasDraft = resp.hasDraft; - }), - }; - }); - }, + publish: async () => { + ensureInitialized(); - duplicateNode: async (node: WorkflowNode) => { - if (!get().initialized) throw "Workflow not initialized yet"; + const root = get().workflow.draft!; + const startConfig = root.config as WorkflowNodeConfigForStart; + const resp = await saveWorkflow({ + id: get().workflow.id!, + trigger: startConfig.trigger, + triggerCron: startConfig.triggerCron, + content: root, + hasDraft: false, + }); - const root = duplicateNode(get().workflow.draft!, node); - const resp = await saveWorkflow({ - id: get().workflow.id!, - draft: root, - hasDraft: true, - }); + set((state: WorkflowState) => { + return { + workflow: produce(state.workflow, (draft) => { + draft.trigger = resp.trigger; + draft.triggerCron = resp.triggerCron; + draft.content = resp.content; + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), + }; + }); + }, - set((state: WorkflowState) => { - return { - workflow: produce(state.workflow, (draft) => { - draft.draft = resp.draft; - draft.hasDraft = resp.hasDraft; - }), - }; - }); - }, + rollback: async () => { + ensureInitialized(); - updateNode: async (node: WorkflowNode) => { - if (!get().initialized) throw "Workflow not initialized yet"; + const root = get().workflow.content!; + const startConfig = root.config as WorkflowNodeConfigForStart; + const resp = await saveWorkflow({ + id: get().workflow.id!, + draft: root, + hasDraft: false, + trigger: startConfig.trigger, + triggerCron: startConfig.triggerCron, + }); - const root = updateNode(get().workflow.draft!, node); - const resp = await saveWorkflow({ - id: get().workflow.id!, - draft: root, - hasDraft: true, - }); + set((state: WorkflowState) => { + return { + workflow: produce(state.workflow, (draft) => { + draft.trigger = resp.trigger; + draft.triggerCron = resp.triggerCron; + draft.content = resp.content; + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), + }; + }); + }, - set((state: WorkflowState) => { - return { - workflow: produce(state.workflow, (draft) => { - draft.draft = resp.draft; - draft.hasDraft = resp.hasDraft; - }), - }; - }); - }, + addNode: async (node, previousNodeId) => { + ensureInitialized(); - removeNode: async (node: WorkflowNode) => { - if (!get().initialized) throw "Workflow not initialized yet"; + const root = addNode(get().workflow.draft!, node, previousNodeId); + const resp = await saveWorkflow({ + id: get().workflow.id!, + draft: root, + hasDraft: true, + }); - const root = removeNode(get().workflow.draft!, node.id); - const resp = await saveWorkflow({ - id: get().workflow.id!, - draft: root, - hasDraft: true, - }); + set((state: WorkflowState) => { + return { + workflow: produce(state.workflow, (draft) => { + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), + }; + }); + }, - set((state: WorkflowState) => { - return { - workflow: produce(state.workflow, (draft) => { - draft.draft = resp.draft; - draft.hasDraft = resp.hasDraft; - }), - }; - }); - }, + duplicateNode: async (node) => { + ensureInitialized(); - addBranch: async (branchId: string) => { - if (!get().initialized) throw "Workflow not initialized yet"; + const root = duplicateNode(get().workflow.draft!, node); + const resp = await saveWorkflow({ + id: get().workflow.id!, + draft: root, + hasDraft: true, + }); - const root = addBranch(get().workflow.draft!, branchId); - const resp = await saveWorkflow({ - id: get().workflow.id!, - draft: root, - hasDraft: true, - }); + set((state: WorkflowState) => { + return { + workflow: produce(state.workflow, (draft) => { + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), + }; + }); + }, - set((state: WorkflowState) => { - return { - workflow: produce(state.workflow, (draft) => { - draft.draft = resp.draft; - draft.hasDraft = resp.hasDraft; - }), - }; - }); - }, + updateNode: async (node) => { + ensureInitialized(); - duplicateBranch: async (branchId: string, index: number) => { - if (!get().initialized) throw "Workflow not initialized yet"; + const root = updateNode(get().workflow.draft!, node); + const resp = await saveWorkflow({ + id: get().workflow.id!, + draft: root, + hasDraft: true, + }); - const root = duplicateBranch(get().workflow.draft!, branchId, index); - const resp = await saveWorkflow({ - id: get().workflow.id!, - draft: root, - hasDraft: true, - }); + set((state: WorkflowState) => { + return { + workflow: produce(state.workflow, (draft) => { + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), + }; + }); + }, - set((state: WorkflowState) => { - return { - workflow: produce(state.workflow, (draft) => { - draft.draft = resp.draft; - draft.hasDraft = resp.hasDraft; - }), - }; - }); - }, + removeNode: async (node) => { + ensureInitialized(); - removeBranch: async (branchId: string, index: number) => { - if (!get().initialized) throw "Workflow not initialized yet"; + const root = removeNode(get().workflow.draft!, node.id); + const resp = await saveWorkflow({ + id: get().workflow.id!, + draft: root, + hasDraft: true, + }); - const root = removeBranch(get().workflow.draft!, branchId, index); - const resp = await saveWorkflow({ - id: get().workflow.id!, - draft: root, - hasDraft: true, - }); + set((state: WorkflowState) => { + return { + workflow: produce(state.workflow, (draft) => { + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), + }; + }); + }, - set((state: WorkflowState) => { - return { - workflow: produce(state.workflow, (draft) => { - draft.draft = resp.draft; - draft.hasDraft = resp.hasDraft; - }), - }; - }); - }, + addBranch: async (branchId) => { + ensureInitialized(); - getWorkflowOuptutBeforeId: (nodeId: string, typeFilter?: string | string[]) => { - return getOutputBeforeNodeId(get().workflow.draft as WorkflowNode, nodeId, typeFilter); - }, -})); + const root = addBranch(get().workflow.draft!, branchId); + const resp = await saveWorkflow({ + id: get().workflow.id!, + draft: root, + hasDraft: true, + }); + + set((state: WorkflowState) => { + return { + workflow: produce(state.workflow, (draft) => { + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), + }; + }); + }, + + duplicateBranch: async (branchId, index) => { + ensureInitialized(); + + const root = duplicateBranch(get().workflow.draft!, branchId, index); + const resp = await saveWorkflow({ + id: get().workflow.id!, + draft: root, + hasDraft: true, + }); + + set((state: WorkflowState) => { + return { + workflow: produce(state.workflow, (draft) => { + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), + }; + }); + }, + + removeBranch: async (branchId, index) => { + ensureInitialized(); + + const root = removeBranch(get().workflow.draft!, branchId, index); + const resp = await saveWorkflow({ + id: get().workflow.id!, + draft: root, + hasDraft: true, + }); + + set((state: WorkflowState) => { + return { + workflow: produce(state.workflow, (draft) => { + draft.draft = resp.draft; + draft.hasDraft = resp.hasDraft; + }), + }; + }); + }, + + getWorkflowOuptutBeforeId: (nodeId, typeFilter) => { + return getOutputBeforeNodeId(get().workflow.draft!, nodeId, typeFilter); + }, + }; +}); diff --git a/ui/src/stores/workflow/types.ts b/ui/src/stores/workflow/types.ts new file mode 100644 index 000000000..da85e6f1b --- /dev/null +++ b/ui/src/stores/workflow/types.ts @@ -0,0 +1,28 @@ +import { type WorkflowModel, type WorkflowNode } from "@/domain/workflow"; + +export interface WorkflowState { + workflow: WorkflowModel; + initialized: boolean; +} + +export interface WorkflowActions { + init(id: string): void; + destroy(): void; + + setBaseInfo: (name: string, description: string) => void; + setEnabled(enabled: boolean): void; + publish(): void; + rollback(): void; + + addNode: (node: WorkflowNode, previousNodeId: string) => void; + duplicateNode: (node: WorkflowNode) => void; + updateNode: (node: WorkflowNode) => void; + removeNode: (node: WorkflowNode) => void; + addBranch: (branchId: string) => void; + duplicateBranch: (branchId: string, index: number) => void; + removeBranch: (branchId: string, index: number) => void; + + getWorkflowOuptutBeforeId: (nodeId: string, typeFilter?: string | string[]) => WorkflowNode[]; +} + +export interface WorkflowStore extends WorkflowState, WorkflowActions {} From 344e9c719e48fca3dc7badab35c98bd63799c7ab Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Wed, 30 Jul 2025 13:17:17 +0800 Subject: [PATCH 10/25] refactor(ui): extract css --- ui/src/global.css | 6 ++++++ ui/src/i18n/locales/zh/nls.certificate.json | 2 +- ui/src/i18n/locales/zh/nls.dashboard.json | 2 +- ui/src/i18n/locales/zh/nls.workflow.runs.json | 2 +- ui/src/pages/ConsoleLayout.tsx | 2 +- ui/src/pages/accesses/AccessList.tsx | 4 +++- ui/src/pages/accesses/AccessNew.tsx | 4 ++-- ui/src/pages/certificates/CertificateList.tsx | 4 +++- ui/src/pages/dashboard/Dashboard.tsx | 4 +++- ui/src/pages/settings/Settings.tsx | 4 +++- ui/src/pages/settings/SettingsAccount.tsx | 12 +++++++++--- ui/src/pages/workflows/WorkflowDetail.tsx | 4 ++-- ui/src/pages/workflows/WorkflowDetailRuns.tsx | 2 +- ui/src/pages/workflows/WorkflowList.tsx | 4 +++- ui/src/pages/workflows/WorkflowNew.tsx | 4 ++-- 15 files changed, 41 insertions(+), 19 deletions(-) diff --git a/ui/src/global.css b/ui/src/global.css index 0cf2aee9c..54dc2549b 100644 --- a/ui/src/global.css +++ b/ui/src/global.css @@ -59,3 +59,9 @@ line-height: 1; } } + +@layer components { + .container { + @apply mx-auto max-w-320; + } +} diff --git a/ui/src/i18n/locales/zh/nls.certificate.json b/ui/src/i18n/locales/zh/nls.certificate.json index 6ea7260a3..470d5756d 100644 --- a/ui/src/i18n/locales/zh/nls.certificate.json +++ b/ui/src/i18n/locales/zh/nls.certificate.json @@ -3,7 +3,7 @@ "certificate.page.subtitle": "SSL 证书含有网站的公钥和网站标识以及其他相关信息。它们来自于工作流的执行输出。", "certificate.nodata.title": "暂无证书", - "certificate.nodata.description": "当前未找到任何认证。请先运行一个工作流以生成证书。", + "certificate.nodata.description": "当前未找到任何认证。请先执行一个工作流以生成证书。", "certificate.nodata.button": "前往工作流", "certificate.search.placeholder": "按证书名称或序列号搜索……", diff --git a/ui/src/i18n/locales/zh/nls.dashboard.json b/ui/src/i18n/locales/zh/nls.dashboard.json index 53ef62e72..f9912c35a 100644 --- a/ui/src/i18n/locales/zh/nls.dashboard.json +++ b/ui/src/i18n/locales/zh/nls.dashboard.json @@ -8,7 +8,7 @@ "dashboard.statistics.enabled_workflows": "已启用工作流", "dashboard.latest_workflow_runs": "最近执行的工作流", - "dashboard.latest_workflow_runs.nodata.description": "当前未找到任何工作流执行记录。请先运行一个工作流。", + "dashboard.latest_workflow_runs.nodata.description": "当前未找到任何工作流执行记录。请先执行一个工作流。", "dashboard.latest_workflow_runs.nodata.button": "前往工作流", "dashboard.quick_actions": "快捷操作", diff --git a/ui/src/i18n/locales/zh/nls.workflow.runs.json b/ui/src/i18n/locales/zh/nls.workflow.runs.json index bc02aa1c1..5833dbfc1 100644 --- a/ui/src/i18n/locales/zh/nls.workflow.runs.json +++ b/ui/src/i18n/locales/zh/nls.workflow.runs.json @@ -10,7 +10,7 @@ "workflow_run.deletion.alert": "执行记录中包含工作流各节点的执行结果,删除后可能导致因找不到前次执行结果而触发重新申请或部署证书。如无必要请勿提前删除,建议保留至少 180 天。", "workflow_run.cancellation.alert": "如遇进程意外中止、服务器超时等原因,你可以手动取消长时间挂起的执行,避免阻塞后续执行。", - "workflow_run.nodata.description": "当前未找到任何执行记录。请先运行此工作流。", + "workflow_run.nodata.description": "当前未找到任何执行记录。请先执行此工作流。", "workflow_run.props.id": "ID", "workflow_run.props.status": "状态", diff --git a/ui/src/pages/ConsoleLayout.tsx b/ui/src/pages/ConsoleLayout.tsx index a8e628d5c..175bc4b5e 100644 --- a/ui/src/pages/ConsoleLayout.tsx +++ b/ui/src/pages/ConsoleLayout.tsx @@ -69,7 +69,7 @@ const ConsoleLayout = () => {
-
+
{ return (
-
+

{t("access.page.title")}

{t("access.page.subtitle")}

+
+
diff --git a/ui/src/pages/accesses/AccessNew.tsx b/ui/src/pages/accesses/AccessNew.tsx index dc4686698..85065344b 100644 --- a/ui/src/pages/accesses/AccessNew.tsx +++ b/ui/src/pages/accesses/AccessNew.tsx @@ -85,12 +85,12 @@ const AccessNew = () => { return (
-
+

{t("access.new.title")}

{t("access.new.subtitle")}

-
+
{ return (
-
+

{t("certificate.page.title")}

{t("certificate.page.subtitle")}

+
+
diff --git a/ui/src/pages/dashboard/Dashboard.tsx b/ui/src/pages/dashboard/Dashboard.tsx index 3e36b551a..5462528da 100644 --- a/ui/src/pages/dashboard/Dashboard.tsx +++ b/ui/src/pages/dashboard/Dashboard.tsx @@ -39,9 +39,11 @@ const Dashboard = () => { return (
-
+

{t("dashboard.page.title")}

+
+
diff --git a/ui/src/pages/settings/Settings.tsx b/ui/src/pages/settings/Settings.tsx index 9386e19f4..41a0dae5a 100644 --- a/ui/src/pages/settings/Settings.tsx +++ b/ui/src/pages/settings/Settings.tsx @@ -34,9 +34,11 @@ const Settings = () => { return (
-
+

{t("settings.page.title")}

+
+
v === formInst.getFieldValue("newPassword"), { error: t("settings.account.password.form.email.password.errmsg.not_matched"), }), diff --git a/ui/src/pages/workflows/WorkflowDetail.tsx b/ui/src/pages/workflows/WorkflowDetail.tsx index 921ec8a0f..b4be3d1ce 100644 --- a/ui/src/pages/workflows/WorkflowDetail.tsx +++ b/ui/src/pages/workflows/WorkflowDetail.tsx @@ -93,7 +93,7 @@ const WorkflowDetail = () => { return (
-
+
@@ -135,7 +135,7 @@ const WorkflowDetail = () => { +
} diff --git a/ui/src/pages/workflows/WorkflowDetailRuns.tsx b/ui/src/pages/workflows/WorkflowDetailRuns.tsx index 9f597bab2..f5950da80 100644 --- a/ui/src/pages/workflows/WorkflowDetailRuns.tsx +++ b/ui/src/pages/workflows/WorkflowDetailRuns.tsx @@ -274,7 +274,7 @@ const WorkflowDetailRuns = () => { }; return ( -
+
} showIcon type="info" /> } showIcon type="info" /> diff --git a/ui/src/pages/workflows/WorkflowList.tsx b/ui/src/pages/workflows/WorkflowList.tsx index 88641169a..0c7362d90 100644 --- a/ui/src/pages/workflows/WorkflowList.tsx +++ b/ui/src/pages/workflows/WorkflowList.tsx @@ -415,10 +415,12 @@ const WorkflowList = () => { return (
-
+

{t("workflow.page.title")}

{t("workflow.page.subtitle")}

+
+
diff --git a/ui/src/pages/workflows/WorkflowNew.tsx b/ui/src/pages/workflows/WorkflowNew.tsx index 6524fb1f3..a4115b1a1 100644 --- a/ui/src/pages/workflows/WorkflowNew.tsx +++ b/ui/src/pages/workflows/WorkflowNew.tsx @@ -72,12 +72,12 @@ const WorkflowNew = () => { return (
-
+

{t("workflow.new.title")}

{t("workflow.new.subtitle")}

-
+
{t("workflow.new.templates.title")}
From cdf39a0f39c46dff1d47878c7faddf8835799c4a Mon Sep 17 00:00:00 2001 From: Fu Diwei Date: Mon, 4 Aug 2025 16:59:23 +0800 Subject: [PATCH 11/25] feat(ui): new workflow designer --- ui/package-lock.json | 547 +++++++++--------- ui/package.json | 20 +- ui/src/components/MultipleSplitValueInput.tsx | 2 +- .../workflow/WorkflowElementsContainer.tsx | 20 +- .../components/workflow/designer/Editor.tsx | 119 ++++ .../workflow/designer/NodeRender.tsx | 27 + .../components/workflow/designer/Toolbar.tsx | 78 +++ .../workflow/designer/components/Adder.tsx | 65 +++ .../designer/components/BranchAdder.tsx | 62 ++ .../workflow/designer/components/Collapse.tsx | 9 + .../components/DragHighlightAdder.tsx | 14 + .../workflow/designer/components/DragNode.tsx | 26 + .../designer/components/DraggingAdder.tsx | 31 + .../workflow/designer/components/Null.tsx | 11 + .../designer/components/TryCatchCollapse.tsx | 69 +++ .../workflow/designer/components/index.ts | 30 + .../components/workflow/designer/flowgram.css | 22 + .../workflow/designer/nodes/BusinessNode.tsx | 206 +++++++ .../workflow/designer/nodes/ConditionNode.tsx | 108 ++++ .../workflow/designer/nodes/EndNode.tsx | 75 +++ .../workflow/designer/nodes/StartNode.tsx | 67 +++ .../workflow/designer/nodes/TryCatchNode.tsx | 105 ++++ .../workflow/designer/nodes/_example.ts | 63 ++ .../workflow/designer/nodes/_shared.tsx | 161 ++++++ .../workflow/designer/nodes/index.ts | 23 + .../workflow/designer/nodes/typings.ts | 55 ++ .../components/workflow/node/_SharedNode.tsx | 6 +- ui/src/i18n/locales/en/nls.workflow.json | 18 +- .../i18n/locales/en/nls.workflow.nodes.json | 54 +- ui/src/i18n/locales/zh/nls.certificate.json | 4 +- ui/src/i18n/locales/zh/nls.dashboard.json | 4 +- ui/src/i18n/locales/zh/nls.settings.json | 4 +- ui/src/i18n/locales/zh/nls.workflow.json | 30 +- .../i18n/locales/zh/nls.workflow.nodes.json | 58 +- ui/src/i18n/locales/zh/nls.workflow.runs.json | 28 +- ui/src/pages/ConsoleLayout.tsx | 6 +- ui/src/pages/accesses/AccessList.tsx | 6 +- ui/src/pages/certificates/CertificateList.tsx | 6 +- ui/src/pages/workflows/WorkflowDetail.tsx | 79 ++- .../pages/workflows/WorkflowDetailDesign.tsx | 272 +++++++-- .../workflows/WorkflowDetailDesignLegacy.tsx | 132 +++++ ui/src/pages/workflows/WorkflowDetailRuns.tsx | 102 ++-- ui/src/pages/workflows/WorkflowList.tsx | 6 +- ui/src/routers/index.tsx | 5 + 44 files changed, 2351 insertions(+), 484 deletions(-) create mode 100644 ui/src/components/workflow/designer/Editor.tsx create mode 100644 ui/src/components/workflow/designer/NodeRender.tsx create mode 100644 ui/src/components/workflow/designer/Toolbar.tsx create mode 100644 ui/src/components/workflow/designer/components/Adder.tsx create mode 100644 ui/src/components/workflow/designer/components/BranchAdder.tsx create mode 100644 ui/src/components/workflow/designer/components/Collapse.tsx create mode 100644 ui/src/components/workflow/designer/components/DragHighlightAdder.tsx create mode 100644 ui/src/components/workflow/designer/components/DragNode.tsx create mode 100644 ui/src/components/workflow/designer/components/DraggingAdder.tsx create mode 100644 ui/src/components/workflow/designer/components/Null.tsx create mode 100644 ui/src/components/workflow/designer/components/TryCatchCollapse.tsx create mode 100644 ui/src/components/workflow/designer/components/index.ts create mode 100644 ui/src/components/workflow/designer/flowgram.css create mode 100644 ui/src/components/workflow/designer/nodes/BusinessNode.tsx create mode 100644 ui/src/components/workflow/designer/nodes/ConditionNode.tsx create mode 100644 ui/src/components/workflow/designer/nodes/EndNode.tsx create mode 100644 ui/src/components/workflow/designer/nodes/StartNode.tsx create mode 100644 ui/src/components/workflow/designer/nodes/TryCatchNode.tsx create mode 100644 ui/src/components/workflow/designer/nodes/_example.ts create mode 100644 ui/src/components/workflow/designer/nodes/_shared.tsx create mode 100644 ui/src/components/workflow/designer/nodes/index.ts create mode 100644 ui/src/components/workflow/designer/nodes/typings.ts create mode 100644 ui/src/pages/workflows/WorkflowDetailDesignLegacy.tsx diff --git a/ui/package-lock.json b/ui/package-lock.json index 2f5392d09..46fccd086 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,25 +1,25 @@ { - "name": "ui", + "name": "@certimate/webui", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "ui", - "version": "0.0.0", + "name": "@certimate/webui", "dependencies": { "@ant-design/pro-components": "^2.8.10", "@codemirror/lang-json": "^6.0.2", "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language": "^6.11.2", "@codemirror/legacy-modes": "^6.5.1", - "@flowgram.ai/fixed-layout-editor": "^0.2.28", + "@flowgram.ai/document": "^0.3.2", + "@flowgram.ai/fixed-layout-editor": "^0.3.2", "@tabler/icons-react": "^3.34.1", - "@uiw/codemirror-extensions-basic-setup": "^4.24.1", - "@uiw/codemirror-theme-vscode": "^4.24.1", - "@uiw/react-codemirror": "^4.24.1", + "@uiw/codemirror-extensions-basic-setup": "^4.24.2", + "@uiw/codemirror-theme-vscode": "^4.24.2", + "@uiw/react-codemirror": "^4.24.2", "ahooks": "^3.9.0", - "antd": "^5.26.6", + "antd": "^5.26.7", "antd-zod": "^7.0.0", "clsx": "^2.1.1", "cron-parser": "^5.3.0", @@ -36,8 +36,8 @@ "react-i18next": "^15.6.1", "react-router-dom": "^7.7.1", "tailwind-merge": "^3.3.1", - "zod": "^4.0.10", - "zustand": "^5.0.6" + "zod": "^4.0.14", + "zustand": "^5.0.7" }, "devDependencies": { "@eslint/js": "^9.32.0", @@ -54,7 +54,7 @@ "eslint": "^9.32.0", "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.4", - "eslint-plugin-better-tailwindcss": "^3.7.2", + "eslint-plugin-better-tailwindcss": "^3.7.3", "eslint-plugin-import": "^2.32.0", "eslint-plugin-prettier": "^5.5.3", "eslint-plugin-react-hooks": "^5.2.0", @@ -2784,12 +2784,12 @@ } }, "node_modules/@flowgram.ai/background-plugin": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/background-plugin/-/background-plugin-0.2.28.tgz", - "integrity": "sha512-GEH0e1TioFQuKTZli7UO0yz171xs2T2XAnoWdxGhuWTcZ3PVyJSwJSF6BPDxfWGgN3kL9JeghbCPgElhwEOKGg==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/background-plugin/-/background-plugin-0.3.2.tgz", + "integrity": "sha512-3BeVeZ3bvhqIfsSVuUpxMBdOON1eY9E+7SlAvs1iWbEgzn8LVg7i6lXdl0BIVlUEIthl7aLH7uxrbb/MAj9Fxg==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "reflect-metadata": "~0.2.2" }, @@ -2799,11 +2799,11 @@ } }, "node_modules/@flowgram.ai/command": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/command/-/command-0.2.28.tgz", - "integrity": "sha512-/OMswJnXKm6aHIjwOQfWzCeYCr19x6gA8YNY8z9BpWJa3mPpPMUKjigldvOZVq/LZ3UPLj4EtbW7u4yyfsNCgA==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/command/-/command-0.3.2.tgz", + "integrity": "sha512-P6tuALZrSzjGBAB3BttJWumtmN0NcBJ88dHGsWStMMDW81LYCFKCsybB2wU0YoGVV7viRjCe/szSoQAbp8B2NQ==", "dependencies": { - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "reflect-metadata": "~0.2.2" }, @@ -2813,12 +2813,12 @@ } }, "node_modules/@flowgram.ai/core": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/core/-/core-0.2.28.tgz", - "integrity": "sha512-iVHIGHZTjoI0OJCf2m2ch70jkopWu6P+G1wy4cYMWqWI89lz2grtAEd7Qg8vRHQameLAdoQnAHuSkAXpIIKcaA==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/core/-/core-0.3.2.tgz", + "integrity": "sha512-pb+YP2DKIrTP2woQEbt7cKWnqwXObsMtGhS0DuRgbCIN1+6fLXFjvN2M2QhMRsOPiqY60dWnZWLKtagwPVUDqA==", "dependencies": { - "@flowgram.ai/command": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/command": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "@phosphor/messaging": "^1.3.0", "@tweenjs/tween.js": "^18", "clsx": "^1.1.1", @@ -2858,12 +2858,12 @@ } }, "node_modules/@flowgram.ai/document": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/document/-/document-0.2.28.tgz", - "integrity": "sha512-HzEG5EZNdtooAaGjCCd4k9cjxoUaMs0j2dowVA2/z8CO42ylqLnk+GJqr8jE+gPxS4muwjxdzEa/OXcgsrlqhw==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/document/-/document-0.3.2.tgz", + "integrity": "sha512-N+VAQzZcNt8PUfB/32cGzMFtUUiKGTzCFMFMElB9ffSWG2stHgueRO3kBWCD8yweArfUofhCTk3eERJ345dj3A==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "lodash": "^4.17.21", "nanoid": "^4.0.2", @@ -2888,29 +2888,29 @@ } }, "node_modules/@flowgram.ai/editor": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/editor/-/editor-0.2.28.tgz", - "integrity": "sha512-QMSuOfFXZHHtnjuuX/pDqoGn/iCKjD6I05wcKUn0LqqbFgyYKp49Ju77IS9rGBs6R1Cj0jqFPl3OLW50sFqLEg==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/editor/-/editor-0.3.2.tgz", + "integrity": "sha512-Vdq53nKUoHQKWtVOmMRcI3DgS8U8pW2jFQRklYfAVhmLxyw4636ZplF8F3/AiAbt5tJ7P9OOs5xI456U1U2dqQ==", "dependencies": { - "@flowgram.ai/background-plugin": "0.2.28", - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/form": "0.2.28", - "@flowgram.ai/form-core": "0.2.28", - "@flowgram.ai/group-plugin": "0.2.28", - "@flowgram.ai/history": "0.2.28", - "@flowgram.ai/history-node-plugin": "0.2.28", - "@flowgram.ai/i18n-plugin": "0.2.28", - "@flowgram.ai/materials-plugin": "0.2.28", - "@flowgram.ai/node": "0.2.28", - "@flowgram.ai/node-core-plugin": "0.2.28", - "@flowgram.ai/node-variable-plugin": "0.2.28", - "@flowgram.ai/playground-react": "0.2.28", - "@flowgram.ai/redux-devtool-plugin": "0.2.28", - "@flowgram.ai/renderer": "0.2.28", - "@flowgram.ai/shortcuts-plugin": "0.2.28", - "@flowgram.ai/utils": "0.2.28", - "@flowgram.ai/variable-plugin": "0.2.28", + "@flowgram.ai/background-plugin": "0.3.2", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/form": "0.3.2", + "@flowgram.ai/form-core": "0.3.2", + "@flowgram.ai/group-plugin": "0.3.2", + "@flowgram.ai/history": "0.3.2", + "@flowgram.ai/history-node-plugin": "0.3.2", + "@flowgram.ai/i18n-plugin": "0.3.2", + "@flowgram.ai/materials-plugin": "0.3.2", + "@flowgram.ai/node": "0.3.2", + "@flowgram.ai/node-core-plugin": "0.3.2", + "@flowgram.ai/node-variable-plugin": "0.3.2", + "@flowgram.ai/playground-react": "0.3.2", + "@flowgram.ai/redux-devtool-plugin": "0.3.2", + "@flowgram.ai/renderer": "0.3.2", + "@flowgram.ai/shortcuts-plugin": "0.3.2", + "@flowgram.ai/utils": "0.3.2", + "@flowgram.ai/variable-plugin": "0.3.2", "inversify": "^6.0.1", "reflect-metadata": "~0.2.2" }, @@ -2920,13 +2920,13 @@ } }, "node_modules/@flowgram.ai/fixed-drag-plugin": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-drag-plugin/-/fixed-drag-plugin-0.2.28.tgz", - "integrity": "sha512-dNrFBd6G1uFgkhf0LpAC9/7vV8kIQCBuBAmbeUi6EQZkmWsp9rVd4SAdKGqhNLrs6klt8n5oMltv6WJsMCRS6Q==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-drag-plugin/-/fixed-drag-plugin-0.3.2.tgz", + "integrity": "sha512-XK09rklrkU1EzhmqFBsm/zZGcnnb4LXhNMkol2fDviiq2Aq0UumXL2jRjRaWcFQJVjBawlOd3YciTamSY6xNgg==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/renderer": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/renderer": "0.3.2", "inversify": "^6.0.1", "reflect-metadata": "~0.2.2" }, @@ -2936,29 +2936,29 @@ } }, "node_modules/@flowgram.ai/fixed-history-plugin": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-history-plugin/-/fixed-history-plugin-0.2.28.tgz", - "integrity": "sha512-s9ckjS6NS3ds+FfmvTIvO/02N8rSh+EcSDyKwLaGcrYk6X6Fs1T5h/PyBgGTryJ7EzseqlLTFVBqYXzgbymDtg==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-history-plugin/-/fixed-history-plugin-0.3.2.tgz", + "integrity": "sha512-27+z5s+9LeRBG+sk5e6mPuqu2dw4nBFV49ouNe/l11r+A+w5j6haZdX2kLR0wrWdJWDu7Dn0y9MQyxwPSyVEmg==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/form-core": "0.2.28", - "@flowgram.ai/history": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/form-core": "0.3.2", + "@flowgram.ai/history": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "lodash": "^4.17.21", "reflect-metadata": "~0.2.2" } }, "node_modules/@flowgram.ai/fixed-layout-core": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-layout-core/-/fixed-layout-core-0.2.28.tgz", - "integrity": "sha512-pIa7vaksAmBZm3aaDMkpxB8RsS+fHGntDjGBWuP5tKJkjMKSXyPDzA7vGOwcVLyX3OrYiMD8ypuCDOGPruAr2A==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-layout-core/-/fixed-layout-core-0.3.2.tgz", + "integrity": "sha512-e+J/oxFs4HD8FOdMHh79bXLEp+7gzoCk+42fMTAJ4Or4HEIkep8gRTpFxB0fINnWxpw3CpZZFnhMLIuyR5R46g==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/renderer": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/renderer": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "lodash": "^4.17.21", "reflect-metadata": "~0.2.2" @@ -2969,19 +2969,19 @@ } }, "node_modules/@flowgram.ai/fixed-layout-editor": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-layout-editor/-/fixed-layout-editor-0.2.28.tgz", - "integrity": "sha512-PpLQXWjC2L7eqVzNdt7Z4DxEh6GcIJyTrOQE0FjUB3XuS2IW7ciHogs3gEfAVpoFbZGj8tzIcs6IMUu1GxjeqQ==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/fixed-layout-editor/-/fixed-layout-editor-0.3.2.tgz", + "integrity": "sha512-wZxyYOt4f9CFX+iUiq5J4tyn4K9YmfcprYs26i7CB2D542vvpREVWXzl/FdTiylcW6qTTGcFAsQkdVdmd33xEg==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/editor": "0.2.28", - "@flowgram.ai/fixed-drag-plugin": "0.2.28", - "@flowgram.ai/fixed-history-plugin": "0.2.28", - "@flowgram.ai/fixed-layout-core": "0.2.28", - "@flowgram.ai/history": "0.2.28", - "@flowgram.ai/reactive": "0.2.28", - "@flowgram.ai/select-box-plugin": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/editor": "0.3.2", + "@flowgram.ai/fixed-drag-plugin": "0.3.2", + "@flowgram.ai/fixed-history-plugin": "0.3.2", + "@flowgram.ai/fixed-layout-core": "0.3.2", + "@flowgram.ai/history": "0.3.2", + "@flowgram.ai/reactive": "0.3.2", + "@flowgram.ai/select-box-plugin": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "reflect-metadata": "~0.2.2" }, @@ -2991,12 +2991,12 @@ } }, "node_modules/@flowgram.ai/form": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/form/-/form-0.2.28.tgz", - "integrity": "sha512-OgMDayBmHDUEjb6xWZeDmyn8BKZZazEsas3WJKlm2r6zaobygxE5K5NzJ/oy3OyLTgMtpYq3JOWZPgGu16ZRHg==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/form/-/form-0.3.2.tgz", + "integrity": "sha512-OHp6IhOQUcW3Hw/BWrxi4Kn/UTB96BkQgjl2mQ0o+HyqTwzGa9wNtLkNWqIK/CogD/l31kRp39L6rFH71S2AgQ==", "dependencies": { - "@flowgram.ai/reactive": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/reactive": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "fast-equals": "^2.0.0", "lodash": "^4.17.21", "nanoid": "^4.0.2" @@ -3007,13 +3007,13 @@ } }, "node_modules/@flowgram.ai/form-core": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/form-core/-/form-core-0.2.28.tgz", - "integrity": "sha512-JpapezHkKEl4hgNnhBo/5mBUcgAv7aUQpp3ypZ0g/0L9qLd9vB3PjBw7mL+K4t+954ckJMLW0AHJyqeWKXpO4g==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/form-core/-/form-core-0.3.2.tgz", + "integrity": "sha512-RjipVRN/pKqDF27vL4GhtRpRtlcDlZZgv2chEehd1tnedJRmgH30REN3ET9ya+cF2Lte9z9ALdAD6KMfCqdySw==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "lodash": "^4.17.21", "reflect-metadata": "~0.2.2" @@ -3041,16 +3041,16 @@ } }, "node_modules/@flowgram.ai/free-layout-core": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/free-layout-core/-/free-layout-core-0.2.28.tgz", - "integrity": "sha512-oGLr4dTggl1e7h/usm0Hs4uYjiAKOU8ZY6W9xpEgvXJkRngiGDgh1+S3Sh7WfxvDQeHkaQhGRgcKNqECGqL1LQ==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/free-layout-core/-/free-layout-core-0.3.2.tgz", + "integrity": "sha512-xvoneqfmNqsfzLotqdikBOfMDXC173G0Gqs5XxDZv3fpCuUMyy3eI0KrnoJZMIp6BD5buDmBrm6vk9D8vE1WnQ==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/form-core": "0.2.28", - "@flowgram.ai/node": "0.2.28", - "@flowgram.ai/reactive": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/form-core": "0.3.2", + "@flowgram.ai/node": "0.3.2", + "@flowgram.ai/reactive": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "lodash-es": "^4.17.21", "nanoid": "^4.0.2", @@ -3079,14 +3079,14 @@ } }, "node_modules/@flowgram.ai/group-plugin": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/group-plugin/-/group-plugin-0.2.28.tgz", - "integrity": "sha512-Gk4A5oCOxF45Htf68OIR2yZyQR2416HMDsDISWCuAZiV/LafGDSNwRWiUv/6tpyUpwT1uHSipkTFSRwAWApO8g==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/group-plugin/-/group-plugin-0.3.2.tgz", + "integrity": "sha512-9fn4Sdu6uDqUPz00VBJu8F4CEdkQECg2ZIDETZLU8iG49eeX1qCGPOai7MRMGY83LcEDIR0Sk5w0Z7i4wM5mXQ==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/renderer": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/renderer": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "reflect-metadata": "~0.2.2" }, @@ -3096,12 +3096,12 @@ } }, "node_modules/@flowgram.ai/history": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/history/-/history-0.2.28.tgz", - "integrity": "sha512-Em5yq2ZmP5lRglrdIfHe54zt3PSMM3BUj95jge5rC1uxNes7vhu5jaKhVIK6aehu9m4pOY8ZJ7/Dudr3w04aJQ==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/history/-/history-0.3.2.tgz", + "integrity": "sha512-dL4v3tbUkZ4q4ph3LsCHQV5gV+kyYoPfjcR+ok4SyMoLzOg46YsAri7KVhNDR2El77EzGDPiCZ+9ZSQJqOX/eQ==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "lodash": "^4.17.21", "nanoid": "^4.0.2", @@ -3109,17 +3109,17 @@ } }, "node_modules/@flowgram.ai/history-node-plugin": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/history-node-plugin/-/history-node-plugin-0.2.28.tgz", - "integrity": "sha512-NnFt6ocTvGwWjgluUuvJL+Xivj75LrPEFPqhC+GcmciiNmD0HkhW/ojO0aBKv8/Ppemfjo840dhSyxAxyInPsw==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/history-node-plugin/-/history-node-plugin-0.3.2.tgz", + "integrity": "sha512-Q4kMsq11Z7tpHD9mIwPN3jQMrFter7xzbMznr36G4TLRlPV9bfsWNBhLbBto1WEhbiphQ/TTEiBjWQrxlpAkXQ==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/form": "0.2.28", - "@flowgram.ai/form-core": "0.2.28", - "@flowgram.ai/history": "0.2.28", - "@flowgram.ai/node": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/form": "0.3.2", + "@flowgram.ai/form-core": "0.3.2", + "@flowgram.ai/history": "0.3.2", + "@flowgram.ai/node": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "lodash": "^4.17.21", "reflect-metadata": "~0.2.2" @@ -3148,32 +3148,32 @@ } }, "node_modules/@flowgram.ai/i18n": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/i18n/-/i18n-0.2.28.tgz", - "integrity": "sha512-FsFPlLa834zTBhLKgw6PWm+SoLvsPp2xL9XiphXdjD3kDgLvAsotiO/mdGGuwC+nNMByo2MsUgf7gS7BIVMuTw==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/i18n/-/i18n-0.3.2.tgz", + "integrity": "sha512-k+9f6gwDh4YTL91BfliRL8IEgzGZ4ALBAuFmGVucpx+01NEYAoqwF1+g4u9udlXX9Ncny5DDMNNka1C3B5m46Q==", "dependencies": { - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/utils": "0.3.2", "i18n-js": "^4.5.1" } }, "node_modules/@flowgram.ai/i18n-plugin": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/i18n-plugin/-/i18n-plugin-0.2.28.tgz", - "integrity": "sha512-U0AjDZekzx7Z8ttxSI5JZWE6A2rzQncILYQ4ALTPpaS6nFqna7ju2qEedcUAIf/xo4gZiYugcQc511lvqtnhLg==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/i18n-plugin/-/i18n-plugin-0.3.2.tgz", + "integrity": "sha512-1Zyk6Sh+W62FAF7oP/p+fJ1JxF7iqpoKpOCC65VEHKVQ1TAbHg6fyOoSxNTE7wjnATvt6c3fDw7AYcevIlcRSA==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/i18n": "0.2.28" + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/i18n": "0.3.2" } }, "node_modules/@flowgram.ai/materials-plugin": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/materials-plugin/-/materials-plugin-0.2.28.tgz", - "integrity": "sha512-2s50E0MZwHnE4QVS564d2abd29iMv70r7cnRWeLtc6w91Y+mKHQWM5noeoi/diggIWUmj6w4pFYDnSOcN4DeGA==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/materials-plugin/-/materials-plugin-0.3.2.tgz", + "integrity": "sha512-svG1M1uMIeavXYj8UV5Mv2glcumQ7AYg4/U6xVGhMJFJmnAWNlNVQCqz5PFRkjyIH6FwW/xJVh03TSqpR2Svkw==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/form-core": "0.2.28", - "@flowgram.ai/renderer": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/form-core": "0.3.2", + "@flowgram.ai/renderer": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "reflect-metadata": "~0.2.2" }, @@ -3184,15 +3184,15 @@ } }, "node_modules/@flowgram.ai/node": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/node/-/node-0.2.28.tgz", - "integrity": "sha512-XZ+kKjZdOFYdC8XL2L+BCYAa/wVLnb2dD/lfedMd9Fnf0QaJaQupwhwPnLtmL4HcdB8ro8uBICSIgVGXED66Uw==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/node/-/node-0.3.2.tgz", + "integrity": "sha512-jVr5zlL/vab7g1JSv69fIivKZAb+kPDQJugeNm9Ux9vjaF3nfssFRPeeRCj8JWMw4hqdRUnTms9Mx1udrCLXTg==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/form": "0.2.28", - "@flowgram.ai/form-core": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/form": "0.3.2", + "@flowgram.ai/form-core": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "lodash": "^4.17.21", "nanoid": "^4.0.2", @@ -3204,16 +3204,16 @@ } }, "node_modules/@flowgram.ai/node-core-plugin": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/node-core-plugin/-/node-core-plugin-0.2.28.tgz", - "integrity": "sha512-72R6APzlW2Yy1od2QibIhSW3TW+xz44s0+k4rzJ+EZW0gHEwtmo+OCQLo19WM0CrBFjAomTyK/2J5L6a1mp9OA==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/node-core-plugin/-/node-core-plugin-0.3.2.tgz", + "integrity": "sha512-pSOKiVXSSL4u+7REMfK57n29Kpx7iuH///FAU9/1VdpjtnwLH25F/RBF8fyR+VLqdtwTfhqz2A7OjGMqIardmg==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/form-core": "0.2.28", - "@flowgram.ai/node": "0.2.28", - "@flowgram.ai/renderer": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/form-core": "0.3.2", + "@flowgram.ai/node": "0.3.2", + "@flowgram.ai/renderer": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "reflect-metadata": "~0.2.2" }, @@ -3224,16 +3224,16 @@ } }, "node_modules/@flowgram.ai/node-variable-plugin": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/node-variable-plugin/-/node-variable-plugin-0.2.28.tgz", - "integrity": "sha512-3GpLS6LG1w6Y24TJrXnxSa8BnMhs5sgjqv9VgBdx5DFQRynNtRV52bd3X+eNarNFMMcpEqTGiybrvRqmf84K/w==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/node-variable-plugin/-/node-variable-plugin-0.3.2.tgz", + "integrity": "sha512-GjSqMXv3IqZs6mbiSbHTk/anLQFHl6ozdqM1iCZoOn4V15HW7gJMvXHe3VGwsGxAunsgrbCkkciiZmayWSN9Xg==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/form-core": "0.2.28", - "@flowgram.ai/node": "0.2.28", - "@flowgram.ai/utils": "0.2.28", - "@flowgram.ai/variable-plugin": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/form-core": "0.3.2", + "@flowgram.ai/node": "0.3.2", + "@flowgram.ai/utils": "0.3.2", + "@flowgram.ai/variable-plugin": "0.3.2", "inversify": "^6.0.1", "lodash": "^4.17.21", "reflect-metadata": "~0.2.2" @@ -3262,14 +3262,14 @@ } }, "node_modules/@flowgram.ai/playground-react": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/playground-react/-/playground-react-0.2.28.tgz", - "integrity": "sha512-VvUewy+Av1XHeUa5CBPumkSZR3J/M0m4ME9na6hTfzs7bigKjU8rk+X7397JOWswUBBxGod3Lrw+ujttxxvNag==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/playground-react/-/playground-react-0.3.2.tgz", + "integrity": "sha512-dbuuLF+UKCaIgvlVLd1TbM9t13bePKc+BrFRcYvriXZTAbKI6XvWUD7kQ1hIydU5GgTrhpZEWR6WnrxAvEBlrA==", "dependencies": { - "@flowgram.ai/background-plugin": "0.2.28", - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/shortcuts-plugin": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/background-plugin": "0.3.2", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/shortcuts-plugin": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "reflect-metadata": "~0.2.2" }, @@ -3279,11 +3279,11 @@ } }, "node_modules/@flowgram.ai/reactive": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/reactive/-/reactive-0.2.28.tgz", - "integrity": "sha512-WC8FWI4iBhrrip8If1ghFUhqBxGcHExE4rc6Erx1QQqN7YNFII5/cKF1wAaEgQ99rSVGFxIxjkwrUOjNXnnS6g==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/reactive/-/reactive-0.3.2.tgz", + "integrity": "sha512-U39SOidMenXvcKRgWZ01L7NGCefDwE90S7nil1/1yJ2bqAT9YLpmIY07Sw474sepeZgx2R9HDStuVpLpZOgq0Q==", "dependencies": { - "@flowgram.ai/utils": "0.2.28" + "@flowgram.ai/utils": "0.3.2" }, "peerDependencies": { "react": ">=16.8", @@ -3291,12 +3291,12 @@ } }, "node_modules/@flowgram.ai/redux-devtool-plugin": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/redux-devtool-plugin/-/redux-devtool-plugin-0.2.28.tgz", - "integrity": "sha512-D8C0sWzGAFpFElacEHwONRr2vezcLa3jT6SRtYEbQ0poSA6HLPPYSigRjug8A19jd9NayCVulPm1M59GpuZF3Q==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/redux-devtool-plugin/-/redux-devtool-plugin-0.3.2.tgz", + "integrity": "sha512-yPeQYWK3juW+TeSWYDxjcpjSH1MlbXGpumKaoX4dFfoeNatVVzH77z3R345jLeJv2SwB0bHC7djfp6Dlb2vIsQ==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/variable-core": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/variable-core": "0.3.2", "inversify": "^6.0.1", "reflect-metadata": "~0.2.2", "styled-components": "^5" @@ -3351,14 +3351,14 @@ } }, "node_modules/@flowgram.ai/renderer": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/renderer/-/renderer-0.2.28.tgz", - "integrity": "sha512-7AVVZqsUFeNvq339UfUSohk0ByVDq9ocllODScbeIAVWGZyNjG0tyJyaIEoCYrSqh2l+78C+AsaaBJvOPZG5bg==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/renderer/-/renderer-0.3.2.tgz", + "integrity": "sha512-Pzc43vzAxwpXjJrUkuMopgMMxkplarSlWPw3KdBd9wSIroAslBI6i7z7x3WljfGTJPHGt50EI24tLKTZzGECeQ==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/i18n": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/i18n": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "lodash": "^4.17.21", "reflect-metadata": "~0.2.2" @@ -3369,12 +3369,12 @@ } }, "node_modules/@flowgram.ai/select-box-plugin": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/select-box-plugin/-/select-box-plugin-0.2.28.tgz", - "integrity": "sha512-xIPLKpT3hUtlHzvSOqeu8yqhVJDL7En8cRawKWwJjBs0nrFXEGryPnMCrsmbF1JwTFZrvKrznJacocqmiLwI3A==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/select-box-plugin/-/select-box-plugin-0.3.2.tgz", + "integrity": "sha512-mM58wToixwB/lfGtojwYPgip/sh6Go3+caEH4Eo447dlf88R6bSjWxThZG689QFsY6ZSQEfchg4GO1eYERvOwQ==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/renderer": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/renderer": "0.3.2", "inversify": "^6.0.1", "reflect-metadata": "~0.2.2" }, @@ -3384,13 +3384,13 @@ } }, "node_modules/@flowgram.ai/shortcuts-plugin": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/shortcuts-plugin/-/shortcuts-plugin-0.2.28.tgz", - "integrity": "sha512-S3XkBmZnQ4FZ+A7sD56v6L2UFOhMSwqTmA4S1G8XqMvmbfHjUjR/OxojfQw/MSd5QNygD4ikBesPoj6FMP45FQ==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/shortcuts-plugin/-/shortcuts-plugin-0.3.2.tgz", + "integrity": "sha512-D5Z3CAd61c2e0bQ9n8ULFAK1yFqV9q7UpMQ89IAu+armcT6w0XORC0CP/c7ojoBvGKYaWMH1gZf0NlQHiT6CZQ==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "inversify": "^6.0.1", "reflect-metadata": "~0.2.2", "styled-components": "^5" @@ -3445,9 +3445,9 @@ } }, "node_modules/@flowgram.ai/utils": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/utils/-/utils-0.2.28.tgz", - "integrity": "sha512-HG4LpVo3gUcTp/tJCe72vZ9nByRXgkvp2wYyFFD3ZnGvEL8V3PIRwHGvBMP0UqgYBPHOZyqOZhOenP73msvkwg==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/utils/-/utils-0.3.2.tgz", + "integrity": "sha512-XVmiYft8JfrZsV5M2M8i2AMjhz6Fe4Euv0gOuLiSsZL1ZnR95IHzEHuvSd98of4srcUL+b/RcwB2BMXH+/EJ9w==", "dependencies": { "clsx": "^1.1.1", "inversify": "^6.0.1", @@ -3485,18 +3485,18 @@ } }, "node_modules/@flowgram.ai/variable-core": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-core/-/variable-core-0.2.28.tgz", - "integrity": "sha512-Au6UCupM1d044w++GMv2HC4YGc2nsDNBdem71gjxcDLowGo9FDgKF1Fxh2E+UfCv9QfAcShJzCg0R4EtCpEsuQ==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-core/-/variable-core-0.3.2.tgz", + "integrity": "sha512-95E7akQXzPItx7H/tKT4g5CZ0gg6OCsBou13OfotivZS1Yn5GxtqOeWGz7EHUZwurzB9w6o30VR6Y8vuk049Bg==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/utils": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/utils": "0.3.2", "fast-equals": "^2.0.0", "inversify": "^6.0.1", "lodash": "^4.17.21", "nanoid": "^4.0.2", "reflect-metadata": "~0.2.2", - "rxjs": "^7.8.1" + "rxjs": "^7.8.2" }, "peerDependencies": { "react": ">=16.8", @@ -3521,15 +3521,15 @@ } }, "node_modules/@flowgram.ai/variable-layout": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-layout/-/variable-layout-0.2.28.tgz", - "integrity": "sha512-31mxxnLJFanmdMkMvl6WzGV+CO15gPT0jnMsf0Mqp+s7qadtwuHcwzsDEUU9RKR+d/oXuGBpNGrdtQJzBh2JMQ==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-layout/-/variable-layout-0.3.2.tgz", + "integrity": "sha512-vX9lGLl5yY0VAYv5Bua3jqkh2M80+YB/4Qea0crkPAWzg1eVQcG4zbnR+Wc6c3wNz9cTpT+vq+2kiTsP5+95eQ==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/free-layout-core": "0.2.28", - "@flowgram.ai/utils": "0.2.28", - "@flowgram.ai/variable-core": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/free-layout-core": "0.3.2", + "@flowgram.ai/utils": "0.3.2", + "@flowgram.ai/variable-core": "0.3.2", "inversify": "^6.0.1", "reflect-metadata": "~0.2.2", "styled-components": "^5" @@ -3584,16 +3584,16 @@ } }, "node_modules/@flowgram.ai/variable-plugin": { - "version": "0.2.28", - "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-plugin/-/variable-plugin-0.2.28.tgz", - "integrity": "sha512-lPxcLNClR865dQMGAGy+NSbC+80mWbVXRolbN1ScIj7on9ypTtDs86VTdFGlKMzP8BSWIXM1Rjb6oSEShCxuJA==", + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/@flowgram.ai/variable-plugin/-/variable-plugin-0.3.2.tgz", + "integrity": "sha512-Fm4DS/DrfcyKtvwUyoB4y5dBr8D/JWaSZjhu6kWee3HIV46uJLruufynMBQprWEgSMj9RyYFoFVQWH4hYisNWg==", "dependencies": { - "@flowgram.ai/core": "0.2.28", - "@flowgram.ai/document": "0.2.28", - "@flowgram.ai/free-layout-core": "0.2.28", - "@flowgram.ai/utils": "0.2.28", - "@flowgram.ai/variable-core": "0.2.28", - "@flowgram.ai/variable-layout": "0.2.28", + "@flowgram.ai/core": "0.3.2", + "@flowgram.ai/document": "0.3.2", + "@flowgram.ai/free-layout-core": "0.3.2", + "@flowgram.ai/utils": "0.3.2", + "@flowgram.ai/variable-core": "0.3.2", + "@flowgram.ai/variable-layout": "0.3.2", "inversify": "^6.0.1", "reflect-metadata": "~0.2.2", "styled-components": "^5" @@ -4028,9 +4028,9 @@ } }, "node_modules/@rc-component/trigger": { - "version": "2.2.7", - "resolved": "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-2.2.7.tgz", - "integrity": "sha512-Qggj4Z0AA2i5dJhzlfFSmg1Qrziu8dsdHOihROL5Kl18seO2Eh/ZaTYt2c8a/CyGaTChnFry7BEYew1+/fhSbA==", + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@rc-component/trigger/-/trigger-2.3.0.tgz", + "integrity": "sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==", "dependencies": { "@babel/runtime": "^7.23.2", "@rc-component/portal": "^1.1.0", @@ -5000,9 +5000,9 @@ } }, "node_modules/@uiw/codemirror-extensions-basic-setup": { - "version": "4.24.1", - "resolved": "https://registry.npmmirror.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.24.1.tgz", - "integrity": "sha512-o1m1a8eUS3fWERMbDFvN8t8sZUFPgDKNemmlQ5Ot2vKm+Ax84lKP1dhEFgkiOaZ1bDHk4T5h6SjHuTghrJHKww==", + "version": "4.24.2", + "resolved": "https://registry.npmmirror.com/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.24.2.tgz", + "integrity": "sha512-wW/gjLRvVUeYyhdh2TApn25cvdcR+Rhg6R/j3eTOvXQzU1HNzNYCVH4YKVIfgtfdM/Xs+N8fkk+rbr1YvBppCg==", "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", @@ -5026,20 +5026,20 @@ } }, "node_modules/@uiw/codemirror-theme-vscode": { - "version": "4.24.1", - "resolved": "https://registry.npmmirror.com/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.24.1.tgz", - "integrity": "sha512-w2/eaPcYsHi03aWoKzVxIWv5dBDKW4YPnjwyYLgFY3uNdZmSfoNkFhqW7GJgNNF77ATixPq2wm9XqYeOTtvuww==", + "version": "4.24.2", + "resolved": "https://registry.npmmirror.com/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.24.2.tgz", + "integrity": "sha512-5rLvAMKNjqtHSy3BZnI1Q4ZLqUDDJC5fKOp8Mv5VjPqEmAxEAIGrIXBnhAFlRg5VjHbHEMtM1O5ugL3IeHoBdQ==", "dependencies": { - "@uiw/codemirror-themes": "4.24.1" + "@uiw/codemirror-themes": "4.24.2" }, "funding": { "url": "https://jaywcjlove.github.io/#/sponsor" } }, "node_modules/@uiw/codemirror-themes": { - "version": "4.24.1", - "resolved": "https://registry.npmmirror.com/@uiw/codemirror-themes/-/codemirror-themes-4.24.1.tgz", - "integrity": "sha512-hduBbFNiWNW6nYa2/giKQ9YpzhWNw87BGpCjC+cXYMZ7bCD6q5DC6Hw+7z7ZwSzEaOQvV91lmirOjJ8hn9+pkg==", + "version": "4.24.2", + "resolved": "https://registry.npmmirror.com/@uiw/codemirror-themes/-/codemirror-themes-4.24.2.tgz", + "integrity": "sha512-0fQusJE08APL+1WM0xYqvampVM2RpABHjvZbcJWHdRc+teZbrP8907VW1ZX1tO/7/xgRRt4Fc3RHPCcTs7b0NQ==", "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", @@ -5055,15 +5055,15 @@ } }, "node_modules/@uiw/react-codemirror": { - "version": "4.24.1", - "resolved": "https://registry.npmmirror.com/@uiw/react-codemirror/-/react-codemirror-4.24.1.tgz", - "integrity": "sha512-BivF4NLqbuBQK5gPVhSkOARi9nPXw8X5r25EnInPeY+I9l1dfEX8O9V6+0xHTlGHyUo0cNfGEF9t1KHEicUfJw==", + "version": "4.24.2", + "resolved": "https://registry.npmmirror.com/@uiw/react-codemirror/-/react-codemirror-4.24.2.tgz", + "integrity": "sha512-kp7DhTq4RR+M2zJBQBrHn1dIkBrtOskcwJX4vVsKGByReOvfMrhqRkGTxYMRDTX6x75EG2mvBJPDKYcUQcHWBw==", "dependencies": { "@babel/runtime": "^7.18.6", "@codemirror/commands": "^6.1.0", "@codemirror/state": "^6.1.1", "@codemirror/theme-one-dark": "^6.0.0", - "@uiw/codemirror-extensions-basic-setup": "4.24.1", + "@uiw/codemirror-extensions-basic-setup": "4.24.2", "codemirror": "^6.0.0" }, "funding": { @@ -5075,8 +5075,8 @@ "@codemirror/theme-one-dark": ">=6.0.0", "@codemirror/view": ">=6.0.0", "codemirror": ">=6.0.0", - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "react": ">=17.0.0", + "react-dom": ">=17.0.0" } }, "node_modules/@umijs/route-utils": { @@ -5472,9 +5472,9 @@ } }, "node_modules/antd": { - "version": "5.26.6", - "resolved": "https://registry.npmmirror.com/antd/-/antd-5.26.6.tgz", - "integrity": "sha512-k8ipeT+UL2tP/x4jHTXElScAxsD94JgrIEeGHj80nNO4dL9hqcmaOUBpHo3ieCf6MFjhS7gLUthysQeP6e7DUg==", + "version": "5.26.7", + "resolved": "https://registry.npmmirror.com/antd/-/antd-5.26.7.tgz", + "integrity": "sha512-iCyXN6+i2CUVEOSzzJKfbKeg115qoJhGvSkCh5uzAf9hANwHUOJQhsMn+KtN+Lx/2NQ6wfM7nGZ+7NPNO5Pn1w==", "dependencies": { "@ant-design/colors": "^7.2.1", "@ant-design/cssinjs": "^1.23.0", @@ -5487,7 +5487,7 @@ "@rc-component/mutate-observer": "^1.1.0", "@rc-component/qrcode": "~1.0.0", "@rc-component/tour": "~1.15.1", - "@rc-component/trigger": "^2.2.7", + "@rc-component/trigger": "^2.3.0", "classnames": "^2.5.1", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.11", @@ -5517,7 +5517,7 @@ "rc-switch": "~4.1.0", "rc-table": "~7.51.1", "rc-tabs": "~15.6.1", - "rc-textarea": "~1.10.0", + "rc-textarea": "~1.10.1", "rc-tooltip": "~6.4.0", "rc-tree": "~5.13.1", "rc-tree-select": "~5.27.0", @@ -6151,6 +6151,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "optional": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz", @@ -6631,9 +6648,9 @@ } }, "node_modules/eslint-plugin-better-tailwindcss": { - "version": "3.7.2", - "resolved": "https://registry.npmmirror.com/eslint-plugin-better-tailwindcss/-/eslint-plugin-better-tailwindcss-3.7.2.tgz", - "integrity": "sha512-uJnoDGmUPtxHHcIW5njEoh1CYlq39E4J2pWq+D7REDGhW41arSCeeaiiwAT4Ok04kXNvmFBiYmC6Jdd6Bz7o2Q==", + "version": "3.7.3", + "resolved": "https://registry.npmmirror.com/eslint-plugin-better-tailwindcss/-/eslint-plugin-better-tailwindcss-3.7.3.tgz", + "integrity": "sha512-7I2MM1/OetBamiNWGkAetzQtvph6N7B6Iy5nIU9srFkHXnqR5G6lFX7ZT+JZmLrirrYSn8E0THeJg9QTQaI/kQ==", "dev": true, "dependencies": { "@eslint/css-tree": "^3.6.2", @@ -9326,9 +9343,9 @@ } }, "node_modules/rc-textarea": { - "version": "1.10.0", - "resolved": "https://registry.npmmirror.com/rc-textarea/-/rc-textarea-1.10.0.tgz", - "integrity": "sha512-ai9IkanNuyBS4x6sOL8qu/Ld40e6cEs6pgk93R+XLYg0mDSjNBGey6/ZpDs5+gNLD7urQ14po3V6Ck2dJLt9SA==", + "version": "1.10.2", + "resolved": "https://registry.npmmirror.com/rc-textarea/-/rc-textarea-1.10.2.tgz", + "integrity": "sha512-HfaeXiaSlpiSp0I/pvWpecFEHpVysZ9tpDLNkxQbMvMz6gsr7aVZ7FpWP9kt4t7DB+jJXesYS0us1uPZnlRnwQ==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.1", @@ -11146,17 +11163,17 @@ } }, "node_modules/zod": { - "version": "4.0.10", - "resolved": "https://registry.npmmirror.com/zod/-/zod-4.0.10.tgz", - "integrity": "sha512-3vB+UU3/VmLL2lvwcY/4RV2i9z/YU0DTV/tDuYjrwmx5WeJ7hwy+rGEEx8glHp6Yxw7ibRbKSaIFBgReRPe5KA==", + "version": "4.0.14", + "resolved": "https://registry.npmmirror.com/zod/-/zod-4.0.14.tgz", + "integrity": "sha512-nGFJTnJN6cM2v9kXL+SOBq3AtjQby3Mv5ySGFof5UGRHrRioSJ5iG680cYNjE/yWk671nROcpPj4hAS8nyLhSw==", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zustand": { - "version": "5.0.6", - "resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.6.tgz", - "integrity": "sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==", + "version": "5.0.7", + "resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.7.tgz", + "integrity": "sha512-Ot6uqHDW/O2VdYsKLLU8GQu8sCOM1LcoE8RwvLv9uuRT9s6SOHCKs0ZEOhxg+I1Ld+A1Q5lwx+UlKXXUoCZITg==", "engines": { "node": ">=12.20.0" }, diff --git a/ui/package.json b/ui/package.json index 8150cac20..4bfdbc5e7 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,7 +1,6 @@ { - "name": "ui", + "name": "@certimate/webui", "private": true, - "version": "0.0.0", "type": "module", "scripts": { "dev": "vite --host", @@ -15,13 +14,14 @@ "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language": "^6.11.2", "@codemirror/legacy-modes": "^6.5.1", - "@flowgram.ai/fixed-layout-editor": "^0.2.28", + "@flowgram.ai/fixed-layout-editor": "^0.3.2", + "@flowgram.ai/document": "^0.3.2", "@tabler/icons-react": "^3.34.1", - "@uiw/codemirror-extensions-basic-setup": "^4.24.1", - "@uiw/codemirror-theme-vscode": "^4.24.1", - "@uiw/react-codemirror": "^4.24.1", + "@uiw/codemirror-extensions-basic-setup": "^4.24.2", + "@uiw/codemirror-theme-vscode": "^4.24.2", + "@uiw/react-codemirror": "^4.24.2", "ahooks": "^3.9.0", - "antd": "^5.26.6", + "antd": "^5.26.7", "antd-zod": "^7.0.0", "clsx": "^2.1.1", "cron-parser": "^5.3.0", @@ -38,8 +38,8 @@ "react-i18next": "^15.6.1", "react-router-dom": "^7.7.1", "tailwind-merge": "^3.3.1", - "zod": "^4.0.10", - "zustand": "^5.0.6" + "zod": "^4.0.14", + "zustand": "^5.0.7" }, "devDependencies": { "@eslint/js": "^9.32.0", @@ -56,7 +56,7 @@ "eslint": "^9.32.0", "eslint-config-prettier": "^10.1.8", "eslint-import-resolver-typescript": "^4.4.4", - "eslint-plugin-better-tailwindcss": "^3.7.2", + "eslint-plugin-better-tailwindcss": "^3.7.3", "eslint-plugin-import": "^2.32.0", "eslint-plugin-prettier": "^5.5.3", "eslint-plugin-react-hooks": "^5.2.0", diff --git a/ui/src/components/MultipleSplitValueInput.tsx b/ui/src/components/MultipleSplitValueInput.tsx index 2f571aabd..d161c8474 100644 --- a/ui/src/components/MultipleSplitValueInput.tsx +++ b/ui/src/components/MultipleSplitValueInput.tsx @@ -2,7 +2,7 @@ import { IconList } from "@tabler/icons-react"; import { useControllableValue } from "ahooks"; import { Button, Form, Input, type InputProps, Space } from "antd"; -import { nanoid } from "nanoid"; +import { nanoid } from "nanoid/non-secure"; import { useAntdForm } from "@/hooks"; import ModalForm from "./ModalForm"; diff --git a/ui/src/components/workflow/WorkflowElementsContainer.tsx b/ui/src/components/workflow/WorkflowElementsContainer.tsx index dcc83c724..bf189d3a0 100644 --- a/ui/src/components/workflow/WorkflowElementsContainer.tsx +++ b/ui/src/components/workflow/WorkflowElementsContainer.tsx @@ -30,15 +30,19 @@ const WorkflowElementsContainer = ({ className, style, disabled }: WorkflowEleme
-
- -
-
+
- +
); diff --git a/ui/src/components/workflow/designer/Editor.tsx b/ui/src/components/workflow/designer/Editor.tsx new file mode 100644 index 000000000..eed080e4f --- /dev/null +++ b/ui/src/components/workflow/designer/Editor.tsx @@ -0,0 +1,119 @@ +import { useMemo, useRef } from "react"; +import { + ConstantKeys, + EditorRenderer, + FixedLayoutEditorProvider, + type FixedLayoutPluginContext, + type FixedLayoutProps, + type FlowDocumentJSON, + FlowTextKey, +} from "@flowgram.ai/fixed-layout-editor"; +import "@flowgram.ai/fixed-layout-editor/index.css"; +import { useDeepCompareEffect } from "ahooks"; +import { theme } from "antd"; + +import { getFlowComponents } from "./components"; +import NodeRender from "./NodeRender"; +import { getFlowNodeRegistries } from "./nodes"; +import { BlockNode } from "./nodes/_shared"; +import "./flowgram.css"; + +export interface EditorProps { + className?: string; + style?: React.CSSProperties; + children?: React.ReactNode; + initialData?: FlowDocumentJSON; + readonly?: boolean; +} + +const Editor = ({ className, style, children, initialData, readonly }: EditorProps) => { + const { token: themeToken } = theme.useToken(); + + const flowgramEditorRef = useRef(null); + const flowgramEditorProps = useMemo( + () => ({ + initialData: initialData, + + constants: { + [ConstantKeys.BASE_COLOR]: themeToken.colorBorder, + [ConstantKeys.BASE_ACTIVATED_COLOR]: themeToken.colorPrimary, + [ConstantKeys.NODE_SPACING]: 64, + [ConstantKeys.BRANCH_SPACING]: 64, + // [ConstantKeys.INLINE_BLOCKS_PADDING_TOP]: 48, + // [ConstantKeys.INLINE_BLOCKS_PADDING_BOTTOM]: 48, + }, + + background: { + backgroundColor: themeToken.colorBgContainer, + dotSize: 0, + }, + + playground: { + autoFocus: true, + autoResize: true, + preventGlobalGesture: true, + }, + + scroll: { + enableScrollLimit: true, + }, + + readonly: readonly, + + nodeEngine: { + enable: true, + }, + + variableEngine: { + enable: true, + }, + + materials: { + components: getFlowComponents(), + renderTexts: { + [FlowTextKey.TRY_START_TEXT]: "Try", + [FlowTextKey.TRY_END_TEXT]: "Finally", + [FlowTextKey.CATCH_TEXT]: "Catch", + }, + renderDefaultNode: NodeRender, + }, + + nodeRegistries: getFlowNodeRegistries(), + + getNodeDefaultRegistry(type) { + return { + type, + meta: { + defaultExpanded: true, + }, + formMeta: { + render: () => {type}, + }, + }; + }, + + onAllLayersRendered: (ctx) => { + // 画布初始化后向下滚动一点,露出可能被 Alert 遮挡的部分 + setTimeout(() => { + ctx.playground.config.scroll({ scrollY: -80 }); + }, 1); + }, + }), + [themeToken, initialData, readonly] + ); + + useDeepCompareEffect(() => { + flowgramEditorRef.current?.document?.fromJSON?.(initialData ?? {}); + }, [initialData]); + + return ( +
+ + + {children} + +
+ ); +}; + +export default Editor; diff --git a/ui/src/components/workflow/designer/NodeRender.tsx b/ui/src/components/workflow/designer/NodeRender.tsx new file mode 100644 index 000000000..55a40ac03 --- /dev/null +++ b/ui/src/components/workflow/designer/NodeRender.tsx @@ -0,0 +1,27 @@ +import { type NodeRenderProps, useNodeRender } from "@flowgram.ai/fixed-layout-editor"; + +export interface NodeProps extends NodeRenderProps {} + +const Node = (_: NodeProps) => { + const nodeRender = useNodeRender(); + + return ( +
{ + nodeRender.startDrag(e); + e.stopPropagation(); + }} + > + {nodeRender.form?.render()} +
+ ); +}; + +export default Node; diff --git a/ui/src/components/workflow/designer/Toolbar.tsx b/ui/src/components/workflow/designer/Toolbar.tsx new file mode 100644 index 000000000..72245af6f --- /dev/null +++ b/ui/src/components/workflow/designer/Toolbar.tsx @@ -0,0 +1,78 @@ +import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { EditorState, FlowLayoutDefault, useClientContext, usePlaygroundTools, useRefresh } from "@flowgram.ai/fixed-layout-editor"; +import { IconHandStop, IconLayoutCards, IconMaximize, IconMinus, IconPlus, IconPointer } from "@tabler/icons-react"; +import { Button, Tooltip } from "antd"; + +import { mergeCls } from "@/utils/css"; + +export interface ToolbarProps { + className?: string; + style?: React.CSSProperties; +} + +const Toolbar = ({ className, style }: ToolbarProps) => { + const { t } = useTranslation(); + + const ctx = useClientContext(); + const { playground } = ctx; + + const tools = usePlaygroundTools({ minZoom: 0.1, maxZoom: 3, padding: 48 }); + + const refresh = useRefresh(); + useEffect(() => { + const disposable = playground.config.onReadonlyOrDisabledChange(() => refresh()); + return () => disposable.dispose(); + }, [playground]); + + const [isMouseFriendly, setIsMouseFriendly] = useState(playground.editorState.is(EditorState.STATE_MOUSE_FRIENDLY_SELECT.id)); + + const handleToggleLayout = useCallback(() => { + if (tools.isVertical) { + tools.changeLayout(FlowLayoutDefault.HORIZONTAL_FIXED_LAYOUT); + } else { + tools.changeLayout(FlowLayoutDefault.VERTICAL_FIXED_LAYOUT); + } + }, [tools.isVertical]); + + const handleToggleMouseFriendly = useCallback(() => { + if (isMouseFriendly) { + playground.editorState.changeState(EditorState.STATE_SELECT.id); + setIsMouseFriendly(false); + } else { + playground.editorState.changeState(EditorState.STATE_MOUSE_FRIENDLY_SELECT.id); + setIsMouseFriendly(true); + } + }, [isMouseFriendly]); + + return ( +
+
+ + + + +
+
+ ); +}; + +export default Toolbar; diff --git a/ui/src/components/workflow/designer/components/Adder.tsx b/ui/src/components/workflow/designer/components/Adder.tsx new file mode 100644 index 000000000..d915ae6bb --- /dev/null +++ b/ui/src/components/workflow/designer/components/Adder.tsx @@ -0,0 +1,65 @@ +import { type AdderProps as FlowgramAdderProps, useClientContext } from "@flowgram.ai/fixed-layout-editor"; + +import { IconPlus } from "@tabler/icons-react"; +import { Button, Dropdown } from "antd"; + +import { getFlowNodeRegistries } from "../nodes"; + +export interface AdderProps extends FlowgramAdderProps {} + +const Adder = ({ from, hoverActivated }: AdderProps) => { + const ctx = useClientContext(); + const { operation, playground } = ctx; + + const menuItems = getFlowNodeRegistries() + .filter((registry) => { + if (registry.canAdd != null) { + return registry.canAdd(ctx, from); + } + if (registry.meta?.addDisable != null) { + return registry.meta.addDisable; + } + return true; + }) + .map((registry) => { + const Icon = registry.meta?.icon; + + return { + key: registry.type, + label: registry.type, + icon: ( + + + + ), + onClick: () => { + const props = registry.onAdd!(ctx, from); + const block = operation.addFromNode(from, { + ...props, + blocks: props?.blocks ?? [], + }); + + setTimeout(() => { + playground.scrollToView({ + bounds: block.bounds, + scrollToCenter: true, + }); + }, 1); + }, + }; + }); + + return playground.config.readonlyOrDisabled ? null : ( +
+ + {hoverActivated ? ( +
+ ); +}; + +export default Adder; diff --git a/ui/src/components/workflow/designer/components/BranchAdder.tsx b/ui/src/components/workflow/designer/components/BranchAdder.tsx new file mode 100644 index 000000000..b2ca2c6db --- /dev/null +++ b/ui/src/components/workflow/designer/components/BranchAdder.tsx @@ -0,0 +1,62 @@ +import { useTranslation } from "react-i18next"; +import { FlowNodeBaseType, type FlowNodeEntity, type AdderProps as FlowgramAdderProps, useClientContext } from "@flowgram.ai/fixed-layout-editor"; +import { Button } from "antd"; +import { nanoid } from "nanoid"; + +import { BranchBlockNodeRegistry } from "../nodes/ConditionNode"; +import { CatchBlockNodeRegistry } from "../nodes/TryCatchNode"; +import { NodeType } from "../nodes/typings"; + +export interface BranchAdderProps extends FlowgramAdderProps {} + +const BranchAdder = ({ node }: BranchAdderProps) => { + const { t } = useTranslation(); + + const ctx = useClientContext(); + const { operation, playground } = ctx; + + const handleAddBranch = () => { + let block: FlowNodeEntity; + switch (node.flowNodeType) { + case NodeType.Condition: + block = operation.addBlock(node, BranchBlockNodeRegistry.onAdd!(ctx, node)); + break; + + case NodeType.TryCatch: + block = operation.addBlock(node, CatchBlockNodeRegistry.onAdd!(ctx, node)); + break; + + default: + console.warn(`[certimate] unsupported node type for adding branch: '${node.flowNodeType}'`); + block = operation.addBlock(node, { + id: nanoid(), + type: FlowNodeBaseType.BLOCK, + data: { + name: "Branch", + }, + }); + break; + } + + setTimeout(() => { + playground.scrollToView({ + bounds: block.bounds, + scrollToCenter: true, + }); + }, 1); + }; + + return playground.config.readonlyOrDisabled ? null : ( +
node.firstChild?.renderData?.toggleMouseEnter()} + onMouseLeave={() => node.firstChild?.renderData?.toggleMouseLeave()} + > + +
+ ); +}; + +export default BranchAdder; diff --git a/ui/src/components/workflow/designer/components/Collapse.tsx b/ui/src/components/workflow/designer/components/Collapse.tsx new file mode 100644 index 000000000..28560fd96 --- /dev/null +++ b/ui/src/components/workflow/designer/components/Collapse.tsx @@ -0,0 +1,9 @@ +import { type CollapseProps as FlowgramCollapseProps } from "@flowgram.ai/fixed-layout-editor"; + +export interface CollapseProps extends FlowgramCollapseProps {} + +const Collapse = (_: CollapseProps) => { + return null; +}; + +export default Collapse; diff --git a/ui/src/components/workflow/designer/components/DragHighlightAdder.tsx b/ui/src/components/workflow/designer/components/DragHighlightAdder.tsx new file mode 100644 index 000000000..f4543ef65 --- /dev/null +++ b/ui/src/components/workflow/designer/components/DragHighlightAdder.tsx @@ -0,0 +1,14 @@ +import { type DragNodeProps as FlowgramDragNodeProps } from "@flowgram.ai/fixed-layout-editor"; +import { IconGradienter } from "@tabler/icons-react"; + +const DragHighlightAdder = (_: FlowgramDragNodeProps) => { + return ( +
+
+ +
+
+ ); +}; + +export default DragHighlightAdder; diff --git a/ui/src/components/workflow/designer/components/DragNode.tsx b/ui/src/components/workflow/designer/components/DragNode.tsx new file mode 100644 index 000000000..e6a2ddd64 --- /dev/null +++ b/ui/src/components/workflow/designer/components/DragNode.tsx @@ -0,0 +1,26 @@ +import { type FlowNodeEntity, type DragNodeProps as FlowgramDragNodeProps, getNodeForm } from "@flowgram.ai/fixed-layout-editor"; +import { Badge, Card } from "antd"; + +export interface DragNodeProps extends FlowgramDragNodeProps { + dragStart: FlowNodeEntity; + dragNodes: FlowNodeEntity[]; +} + +const DragNode = ({ dragStart, dragNodes }: DragNodeProps) => { + const count = (dragNodes || []) + .map((n) => (n.allCollapsedChildren.length ? n.allCollapsedChildren.filter((_n) => !_n.hidden).length : 1)) + .reduce((acc, cur) => acc + cur, 0); + return ( + 1 ? count : 0} size="small"> +
+ +
+
{dragStart ? getNodeForm(dragStart)?.getValueIn("name") || `#${dragStart?.id}` : "\u00A0"}
+
+
+
+
+ ); +}; + +export default DragNode; diff --git a/ui/src/components/workflow/designer/components/DraggingAdder.tsx b/ui/src/components/workflow/designer/components/DraggingAdder.tsx new file mode 100644 index 000000000..97f52ed4e --- /dev/null +++ b/ui/src/components/workflow/designer/components/DraggingAdder.tsx @@ -0,0 +1,31 @@ +import { FlowDragLayer, type AdderProps as FlowgramAdderProps, usePlayground } from "@flowgram.ai/fixed-layout-editor"; +import { IconChevronsDown } from "@tabler/icons-react"; + +export interface DraggingAdderProps extends FlowgramAdderProps {} + +const DraggingAdder = ({ from }: DraggingAdderProps) => { + const playground = usePlayground(); + + const layer = playground.getLayer(FlowDragLayer); + if (!layer) return <>; + if ( + layer.options.canDrop && + !layer.options.canDrop({ + dragNodes: layer.dragEntities ?? [], + dropNode: from, + isBranch: false, + }) + ) { + return <>; + } + + return ( +
+
+ +
+
+ ); +}; + +export default DraggingAdder; diff --git a/ui/src/components/workflow/designer/components/Null.tsx b/ui/src/components/workflow/designer/components/Null.tsx new file mode 100644 index 000000000..fe29349c2 --- /dev/null +++ b/ui/src/components/workflow/designer/components/Null.tsx @@ -0,0 +1,11 @@ +import { type FlowNodeEntity } from "@flowgram.ai/fixed-layout-editor"; + +export interface NullProps { + node: FlowNodeEntity; +} + +const Null = (_: NullProps) => { + return null; +}; + +export default Null; diff --git a/ui/src/components/workflow/designer/components/TryCatchCollapse.tsx b/ui/src/components/workflow/designer/components/TryCatchCollapse.tsx new file mode 100644 index 000000000..77f68d38a --- /dev/null +++ b/ui/src/components/workflow/designer/components/TryCatchCollapse.tsx @@ -0,0 +1,69 @@ +import { useState } from "react"; +import { + type CustomLabelProps, + FlowNodeRenderData, + FlowNodeTransformData, + FlowRendererRegistry, + FlowTextKey, + useBaseColor, +} from "@flowgram.ai/fixed-layout-editor"; + +export interface TryCatchCollapseProps extends CustomLabelProps {} + +const TryCatchCollapse = ({ node, ...props }: TryCatchCollapseProps) => { + const { baseColor, baseActivatedColor } = useBaseColor(); + + const nodeActivateData = node.getData(FlowNodeRenderData)!; + const nodeTransformData = node.getData(FlowNodeTransformData)!; + + const [hoverActivated, setHoverActivated] = useState(false); + + const handleMouseEnter = () => { + setHoverActivated(true); + nodeActivateData.activated = true; + }; + + const handleMouseLeave = () => { + setHoverActivated(false); + nodeActivateData.activated = false; + }; + + if (!nodeTransformData || !nodeTransformData.parent) { + return <>; + } + + const width = nodeTransformData.inputPoint.x - nodeTransformData.parent.inputPoint.x; + const height = 40; + return ( +
+
+ {node.getService(FlowRendererRegistry).getText(FlowTextKey.CATCH_TEXT)} +
+ + {/* {renderCollapse()} */} +
+ ); +}; + +export default TryCatchCollapse; diff --git a/ui/src/components/workflow/designer/components/index.ts b/ui/src/components/workflow/designer/components/index.ts new file mode 100644 index 000000000..90fb868a3 --- /dev/null +++ b/ui/src/components/workflow/designer/components/index.ts @@ -0,0 +1,30 @@ +import { FlowRendererKey } from "@flowgram.ai/fixed-layout-editor"; + +import Adder from "./Adder"; +import BranchAdder from "./BranchAdder"; +import Collapse from "./Collapse"; +import DraggingAdder from "./DraggingAdder"; +import DragHighlightAdder from "./DragHighlightAdder"; +import DragNode from "./DragNode"; +import Null from "./Null"; +import TryCatchCollapse from "./TryCatchCollapse"; + +export const getFlowComponents = () => { + return { + [FlowRendererKey.ADDER]: Adder, + [FlowRendererKey.BRANCH_ADDER]: BranchAdder, + [FlowRendererKey.SLOT_ADDER]: Null, + + [FlowRendererKey.COLLAPSE]: Collapse, + [FlowRendererKey.TRY_CATCH_COLLAPSE]: TryCatchCollapse, + [FlowRendererKey.SLOT_COLLPASE]: Null, + + [FlowRendererKey.DRAG_NODE]: DragNode, + [FlowRendererKey.DRAG_HIGHLIGHT_ADDER]: DragHighlightAdder, + [FlowRendererKey.DRAG_BRANCH_HIGHLIGHT_ADDER]: DragHighlightAdder, + [FlowRendererKey.DRAGGABLE_ADDER]: DraggingAdder, + + [FlowRendererKey.CONTEXT_MENU_POPOVER]: Null, + [FlowRendererKey.SELECTOR_BOX_POPOVER]: Null, + }; +}; diff --git a/ui/src/components/workflow/designer/flowgram.css b/ui/src/components/workflow/designer/flowgram.css new file mode 100644 index 000000000..0e4ac1936 --- /dev/null +++ b/ui/src/components/workflow/designer/flowgram.css @@ -0,0 +1,22 @@ +/* 隐藏 flowgram 的标签背景 */ +/* flowgram 暂未提供相关配置项,这里需与 antd.colorBgContainer 保持一致 */ +:root { + --g-editor-background: #fff; +} +.dark { + --g-editor-background: #262c2d; +} + +/* 隐藏 flowgram 的折叠按钮 */ +.flow-canvas-collapse-adder > .flow-canvas-collapse { + display: hidden !important; + position: absolute !important; + top: -99999px !important; + left: -99999px !important; +} + +/* 控制 flowgram 线条样式 */ +/* flowgram 暂未提供相关配置项 */ +.flow-lines-container { + stroke-width: 1.5px; +} diff --git a/ui/src/components/workflow/designer/nodes/BusinessNode.tsx b/ui/src/components/workflow/designer/nodes/BusinessNode.tsx new file mode 100644 index 000000000..af9288615 --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/BusinessNode.tsx @@ -0,0 +1,206 @@ +import { getI18n } from "react-i18next"; +import { IconCloudUpload, IconContract, IconDeviceDesktopSearch, IconPackage, IconSend } from "@tabler/icons-react"; +import { Avatar, Typography } from "antd"; +import { nanoid } from "nanoid"; + +import { deploymentProvidersMap, notificationProvidersMap } from "@/domain/provider"; + +import { BaseNode } from "./_shared"; +import { type NodeRegistry, NodeType } from "./typings"; + +export const BizApplyNodeRegistry: NodeRegistry = { + type: NodeType.BizApply, + + meta: { + helpText: getI18n().t("workflow_node.apply.help"), + + icon: IconContract, + iconColor: "#fff", + iconBgColor: "#5b65f5", + }, + + formMeta: { + render: ({ form }) => { + const { t } = getI18n(); + + const fieldDomains = form.getValueIn("config.domains"); + + return {fieldDomains ? fieldDomains.split(";").join("; ") : t("workflow.detail.design.nodes.placeholder")}; + }, + }, + + onAdd: () => { + const { t } = getI18n(); + + return { + id: nanoid(), + type: NodeType.BizApply, + data: { + name: t("workflow_node.apply.default_name"), + }, + }; + }, +}; + +export const BizUploadNodeRegistry: NodeRegistry = { + type: NodeType.BizUpload, + + meta: { + helpText: getI18n().t("workflow_node.upload.help"), + + icon: IconCloudUpload, + iconColor: "#fff", + iconBgColor: "#5b65f5", + }, + + formMeta: { + render: ({ form }) => { + const { t } = getI18n(); + + const fieldDomains = form.getValueIn("config.domains"); + + return {fieldDomains ? fieldDomains.split(";").join("; ") : t("workflow.detail.design.nodes.placeholder")}; + }, + }, + + onAdd: () => { + const { t } = getI18n(); + + return { + id: nanoid(), + type: NodeType.BizUpload, + data: { + name: t("workflow_node.upload.default_name"), + }, + }; + }, +}; + +export const BizMonitorNodeRegistry: NodeRegistry = { + type: NodeType.BizMonitor, + + meta: { + helpText: getI18n().t("workflow_node.monitor.help"), + + icon: IconDeviceDesktopSearch, + iconColor: "#fff", + iconBgColor: "#5b65f5", + }, + + formMeta: { + render: ({ form }) => { + const { t } = getI18n(); + + const fieldDomain = form.getValueIn("config.domain"); + const fieldHost = form.getValueIn("config.host"); + + return {fieldDomain || fieldHost ? fieldDomain || fieldHost : t("workflow.detail.design.nodes.placeholder")}; + }, + }, + + onAdd: () => { + const { t } = getI18n(); + + return { + id: nanoid(), + type: NodeType.BizMonitor, + data: { + name: t("workflow_node.monitor.default_name"), + }, + }; + }, +}; + +export const BizDeployNodeRegistry: NodeRegistry = { + type: NodeType.BizDeploy, + + meta: { + helpText: getI18n().t("workflow_node.deploy.help"), + + icon: IconPackage, + iconColor: "#fff", + iconBgColor: "#5b65f5", + }, + + formMeta: { + render: ({ form }) => { + const { t } = getI18n(); + + const fieldProvider = form.getValueIn("config.provider"); + + return ( + +
+ {fieldProvider ? ( + <> +
{t(deploymentProvidersMap.get(fieldProvider)?.name ?? "")}
+ + + ) : ( + t("workflow.detail.design.nodes.placeholder") + )} +
+
+ ); + }, + }, + + onAdd: () => { + const { t } = getI18n(); + + return { + id: nanoid(), + type: NodeType.BizMonitor, + data: { + name: t("workflow_node.deploy.default_name"), + }, + }; + }, +}; + +export const BizNotifyNodeRegistry: NodeRegistry = { + type: NodeType.BizNotify, + + meta: { + helpText: getI18n().t("workflow_node.notify.help"), + + icon: IconSend, + iconColor: "#fff", + iconBgColor: "#0693d4", + }, + + formMeta: { + render: ({ form }) => { + const { t } = getI18n(); + + const fieldProvider = form.getValueIn("config.provider"); + + return ( + +
+ {fieldProvider ? ( + <> +
{t(notificationProvidersMap.get(fieldProvider)?.name ?? "")}
+ + + ) : ( + t("workflow.detail.design.nodes.placeholder") + )} +
+
+ ); + }, + }, + + onAdd: () => { + const { t } = getI18n(); + + return { + id: nanoid(), + type: NodeType.BizNotify, + data: { + name: t("workflow_node.notify.default_name"), + }, + }; + }, +}; diff --git a/ui/src/components/workflow/designer/nodes/ConditionNode.tsx b/ui/src/components/workflow/designer/nodes/ConditionNode.tsx new file mode 100644 index 000000000..71f106c9d --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/ConditionNode.tsx @@ -0,0 +1,108 @@ +import { getI18n } from "react-i18next"; +import { FlowNodeBaseType, FlowNodeSplitType, ValidateTrigger } from "@flowgram.ai/fixed-layout-editor"; +import { IconFilter, IconFilterFilled, IconSitemap } from "@tabler/icons-react"; +import { nanoid } from "nanoid"; + +import { BaseNode, BlockNode } from "./_shared"; +import { type NodeRegistry, NodeType } from "./typings"; + +export const ConditionNodeRegistry: NodeRegistry = { + type: NodeType.Condition, + + extend: FlowNodeSplitType.DYNAMIC_SPLIT, + + meta: { + helpText: getI18n().t("workflow_node.condition.help"), + + icon: IconSitemap, + iconColor: "#fff", + iconBgColor: "#373c43", + + expandable: false, + }, + + formMeta: { + validateTrigger: ValidateTrigger.onChange, + render: () => { + return ; + }, + }, + + onAdd() { + const { t } = getI18n(); + + return { + id: nanoid(), + type: NodeType.Condition, + data: { + name: t("workflow_node.condition.default_name"), + }, + blocks: [ + { + id: nanoid(), + type: NodeType.BranchBlock, + blocks: [], + data: { + name: t("workflow_node.branch_block.default_name") + " 1", + }, + }, + { + id: nanoid(), + type: NodeType.BranchBlock, + data: { + name: t("workflow_node.branch_block.default_name") + " 2", + }, + }, + ], + }; + }, +}; + +export const BranchBlockNodeRegistry: NodeRegistry = { + type: NodeType.BranchBlock, + + extend: FlowNodeBaseType.BLOCK, + + meta: { + addDisable: true, + copyDisable: true, + }, + + formMeta: { + validateTrigger: ValidateTrigger.onChange, + render: ({ form }) => { + const fieldExpr = form.getValueIn("config.expression"); + + return ( + +
+
+ {fieldExpr ? : } +
+
{form.getValueIn("name") || "\u00A0"}
+
+
+ ); + }, + }, + + canAdd: () => { + return false; + }, + + canDelete: (_, node) => { + return node.parent!.blocks.length >= 2; + }, + + onAdd() { + const { t } = getI18n(); + + return { + id: nanoid(), + type: NodeType.BranchBlock, + data: { + name: t("workflow_node.branch_block.default_name"), + }, + }; + }, +}; diff --git a/ui/src/components/workflow/designer/nodes/EndNode.tsx b/ui/src/components/workflow/designer/nodes/EndNode.tsx new file mode 100644 index 000000000..c47b23755 --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/EndNode.tsx @@ -0,0 +1,75 @@ +import { getI18n } from "react-i18next"; +import { FlowNodeBaseType } from "@flowgram.ai/fixed-layout-editor"; +import { IconLogout } from "@tabler/icons-react"; +import { nanoid } from "nanoid"; + +import { BaseNode } from "./_shared"; +import { type NodeRegistry, NodeType } from "./typings"; + +export const EndNodeRegistry: NodeRegistry = { + type: NodeType.End, + + meta: { + helpText: getI18n().t("workflow_node.end.help"), + + icon: IconLogout, + iconColor: "#fff", + iconBgColor: "#336df4", + + isNodeEnd: true, + + expandable: false, + selectable: false, + + copyDisable: true, + }, + + formMeta: { + render: () => { + return ; + }, + }, + + canAdd(_, from) { + // You can only add to the last node of the branch + if (!from.isLast) return false; + + /** + * condition + * blockIcon + * inlineBlocks + * block1 + * blockOrderIcon + * <---- [add end] + * block2 + * blockOrderIcon + * end + */ + // originParent can determine whether it is condition , and then determine whether it is the last one + // https://github.com/bytedance/flowgram.ai/pull/146 + if (from.parent && from.parent.parent?.flowNodeType === FlowNodeBaseType.INLINE_BLOCKS && from.parent.originParent && !from.parent.originParent.isLast) { + const allBranches = from.parent.parent!.blocks; + // Determine whether the last node of all branch is end, All branches are not allowed to be end + const branchEndCount = allBranches.filter((block) => block.blocks[block.blocks.length - 1]?.getNodeMeta().isNodeEnd).length; + return branchEndCount < allBranches.length - 1; + } + + return true; + }, + + canDelete(ctx, node) { + return node.parent !== ctx.document.root; + }, + + onAdd() { + const { t } = getI18n(); + + return { + id: nanoid(), + type: NodeType.End, + data: { + name: t("workflow_node.end.default_name"), + }, + }; + }, +}; diff --git a/ui/src/components/workflow/designer/nodes/StartNode.tsx b/ui/src/components/workflow/designer/nodes/StartNode.tsx new file mode 100644 index 000000000..5b14291b7 --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/StartNode.tsx @@ -0,0 +1,67 @@ +import { getI18n } from "react-i18next"; +import { IconRocket } from "@tabler/icons-react"; +import { Typography } from "antd"; + +import { WORKFLOW_TRIGGERS } from "@/domain/workflow"; + +import { BaseNode } from "./_shared"; +import { type NodeRegistry, NodeType } from "./typings"; + +export const StartNodeRegistry: NodeRegistry = { + type: NodeType.Start, + + meta: { + helpText: getI18n().t("workflow_node.start.help"), + + icon: IconRocket, + iconColor: "#fff", + iconBgColor: "#ed6d0c", + + isStart: true, + + expandable: false, + selectable: false, + + addDisable: true, + copyDisable: true, + deleteDisable: true, + }, + + formMeta: { + render: ({ form }) => { + const { t } = getI18n(); + + const fieldTrigger = form.getValueIn("config.trigger"); + const fieldTriggerCron = form.getValueIn("config.triggerCron"); + + return ( + +
+ {fieldTrigger ? ( + <> +
+ {fieldTrigger === WORKFLOW_TRIGGERS.SCHEDULED + ? t("workflow.props.trigger.scheduled") + : fieldTrigger === WORKFLOW_TRIGGERS.MANUAL + ? t("workflow.props.trigger.manual") + : "\u00A0"} +
+
{fieldTrigger === WORKFLOW_TRIGGERS.SCHEDULED ? fieldTriggerCron || "\u00A0" : ""}
+ + ) : ( + t("workflow.detail.design.nodes.placeholder") + )} +
+
+ ); + }, + }, + + canAdd: () => { + return false; + }, + + canDelete: () => { + return false; + }, +}; diff --git a/ui/src/components/workflow/designer/nodes/TryCatchNode.tsx b/ui/src/components/workflow/designer/nodes/TryCatchNode.tsx new file mode 100644 index 000000000..c4b0d7add --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/TryCatchNode.tsx @@ -0,0 +1,105 @@ +import { getI18n } from "react-i18next"; +import { ValidateTrigger } from "@flowgram.ai/fixed-layout-editor"; +import { IconArrowsSplit, IconCircleX } from "@tabler/icons-react"; +import { nanoid } from "nanoid"; + +import { BaseNode, BlockNode } from "./_shared"; +import { type NodeRegistry, NodeType } from "./typings"; + +export const TryCatchNodeRegistry: NodeRegistry = { + type: NodeType.TryCatch, + + meta: { + helpText: getI18n().t("workflow_node.try_catch.help"), + + icon: IconArrowsSplit, + iconColor: "#fff", + iconBgColor: "#373c43", + + expandable: false, + }, + + formMeta: { + validateTrigger: ValidateTrigger.onChange, + render: () => { + return ; + }, + }, + + onAdd() { + const { t } = getI18n(); + + return { + id: nanoid(), + type: NodeType.TryCatch, + data: { + name: t("workflow_node.try_catch.default_name"), + }, + blocks: [ + { + id: nanoid(), + type: NodeType.TryBlock, + blocks: [], + }, + { + id: nanoid(), + type: NodeType.CatchBlock, + blocks: [ + { + id: nanoid(), + type: NodeType.End, + data: { + name: t("workflow_node.end.default_name"), + }, + }, + ], + data: { + name: t("workflow_node.catch_block.default_name"), + }, + }, + ], + }; + }, +}; + +export const CatchBlockNodeRegistry: NodeRegistry = { + type: NodeType.CatchBlock, + + meta: { + addDisable: true, + copyDisable: true, + }, + + formMeta: { + validateTrigger: ValidateTrigger.onChange, + render: ({ form }) => { + return ( + +
+
+ +
+
{form.getValueIn("name") || "\u00A0"}
+
+
+ ); + }, + }, + + canAdd: () => false, + + canDelete: (_, node) => node.parent != null && node.parent.blocks.length >= 2, + + onAdd() { + const { t } = getI18n(); + + return { + id: nanoid(), + type: NodeType.CatchBlock, + blocks: [], + data: { + name: t("workflow_node.catch_block.default_name"), + }, + }; + }, +}; diff --git a/ui/src/components/workflow/designer/nodes/_example.ts b/ui/src/components/workflow/designer/nodes/_example.ts new file mode 100644 index 000000000..cd6bc972f --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/_example.ts @@ -0,0 +1,63 @@ +import { type FlowNodeRegistry } from "@flowgram.ai/fixed-layout-editor"; +import { nanoid } from "nanoid"; + +/** + * 自定义节点注册 + */ +export const nodeRegistry: FlowNodeRegistry = { + /** + * 自定义节点类型 + */ + type: "condition", + + /** + * 自定义节点扩展: + * - loop: 扩展为循环节点 + * - start: 扩展为开始节点 + * - dynamicSplit: 扩展为分支节点 + * - end: 扩展为结束节点 + * - tryCatch: 扩展为 tryCatch 节点 + * - break: 分支断开 + * - default: 扩展为普通节点 (默认) + */ + extend: "dynamicSplit", + + /** + * 节点配置信息 + */ + meta: { + isStart: false, // 是否为开始节点 + isNodeEnd: false, // 是否为结束节点,结束节点后边无法再添加节点 + draggable: false, // 是否可拖拽,如开始节点和结束节点无法拖拽 + selectable: false, // 触发器等开始节点不能被框选 + deleteDisable: true, // 禁止删除 + copyDisable: true, // 禁止copy + addDisable: true, // 禁止添加 + }, + + onAdd() { + return { + id: `condition_${nanoid(5)}`, + type: "condition", + data: { + title: "Condition", + }, + blocks: [ + { + id: nanoid(5), + type: "block", + data: { + title: "If_0", + }, + }, + { + id: nanoid(5), + type: "block", + data: { + title: "If_1", + }, + }, + ], + }; + }, +}; diff --git a/ui/src/components/workflow/designer/nodes/_shared.tsx b/ui/src/components/workflow/designer/nodes/_shared.tsx new file mode 100644 index 000000000..a9bd61bdb --- /dev/null +++ b/ui/src/components/workflow/designer/nodes/_shared.tsx @@ -0,0 +1,161 @@ +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { useClientContext, useNodeRender } from "@flowgram.ai/fixed-layout-editor"; +import { IconCopy, IconDotsVertical, IconGripVertical, IconLabel, IconX } from "@tabler/icons-react"; +import { Button, type ButtonProps, Card, Dropdown, type MenuProps, Popover, Tooltip, Typography, theme } from "antd"; +import { mergeCls } from "@/utils/css"; + +import { type NodeRegistry } from "./typings"; + +export const BaseNode = ({ className, style, children }: { className?: string; style?: React.CSSProperties; children?: React.ReactNode }) => { + const { token: themeToken } = theme.useToken(); + + const ctx = useClientContext(); + const { playground } = ctx; + + const nodeRender = useNodeRender(); + const nodeRegistry = nodeRender.node.getNodeRegistry(); + + const NodeIcon = nodeRegistry.meta?.icon; + const renderNodeIcon = () => { + return NodeIcon == null ? null : ( +
+ +
+ ); + }; + + return ( +
+ +
+ {nodeRegistry.meta?.helpText == null ? ( + renderNodeIcon() + ) : ( + } mouseEnterDelay={1}> +
{renderNodeIcon()}
+
+ )} +
+
+ {nodeRender.data?.name || "\u00A0"} +
+ {children != null && ( +
+ {children} +
+ )} +
+
e.stopPropagation()}> + +
+
+
+ {!playground.config.readonlyOrDisabled && nodeRegistry.meta?.draggable === true && ( +
+ +
+ )} +
+ ); +}; + +export const BlockNode = ({ className, style, children }: { className?: string; style?: React.CSSProperties; children?: React.ReactNode }) => { + const ctx = useClientContext(); + const { playground } = ctx; + + const nodeRender = useNodeRender(); + const nodeRegistry = nodeRender.node.getNodeRegistry(); + + return ( + } placement="right"> +
+ +
+
{children ?? (nodeRender.data?.name || "\u00A0")}
+
+
+ {!playground.config.readonlyOrDisabled && nodeRegistry.meta?.draggable === true && ( +
+ +
+ )} +
+
+ ); +}; + +export const NodeMenuButton = ({ className, style, ...props }: ButtonProps) => { + const ctx = useClientContext(); + const { playground } = ctx; + + const menuItems = useNodeMenuItems(); + + return playground.config.readonlyOrDisabled ? null : ( + +
diff --git a/ui/src/pages/accesses/AccessList.tsx b/ui/src/pages/accesses/AccessList.tsx index 7468acb53..ffcfa9bf1 100644 --- a/ui/src/pages/accesses/AccessList.tsx +++ b/ui/src/pages/accesses/AccessList.tsx @@ -143,9 +143,9 @@ const AccessList = () => { renderCell(checked, _, index, node) { if (!checked) { return ( -
-
{(page - 1) * pageSize + index + 1}
-
{node}
+
+
{(page - 1) * pageSize + index + 1}
+
{node}
); } diff --git a/ui/src/pages/certificates/CertificateList.tsx b/ui/src/pages/certificates/CertificateList.tsx index 999a3bebc..334b99c85 100644 --- a/ui/src/pages/certificates/CertificateList.tsx +++ b/ui/src/pages/certificates/CertificateList.tsx @@ -190,9 +190,9 @@ const CertificateList = () => { renderCell(checked, _, index, node) { if (!checked) { return ( -
-
{(page - 1) * pageSize + index + 1}
-
{node}
+
+
{(page - 1) * pageSize + index + 1}
+
{node}
); } diff --git a/ui/src/pages/workflows/WorkflowDetail.tsx b/ui/src/pages/workflows/WorkflowDetail.tsx index b4be3d1ce..baccd46f9 100644 --- a/ui/src/pages/workflows/WorkflowDetail.tsx +++ b/ui/src/pages/workflows/WorkflowDetail.tsx @@ -3,10 +3,11 @@ import { useTranslation } from "react-i18next"; import { Outlet, useLocation, useNavigate, useParams } from "react-router-dom"; import { IconEdit, IconHistory, IconPlayerPlay, IconRobot } from "@tabler/icons-react"; import { useSize } from "ahooks"; -import { App, Button, Input, type InputRef, Segmented, Skeleton, Space } from "antd"; +import { App, Button, Input, type InputRef, Segmented, Skeleton } from "antd"; import { startRun as startWorkflowRun } from "@/api/workflows"; import Show from "@/components/Show"; +import { isAllNodesValidated } from "@/domain/workflow"; import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun"; import { useZustandShallowSelector } from "@/hooks"; import { useWorkflowStore } from "@/stores/workflow"; @@ -19,10 +20,10 @@ const WorkflowDetail = () => { const { t } = useTranslation(); - const { message, modal } = App.useApp(); + const { message, modal, notification } = App.useApp(); const { id: workflowId } = useParams(); - const { workflow, initialized, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "initialized", "init", "destroy"])); + const { workflow, initialized, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "initialized", "init", "destroy", "setEnabled"])); useEffect(() => { workflowState.init(workflowId!); @@ -35,6 +36,7 @@ const WorkflowDetail = () => { const divHeaderSize = useSize(divHeaderRef); const tabs = [ + ["design-legacy", "workflow.detail.design_legacy.tab", ], // TODO: 正式发布时移除 ["design", "workflow.detail.design.tab", ], ["runs", "workflow.detail.runs.tab", ], ] satisfies [string, string, React.ReactElement][]; @@ -90,6 +92,20 @@ const WorkflowDetail = () => { }); }; + const handleActiveClick = async () => { + try { + if (!workflow.enabled && (!workflow.content || !isAllNodesValidated(workflow.content))) { + message.warning(t("workflow.action.enable.errmsg.uncompleted")); + return; + } + + await workflowState.setEnabled(!workflow.enabled); + } catch (err) { + console.error(err); + notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + } + }; + return (
@@ -117,11 +133,14 @@ const WorkflowDetail = () => {
- - - + +
+ + +
+
@@ -152,7 +171,7 @@ const WorkflowDetailBaseName = () => { const { notification } = App.useApp(); - const { workflow, setBaseInfo: setWorkflowBaseInfo } = useWorkflowStore(useZustandShallowSelector(["workflow", "setBaseInfo"])); + const { workflow, initialized, setBaseInfo: setWorkflowBaseInfo } = useWorkflowStore(useZustandShallowSelector(["workflow", "initialized", "setBaseInfo"])); const inputRef = useRef(null); @@ -194,7 +213,7 @@ const WorkflowDetailBaseName = () => { }; return ( -
+

{ > {workflow.name || "\u00A0"}

- - , - onClick: handleRollbackClick, - }, - ], - }} - trigger={["click"]} - > - + , + onClick: handleRollbackClick, + }, + ], + }} + trigger={["click"]} + > +
+
- - -
+
+
+
+ +
+
+
+
); }; +const compactWorkflowDraft = (root: WorkflowNode | undefined) => { + const { t } = getI18n(); + + // TODO: 仅为兼容适配 v0.3.x 数据,正式上线后待删除 + const res: FlowDocumentJSON["nodes"] = []; + + if (!root) { + res.push({ + id: nanoid(), + type: "start", + data: { + name: "Start", + }, + }); + } else { + const convert = (node: WorkflowNode | undefined) => { + const temp: typeof res = []; + + let current: typeof node = node; + while (current) { + switch (current.type) { + case WorkflowNodeType.Start: + temp.push({ + id: current.id, + type: "start", + data: { + name: current.name, + config: current.config, + }, + }); + break; + + case WorkflowNodeType.Apply: + temp.push({ + id: current.id, + type: "bizApply", + data: { + name: current.name, + config: current.config, + }, + }); + break; + + case WorkflowNodeType.Upload: + temp.push({ + id: current.id, + type: "bizUpload", + data: { + name: current.name, + config: current.config, + }, + }); + break; + + case WorkflowNodeType.Monitor: + temp.push({ + id: current.id, + type: "bizMonitor", + data: { + name: current.name, + config: current.config, + }, + }); + break; + + case WorkflowNodeType.Deploy: + temp.push({ + id: current.id, + type: "bizDeploy", + data: { + name: current.name, + config: current.config, + }, + }); + break; + + case WorkflowNodeType.Notify: + temp.push({ + id: current.id, + type: "bizNotify", + data: { + name: current.name, + config: current.config, + }, + }); + break; + + case WorkflowNodeType.ExecuteResultBranch: { + const tryNode = temp.pop()!; + temp.push({ + id: current.id, + type: "tryCatch", + blocks: [ + { + id: current.branches?.find((b) => b.type === WorkflowNodeType.ExecuteSuccess)?.id || nanoid(), + type: "tryBlock", + blocks: [tryNode], + data: { + name: current.branches?.find((b) => b.type === WorkflowNodeType.ExecuteSuccess)?.name, + }, + }, + { + id: current.branches?.find((b) => b.type === WorkflowNodeType.ExecuteFailure)?.id || nanoid(), + type: "catchBlock", + blocks: [ + ...convert(current.branches?.find((b) => b.type === WorkflowNodeType.ExecuteFailure)?.next), + { + id: nanoid(), + type: "end", + data: { + name: t("workflow_node.end.default_name"), + }, + }, + ], + data: { + name: current.branches?.find((b) => b.type === WorkflowNodeType.ExecuteFailure)?.name, + }, + }, + ], + data: { + name: current.name, + config: current.config, + }, + }); + + current = current.branches?.find((b) => b.type === WorkflowNodeType.ExecuteSuccess); + break; + } + + case WorkflowNodeType.Branch: { + temp.push({ + id: current.id, + type: "condition", + blocks: + current.branches?.map((branch) => { + return { + id: branch.id, + type: "branchBlock", + blocks: convert(branch.next), + data: { + name: branch.name, + config: branch.config, + }, + }; + }) ?? [], + data: { + name: current.name, + config: current.config, + }, + }); + break; + } + } + + current = current?.next; + } + + return temp; + }; + + res.push(...convert(root)); + } + + res.push({ + id: nanoid(), + type: "end", + data: { + name: t("workflow_node.end.default_name"), + }, + }); + + return res; +}; + export default WorkflowDetailDesign; diff --git a/ui/src/pages/workflows/WorkflowDetailDesignLegacy.tsx b/ui/src/pages/workflows/WorkflowDetailDesignLegacy.tsx new file mode 100644 index 000000000..5cb26f08c --- /dev/null +++ b/ui/src/pages/workflows/WorkflowDetailDesignLegacy.tsx @@ -0,0 +1,132 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { IconArrowBackUp, IconDots } from "@tabler/icons-react"; +import { Alert, App, Button, Card, Dropdown, Space } from "antd"; +import { isEqual } from "radash"; + +import Show from "@/components/Show"; +import WorkflowElementsContainer from "@/components/workflow/WorkflowElementsContainer"; +import { isAllNodesValidated } from "@/domain/workflow"; +import { WORKFLOW_RUN_STATUSES } from "@/domain/workflowRun"; +import { useZustandShallowSelector } from "@/hooks"; +import { useWorkflowStore } from "@/stores/workflow"; +import { getErrMsg } from "@/utils/error"; + +const WorkflowDetailDesign = () => { + const { t } = useTranslation(); + + const { message, modal, notification } = App.useApp(); + + const { workflow, ...workflowState } = useWorkflowStore(useZustandShallowSelector(["workflow", "init", "publish", "rollback"])); + + const [isPendingOrRunning, setIsPendingOrRunning] = useState(false); + const [allowRollback, setAllowRollback] = useState(false); + const [allowPublish, setAllowPublish] = useState(false); + + useEffect(() => { + const pending = workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.PENDING || workflow.lastRunStatus === WORKFLOW_RUN_STATUSES.RUNNING; + setIsPendingOrRunning(pending); + }, [workflow]); + + useEffect(() => { + const hasContent = !!workflow.content; + const hasChanges = workflow.hasDraft! || !isEqual(workflow.draft, workflow.content); + setAllowRollback(!isPendingOrRunning && hasContent && hasChanges); + setAllowPublish(!isPendingOrRunning && hasChanges); + }, [workflow.content, workflow.draft, workflow.hasDraft, isPendingOrRunning]); + + const handleRollbackClick = () => { + modal.confirm({ + title: t("workflow.action.rollback.modal.title"), + content: t("workflow.action.rollback.modal.content"), + onOk: async () => { + try { + await workflowState.rollback(); + + message.success(t("common.text.operation_succeeded")); + } catch (err) { + console.error(err); + notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + } + }, + }); + }; + + const handlePublishClick = () => { + if (!isAllNodesValidated(workflow.draft!)) { + message.warning(t("workflow.action.publish.errmsg.uncompleted")); + return; + } + + modal.confirm({ + title: t("workflow.action.publish.modal.title"), + content: t("workflow.action.publish.modal.content"), + onOk: async () => { + try { + await workflowState.publish(); + + message.success(t("common.text.operation_succeeded")); + } catch (err) { + console.error(err); + notification.error({ message: t("common.text.request_error"), description: getErrMsg(err) }); + } + }, + }); + }; + + return ( +
+ +
+
+
+
+
+
+ + {t("workflow.detail.design.unpublished_draft.alert")}
} showIcon type="warning" /> + +
+ + + , + onClick: handleRollbackClick, + }, + ], + }} + trigger={["click"]} + > +
+
+
+
+ + +
+ +
+ ); +}; + +export default WorkflowDetailDesign; diff --git a/ui/src/pages/workflows/WorkflowDetailRuns.tsx b/ui/src/pages/workflows/WorkflowDetailRuns.tsx index f5950da80..1aa551ed0 100644 --- a/ui/src/pages/workflows/WorkflowDetailRuns.tsx +++ b/ui/src/pages/workflows/WorkflowDetailRuns.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { IconBrowserShare, IconHistory, IconPlayerPause, IconTrash } from "@tabler/icons-react"; +import { IconBrowserShare, IconDotsVertical, IconHistory, IconPlayerPause, IconTrash } from "@tabler/icons-react"; import { useRequest } from "ahooks"; -import { Alert, App, Button, Skeleton, Table, type TableProps, Tooltip } from "antd"; +import { Alert, App, Button, Dropdown, Skeleton, Table, type TableProps, Tooltip } from "antd"; import dayjs from "dayjs"; import { ClientResponseError } from "pocketbase"; @@ -98,53 +98,63 @@ const WorkflowDetailRuns = () => { key: "$action", align: "end", fixed: "right", - width: 120, + width: 64, render: (_, record) => { - const allowCancel = record.status === WORKFLOW_RUN_STATUSES.PENDING || record.status === WORKFLOW_RUN_STATUSES.RUNNING; - const aloowDelete = - record.status === WORKFLOW_RUN_STATUSES.SUCCEEDED || - record.status === WORKFLOW_RUN_STATUSES.FAILED || - record.status === WORKFLOW_RUN_STATUSES.CANCELED; + const cancelDisabled = !([WORKFLOW_RUN_STATUSES.PENDING, WORKFLOW_RUN_STATUSES.RUNNING] as string[]).includes(record.status); + const deleteDisabled = !cancelDisabled; return ( -
- -
+ + + + ), + onClick: () => { + handleRecordDetailClick(record); + }, + }, + { + key: "cancel", + label: t("workflow_run.action.cancel.button"), + icon: ( + + + + ), + disabled: cancelDisabled, + onClick: () => { + handleRecordCancelClick(record); + }, + }, + { + type: "divider", + }, + { + key: "delete", + label: t("workflow_run.action.delete.button"), + icon: ( + + + + ), + danger: true, + disabled: deleteDisabled, + onClick: () => { + handleRecordDeleteClick(record); + }, + }, + ], + }} + trigger={["click"]} + > +
); diff --git a/ui/src/components/workflow/designer/flowgram.css b/ui/src/components/workflow/designer/flowgram.css index 0e4ac1936..ee2cba5c5 100644 --- a/ui/src/components/workflow/designer/flowgram.css +++ b/ui/src/components/workflow/designer/flowgram.css @@ -13,6 +13,10 @@ position: absolute !important; top: -99999px !important; left: -99999px !important; + width: 0 !important; + height: 0 !important; + visibility: hidden !important; + transform: scale(0) !important; } /* 控制 flowgram 线条样式 */ diff --git a/ui/src/components/workflow/designer/nodes/BusinessNode.tsx b/ui/src/components/workflow/designer/nodes/BusinessNode.tsx index af9288615..60e3e58ae 100644 --- a/ui/src/components/workflow/designer/nodes/BusinessNode.tsx +++ b/ui/src/components/workflow/designer/nodes/BusinessNode.tsx @@ -1,6 +1,6 @@ import { getI18n } from "react-i18next"; import { IconCloudUpload, IconContract, IconDeviceDesktopSearch, IconPackage, IconSend } from "@tabler/icons-react"; -import { Avatar, Typography } from "antd"; +import { Avatar } from "antd"; import { nanoid } from "nanoid"; import { deploymentProvidersMap, notificationProvidersMap } from "@/domain/provider"; diff --git a/ui/src/components/workflow/designer/nodes/StartNode.tsx b/ui/src/components/workflow/designer/nodes/StartNode.tsx index 5b14291b7..60104cc26 100644 --- a/ui/src/components/workflow/designer/nodes/StartNode.tsx +++ b/ui/src/components/workflow/designer/nodes/StartNode.tsx @@ -1,6 +1,5 @@ import { getI18n } from "react-i18next"; import { IconRocket } from "@tabler/icons-react"; -import { Typography } from "antd"; import { WORKFLOW_TRIGGERS } from "@/domain/workflow"; diff --git a/ui/src/components/workflow/designer/nodes/_shared.tsx b/ui/src/components/workflow/designer/nodes/_shared.tsx index a9bd61bdb..0e579a0b7 100644 --- a/ui/src/components/workflow/designer/nodes/_shared.tsx +++ b/ui/src/components/workflow/designer/nodes/_shared.tsx @@ -99,7 +99,7 @@ export const NodeMenuButton = ({ className, style, ...props }: ButtonProps) => { return playground.config.readonlyOrDisabled ? null : ( -
); diff --git a/ui/src/components/workflow/designer/components/TryCatchCollapse.tsx b/ui/src/components/workflow/designer/components/TryCatchCollapse.tsx index 77f68d38a..5e5422f6d 100644 --- a/ui/src/components/workflow/designer/components/TryCatchCollapse.tsx +++ b/ui/src/components/workflow/designer/components/TryCatchCollapse.tsx @@ -60,8 +60,6 @@ const TryCatchCollapse = ({ node, ...props }: TryCatchCollapseProps) => { > {node.getService(FlowRendererRegistry).getText(FlowTextKey.CATCH_TEXT)}
- - {/* {renderCollapse()} */}
); }; diff --git a/ui/src/components/workflow/designer/nodes/ConditionNode.tsx b/ui/src/components/workflow/designer/nodes/ConditionNode.tsx index 71f106c9d..15d1b2c35 100644 --- a/ui/src/components/workflow/designer/nodes/ConditionNode.tsx +++ b/ui/src/components/workflow/designer/nodes/ConditionNode.tsx @@ -91,7 +91,7 @@ export const BranchBlockNodeRegistry: NodeRegistry = { }, canDelete: (_, node) => { - return node.parent!.blocks.length >= 2; + return node.parent != null && node.parent.blocks.length >= 2; }, onAdd() { diff --git a/ui/src/components/workflow/designer/nodes/TryCatchNode.tsx b/ui/src/components/workflow/designer/nodes/TryCatchNode.tsx index c4b0d7add..1c0ec2eb7 100644 --- a/ui/src/components/workflow/designer/nodes/TryCatchNode.tsx +++ b/ui/src/components/workflow/designer/nodes/TryCatchNode.tsx @@ -66,6 +66,8 @@ export const CatchBlockNodeRegistry: NodeRegistry = { type: NodeType.CatchBlock, meta: { + draggable: false, + addDisable: true, copyDisable: true, }, @@ -88,7 +90,9 @@ export const CatchBlockNodeRegistry: NodeRegistry = { canAdd: () => false, - canDelete: (_, node) => node.parent != null && node.parent.blocks.length >= 2, + canDelete: (_, node) => { + return node.parent != null && node.parent.blocks.length >= 2; + }, onAdd() { const { t } = getI18n(); @@ -96,7 +100,15 @@ export const CatchBlockNodeRegistry: NodeRegistry = { return { id: nanoid(), type: NodeType.CatchBlock, - blocks: [], + blocks: [ + { + id: nanoid(), + type: NodeType.End, + data: { + name: t("workflow_node.end.default_name"), + }, + }, + ], data: { name: t("workflow_node.catch_block.default_name"), }, diff --git a/ui/src/components/workflow/designer/nodes/_example.ts b/ui/src/components/workflow/designer/nodes/_example.ts index cd6bc972f..be87a0e12 100644 --- a/ui/src/components/workflow/designer/nodes/_example.ts +++ b/ui/src/components/workflow/designer/nodes/_example.ts @@ -8,7 +8,7 @@ export const nodeRegistry: FlowNodeRegistry = { /** * 自定义节点类型 */ - type: "condition", + type: "ifElse", /** * 自定义节点扩展: @@ -31,30 +31,30 @@ export const nodeRegistry: FlowNodeRegistry = { draggable: false, // 是否可拖拽,如开始节点和结束节点无法拖拽 selectable: false, // 触发器等开始节点不能被框选 deleteDisable: true, // 禁止删除 - copyDisable: true, // 禁止copy + copyDisable: true, // 禁止克隆 addDisable: true, // 禁止添加 }, onAdd() { return { - id: `condition_${nanoid(5)}`, - type: "condition", + id: `IfElse_${nanoid(5)}`, + type: "ifElse", data: { - title: "Condition", + title: "IfElse", }, blocks: [ { id: nanoid(5), type: "block", data: { - title: "If_0", + title: "If", }, }, { id: nanoid(5), type: "block", data: { - title: "If_1", + title: "Else", }, }, ], diff --git a/ui/src/components/workflow/designer/nodes/_shared.tsx b/ui/src/components/workflow/designer/nodes/_shared.tsx index 0e579a0b7..ff2c50552 100644 --- a/ui/src/components/workflow/designer/nodes/_shared.tsx +++ b/ui/src/components/workflow/designer/nodes/_shared.tsx @@ -1,11 +1,12 @@ -import { useMemo } from "react"; +import { useContext, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useClientContext, useNodeRender } from "@flowgram.ai/fixed-layout-editor"; +import { useClientContext } from "@flowgram.ai/fixed-layout-editor"; import { IconCopy, IconDotsVertical, IconGripVertical, IconLabel, IconX } from "@tabler/icons-react"; -import { Button, type ButtonProps, Card, Dropdown, type MenuProps, Popover, Tooltip, Typography, theme } from "antd"; +import { Button, type ButtonProps, Card, Dropdown, Popover, Tooltip, Typography, theme } from "antd"; import { mergeCls } from "@/utils/css"; import { type NodeRegistry } from "./typings"; +import { NodeRenderContext } from "../NodeRenderContext"; export const BaseNode = ({ className, style, children }: { className?: string; style?: React.CSSProperties; children?: React.ReactNode }) => { const { token: themeToken } = theme.useToken(); @@ -13,7 +14,7 @@ export const BaseNode = ({ className, style, children }: { className?: string; s const ctx = useClientContext(); const { playground } = ctx; - const nodeRender = useNodeRender(); + const nodeRender = useContext(NodeRenderContext); const nodeRegistry = nodeRender.node.getNodeRegistry(); const NodeIcon = nodeRegistry.meta?.icon; @@ -70,7 +71,7 @@ export const BlockNode = ({ className, style, children }: { className?: string; const ctx = useClientContext(); const { playground } = ctx; - const nodeRender = useNodeRender(); + const nodeRender = useContext(NodeRenderContext); const nodeRegistry = nodeRender.node.getNodeRegistry(); return ( @@ -92,70 +93,72 @@ export const BlockNode = ({ className, style, children }: { className?: string; }; export const NodeMenuButton = ({ className, style, ...props }: ButtonProps) => { + const { t } = useTranslation(); + const ctx = useClientContext(); const { playground } = ctx; - const menuItems = useNodeMenuItems(); + const { node, deleteNode, isBlockIcon, isBlockOrderIcon } = useContext(NodeRenderContext); + const nodeRegistry = node.getNodeRegistry(); + + const getLatestNodeDeleteDisabledState = () => { + if (nodeRegistry.canDelete != null) { + return !nodeRegistry.canDelete(ctx, node); + } + return !!nodeRegistry.meta?.deleteDisable; + }; + const [nodeDeleteDisabled, setNodeDeleteDisabled] = useState(() => getLatestNodeDeleteDisabledState()); + useEffect(() => { + // 这里不能使用 useMemo() 来决定 nodeDeleteDisabled,因为依赖项没有发生改变(对象引用始终是同一个) + // 因此需要使用 useEffect() 来监听 node 和 node.parent 的变化,并更新 nodeDeleteDisabled 的状态 + const disposable1 = node.onEntityChange(() => setNodeDeleteDisabled(getLatestNodeDeleteDisabledState())); + const disposable2 = node.parent?.onEntityChange(() => setNodeDeleteDisabled(getLatestNodeDeleteDisabledState())); + return () => { + disposable1?.dispose(); + disposable2?.dispose(); + }; + }, []); return playground.config.readonlyOrDisabled ? null : ( - + , + onClick: () => { + alert("TODO: rename"); + }, + }, + { + key: "duplicate", + label: isBlockIcon || isBlockOrderIcon ? t("workflow.detail.design.nodes.duplicate_branch") : t("workflow.detail.design.nodes.duplicate_node"), + icon: , + onClick: () => { + alert("TODO: duplicate"); + }, + }, + { + type: "divider", + }, + { + key: "remove", + label: isBlockIcon || isBlockOrderIcon ? t("workflow.detail.design.nodes.remove_branch") : t("workflow.detail.design.nodes.remove_node"), + icon: , + danger: true, + disabled: nodeDeleteDisabled, + onClick: () => { + deleteNode(); + }, + }, + ], + }} + trigger={["click"]} + > - + ({ + key: `${zoom}%`, + label: `${zoom}%`, + onClick: () => tools.updateZoom(zoom / 100), + })), + { + type: "divider", + }, + { + key: "auto", + label: t("workflow.detail.design.toolbar.auto_fit"), + onClick: () => tools.fitView(), + }, + ], + }} + trigger={["click"]} + > + +