@oomol-lab/open-flow 0.1.0-beta.79 → 0.1.0-beta.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/{FlowCanvas-BQGBcfoL.js → FlowCanvas-CrsiDHK8.js} +50 -51
- package/dist/browser/{createResourceDialog-DkovGogv.js → createResourceDialog-CS3u9ga9.js} +2 -2
- package/dist/browser/{dialog-CFFuRM8s.js → dialog-CFlvkliO.js} +7 -2
- package/dist/browser/{exportSvg-CYwyYiuH.js → exportPng-BOPfjeEi.js} +236 -38
- package/dist/browser/{flowChanges-CN2R-m1M.js → flowChanges-pFjPHU5k.js} +326 -203
- package/dist/browser/{flowWorkspace-CXVL1FNv.js → flowWorkspace-CRlqc403.js} +698 -480
- package/dist/browser/preview.css +153 -132
- package/dist/browser/preview.js +921 -643
- package/dist/browser/ui-logo.d.ts +1 -0
- package/dist/browser/ui.css +29 -4
- package/dist/browser/ui.js +4 -1
- package/dist/browser/workbench.css +153 -132
- package/dist/browser/workbench.js +9 -9
- package/dist/browser/{workbenchSelect-CkOS95gX.js → workbenchSelect-Ci0KCYv9.js} +1 -1
- package/dist/command/index.js +41 -41
- package/dist/common/control-api-conformance.js +49 -19
- package/dist/common/control-api.d.ts +26 -6
- package/dist/common/control-api.js +49 -19
- package/dist/common/control-requests.js +6 -2
- package/dist/common/flow-authoring.js +6 -2
- package/dist/common/flow-change.js +6 -2
- package/dist/common/flow-encoding.js +6 -2
- package/dist/common/flow-semantics.js +6 -2
- package/dist/common/flow-state.d.ts +47 -0
- package/dist/common/flow-state.js +7 -1
- package/dist/common/mcp.js +49 -18
- package/dist/common/scheduler.js +6 -2
- package/package.json +1 -1
- package/src/control/common/api.ts +24 -8
- package/src/control/common/connectorDecoders.ts +11 -30
- package/src/control/common/revisionDecoders.ts +14 -0
- package/src/state/common/state.ts +14 -0
|
@@ -3188,7 +3188,7 @@ var CanvasStore = class {
|
|
|
3188
3188
|
if (this.$.editable.value) this.#callbacks.onIgnoreNodes?.(nodeIds, ignored);
|
|
3189
3189
|
};
|
|
3190
3190
|
this.changeDirection = (direction) => {
|
|
3191
|
-
if (!this
|
|
3191
|
+
if (!this.canChangeDirection || this.$.changingDirection.value || this.$.direction.value === direction) return;
|
|
3192
3192
|
this.layoutComplete = false;
|
|
3193
3193
|
this.layoutMeasurementAttempts = 0;
|
|
3194
3194
|
this.$$.changingDirection.set(true);
|
|
@@ -18784,31 +18784,31 @@ function IconStack({ icons, count, limit = 3, size = "default", countClassName }
|
|
|
18784
18784
|
});
|
|
18785
18785
|
}
|
|
18786
18786
|
var CanvasCard_module_default = {
|
|
18787
|
-
card: "
|
|
18788
|
-
value: "
|
|
18789
|
-
preview: "
|
|
18790
|
-
comment: "
|
|
18791
|
-
selected: "
|
|
18792
|
-
problem: "
|
|
18793
|
-
runningBeam: "
|
|
18794
|
-
"running-beam": "CanvasCard_running-
|
|
18795
|
-
header: "
|
|
18796
|
-
icon: "
|
|
18797
|
-
identity: "
|
|
18798
|
-
indicator: "
|
|
18799
|
-
warning: "
|
|
18800
|
-
danger: "
|
|
18801
|
-
content: "
|
|
18802
|
-
branches: "
|
|
18803
|
-
collapse: "
|
|
18804
|
-
collapseInner: "
|
|
18805
|
-
compactIdentity: "
|
|
18806
|
-
compact: "
|
|
18807
|
-
withBody: "
|
|
18808
|
-
compactContent: "
|
|
18809
|
-
singleContent: "
|
|
18810
|
-
subtitle: "
|
|
18811
|
-
fill: "
|
|
18787
|
+
card: "CanvasCard_card_e92f9",
|
|
18788
|
+
value: "CanvasCard_value_e92f9",
|
|
18789
|
+
preview: "CanvasCard_preview_e92f9",
|
|
18790
|
+
comment: "CanvasCard_comment_e92f9",
|
|
18791
|
+
selected: "CanvasCard_selected_e92f9",
|
|
18792
|
+
problem: "CanvasCard_problem_e92f9",
|
|
18793
|
+
runningBeam: "CanvasCard_runningBeam_e92f9",
|
|
18794
|
+
"running-beam": "CanvasCard_running-beam_e92f9",
|
|
18795
|
+
header: "CanvasCard_header_e92f9",
|
|
18796
|
+
icon: "CanvasCard_icon_e92f9",
|
|
18797
|
+
identity: "CanvasCard_identity_e92f9",
|
|
18798
|
+
indicator: "CanvasCard_indicator_e92f9",
|
|
18799
|
+
warning: "CanvasCard_warning_e92f9",
|
|
18800
|
+
danger: "CanvasCard_danger_e92f9",
|
|
18801
|
+
content: "CanvasCard_content_e92f9",
|
|
18802
|
+
branches: "CanvasCard_branches_e92f9",
|
|
18803
|
+
collapse: "CanvasCard_collapse_e92f9",
|
|
18804
|
+
collapseInner: "CanvasCard_collapseInner_e92f9",
|
|
18805
|
+
compactIdentity: "CanvasCard_compactIdentity_e92f9",
|
|
18806
|
+
compact: "CanvasCard_compact_e92f9",
|
|
18807
|
+
withBody: "CanvasCard_withBody_e92f9",
|
|
18808
|
+
compactContent: "CanvasCard_compactContent_e92f9",
|
|
18809
|
+
singleContent: "CanvasCard_singleContent_e92f9",
|
|
18810
|
+
subtitle: "CanvasCard_subtitle_e92f9",
|
|
18811
|
+
fill: "CanvasCard_fill_e92f9"
|
|
18812
18812
|
};
|
|
18813
18813
|
//#endregion
|
|
18814
18814
|
//#region src/ui/browser/hooks.ts
|
|
@@ -21375,6 +21375,7 @@ var NodeLayout = /* @__PURE__ */ memo(({ canvasStore, nodeStore, visible }) => {
|
|
|
21375
21375
|
modelNode?.kind === "trigger" && /* @__PURE__ */ jsx(Zap, {
|
|
21376
21376
|
"aria-hidden": "true",
|
|
21377
21377
|
className: NodeLayout_module_default.triggerIcon,
|
|
21378
|
+
"data-canvas-bounds": true,
|
|
21378
21379
|
strokeWidth: 1.5
|
|
21379
21380
|
}),
|
|
21380
21381
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -21594,27 +21595,27 @@ var fitViewOptions = {
|
|
|
21594
21595
|
maxZoom: 1
|
|
21595
21596
|
};
|
|
21596
21597
|
var ReactFlowContainer_module_default = {
|
|
21597
|
-
container: "
|
|
21598
|
-
flow: "
|
|
21599
|
-
contextMenu: "
|
|
21600
|
-
movingLayout: "
|
|
21601
|
-
layoutPending: "
|
|
21602
|
-
dock: "
|
|
21603
|
-
viewDock: "
|
|
21604
|
-
expandedViewControls: "
|
|
21605
|
-
viewOptionButtons: "
|
|
21606
|
-
compactViewControls: "
|
|
21607
|
-
createDock: "
|
|
21608
|
-
zoomValue: "
|
|
21609
|
-
viewChoiceTrigger: "
|
|
21610
|
-
viewChoicePanel: "
|
|
21611
|
-
viewChoices: "
|
|
21612
|
-
viewChoice: "
|
|
21613
|
-
dockActions: "
|
|
21614
|
-
miniMap: "
|
|
21615
|
-
viewOptions: "
|
|
21616
|
-
viewOptionsClip: "
|
|
21617
|
-
viewSeparator: "
|
|
21598
|
+
container: "ReactFlowContainer_container_18f86",
|
|
21599
|
+
flow: "ReactFlowContainer_flow_18f86",
|
|
21600
|
+
contextMenu: "ReactFlowContainer_contextMenu_18f86",
|
|
21601
|
+
movingLayout: "ReactFlowContainer_movingLayout_18f86",
|
|
21602
|
+
layoutPending: "ReactFlowContainer_layoutPending_18f86",
|
|
21603
|
+
dock: "ReactFlowContainer_dock_18f86",
|
|
21604
|
+
viewDock: "ReactFlowContainer_viewDock_18f86",
|
|
21605
|
+
expandedViewControls: "ReactFlowContainer_expandedViewControls_18f86",
|
|
21606
|
+
viewOptionButtons: "ReactFlowContainer_viewOptionButtons_18f86",
|
|
21607
|
+
compactViewControls: "ReactFlowContainer_compactViewControls_18f86",
|
|
21608
|
+
createDock: "ReactFlowContainer_createDock_18f86",
|
|
21609
|
+
zoomValue: "ReactFlowContainer_zoomValue_18f86",
|
|
21610
|
+
viewChoiceTrigger: "ReactFlowContainer_viewChoiceTrigger_18f86",
|
|
21611
|
+
viewChoicePanel: "ReactFlowContainer_viewChoicePanel_18f86",
|
|
21612
|
+
viewChoices: "ReactFlowContainer_viewChoices_18f86",
|
|
21613
|
+
viewChoice: "ReactFlowContainer_viewChoice_18f86",
|
|
21614
|
+
dockActions: "ReactFlowContainer_dockActions_18f86",
|
|
21615
|
+
miniMap: "ReactFlowContainer_miniMap_18f86",
|
|
21616
|
+
viewOptions: "ReactFlowContainer_viewOptions_18f86",
|
|
21617
|
+
viewOptionsClip: "ReactFlowContainer_viewOptionsClip_18f86",
|
|
21618
|
+
viewSeparator: "ReactFlowContainer_viewSeparator_18f86"
|
|
21618
21619
|
};
|
|
21619
21620
|
//#endregion
|
|
21620
21621
|
//#region src/canvas/browser/addItemDrag.ts
|
|
@@ -22491,14 +22492,13 @@ function CanvasOptionsMenu({ onRelayout, miniMapExpanded$, interactiveMode$, dir
|
|
|
22491
22492
|
interactiveMode$ != null && /* @__PURE__ */ jsx(CanvasInteractiveModeMenu, { interactiveMode$ })
|
|
22492
22493
|
] });
|
|
22493
22494
|
}
|
|
22494
|
-
function CanvasViewControls({
|
|
22495
|
+
function CanvasViewControls({ direction, changingDirection, onChangeDirection, interactiveMode$, miniMapExpanded$, maxZoomReached, minZoomReached, onFitView, onRelayout, onZoomIn, onZoomOut, onZoomReset, zoom }) {
|
|
22495
22496
|
const t = useTranslate();
|
|
22496
22497
|
const [optionsExpanded, setOptionsExpanded] = useState(false);
|
|
22497
22498
|
const [popupContainer, setPopupContainer] = useState(null);
|
|
22498
22499
|
return /* @__PURE__ */ jsxs(Panel, {
|
|
22499
22500
|
position: "bottom-left",
|
|
22500
22501
|
className: cn$1("open-flow-control-island open-flow-control-island-compact", ReactFlowContainer_module_default.dock, ReactFlowContainer_module_default.viewDock),
|
|
22501
|
-
"data-readonly": readOnly,
|
|
22502
22502
|
"data-canvas-control-scope": true,
|
|
22503
22503
|
"data-tooltip-toolbar": true,
|
|
22504
22504
|
ref: setPopupContainer,
|
|
@@ -23034,7 +23034,6 @@ var FlowControls = /*#__PURE__*/ memo((props) => {
|
|
|
23034
23034
|
variant: BackgroundVariant.Dots
|
|
23035
23035
|
}),
|
|
23036
23036
|
/* @__PURE__ */ jsx(CanvasViewControls, {
|
|
23037
|
-
readOnly: !props.editable,
|
|
23038
23037
|
interactiveMode$: props.interactiveMode$,
|
|
23039
23038
|
direction: props.direction,
|
|
23040
23039
|
changingDirection: props.changingDirection,
|
|
@@ -23750,7 +23749,7 @@ var FlowCanvas = ({ flowCanvasStore, annotationContext, onActivateSelection, onS
|
|
|
23750
23749
|
editable,
|
|
23751
23750
|
direction,
|
|
23752
23751
|
changingDirection,
|
|
23753
|
-
onChangeDirection:
|
|
23752
|
+
onChangeDirection: flowCanvasStore.canChangeDirection ? flowCanvasStore.changeDirection : void 0,
|
|
23754
23753
|
nodesDraggable: nodesDraggable ?? editable,
|
|
23755
23754
|
className,
|
|
23756
23755
|
i18n: flowCanvasStore.i18n,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { l as useTranslate } from "./dist-BFmpkX4P.js";
|
|
2
2
|
import { xr as Button } from "./tooltip-BDcVTNpu.js";
|
|
3
|
-
import { a as DialogFooter, d as Field, f as FieldDescription, h as FieldLabel, i as DialogDescription, l as Icon, m as FieldGroup, n as DialogClose, o as DialogHeader, p as FieldError, r as DialogContent,
|
|
4
|
-
import { t as WorkbenchSelect } from "./workbenchSelect-
|
|
3
|
+
import { a as DialogFooter, d as Field, f as FieldDescription, h as FieldLabel, i as DialogDescription, in as Input, l as Icon, m as FieldGroup, n as DialogClose, o as DialogHeader, p as FieldError, r as DialogContent, s as DialogTitle, t as Dialog, u as Spinner } from "./dialog-CFlvkliO.js";
|
|
4
|
+
import { t as WorkbenchSelect } from "./workbenchSelect-Ci0KCYv9.js";
|
|
5
5
|
import { useEffect, useRef, useState } from "react";
|
|
6
6
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
//#region src/workbench/browser/runtime/shell/createResourceDialog.tsx
|
|
@@ -2286,7 +2286,7 @@ var keySchema = string().min(1).refine((value) => new TextEncoder().encode(value
|
|
|
2286
2286
|
strictObject({
|
|
2287
2287
|
scope: _enum(["draft", "live"]),
|
|
2288
2288
|
values: literal(false).optional()
|
|
2289
|
-
});
|
|
2289
|
+
}).omit({ values: true });
|
|
2290
2290
|
strictObject({
|
|
2291
2291
|
scope: _enum(["draft", "live"]),
|
|
2292
2292
|
keys: array(keySchema).min(1).max(stateReadLimits.maxKeys).refine((keys) => new Set(keys).size === keys.length, "State keys must be distinct.")
|
|
@@ -2299,6 +2299,7 @@ strictObject({
|
|
|
2299
2299
|
scope: _enum(["draft", "live"]),
|
|
2300
2300
|
space: string().regex(/^(flow|node:.+)$/)
|
|
2301
2301
|
});
|
|
2302
|
+
strictObject({ scope: _enum(["draft", "live"]) });
|
|
2302
2303
|
var skipDuplicatesConfigSchema = strictObject({ fields: array(string().refine((field) => field.length > 0 && field.split(".").every((part) => part.length > 0), "Use a nonempty dot-separated field path.")).refine((fields) => new Set(fields).size === fields.length, "Identity fields must be distinct.").default([]) });
|
|
2303
2304
|
var skipDuplicatesExecutorSchema = skipDuplicatesConfigSchema.extend({ kind: literal("skip-duplicates") });
|
|
2304
2305
|
function skipDuplicatesTask(config = skipDuplicatesConfigSchema.parse({}), name = "Skip Duplicates") {
|
|
@@ -2343,6 +2344,10 @@ var flowStateViewSchema = object({
|
|
|
2343
2344
|
updatedAt: number()
|
|
2344
2345
|
}))
|
|
2345
2346
|
});
|
|
2347
|
+
var flowStateSummarySchema = flowStateViewSchema.extend({ values: strictObject({
|
|
2348
|
+
count: number().int().nonnegative(),
|
|
2349
|
+
updatedAt: number().nullable()
|
|
2350
|
+
}) });
|
|
2346
2351
|
var flowStateValuesSchema = object({
|
|
2347
2352
|
version: literal(1),
|
|
2348
2353
|
scope: _enum(["draft", "live"]),
|
|
@@ -9360,4 +9365,4 @@ function DialogDescription({ className, ...props }) {
|
|
|
9360
9365
|
});
|
|
9361
9366
|
}
|
|
9362
9367
|
//#endregion
|
|
9363
|
-
export { waitAutoContinueSchema as $,
|
|
9368
|
+
export { waitAutoContinueSchema as $, defaultItemEquality as $t, comparisonIssue as A, variableInputCompatible as At, authoringDefinitionSchemas as B, SelectChevron as Bt, upgradeManualTrigger as C, limitDecisionQuestions as Ct, nodeSystemOutputPorts as D, matchesSchema as Dt, nodeErrorSchema as E, comparePorts as Et, operatorsForType as F, flowStateViewSchema as Ft, workflowAgentToolSchema as G, SelectTrigger as Gt, connectorCapabilitySchema as H, SelectGroup as Ht, otherwiseOutput as I, skipDuplicatesConfigSchema as It, webhookResponseAllowsBody as J, hasNullItemLabel as Jt, webhookHeaderValuePort as K, SelectValue as Kt, unaryOperator as L, skipDuplicatesTask as Lt, mapConditionSources as M, format as Mt, nodeInputMappings as N, flowStateSummarySchema as Nt, supportsNodeErrorHandling as O, portsAssignable as Ot, operandHandle as P, flowStateValuesSchema as Pt, workflowOutputsPort as Q, compareItemEquality as Qt, valueType as R, dequal as Rt, resourceNameIssue as S, defaultDecisionQuestion as St, workflowToolNameIssue as T, webhookSupportsBody as Tt, decodeRevisionContent as U, SelectItem as Ut, connectorAgentToolSchema as V, SelectContent as Vt, httpCapabilitySchema as W, SelectSeparator as Wt, webhookResponseTimeout as X, stringifyAsLabel as Xt, webhookResponseInputs as Y, isGroupedItems as Yt, webhookStatusCodePort as Z, stringifyAsValue as Zt, applyFlowChanges as _, isHttpHost as _t, DialogFooter as a, ChevronDown as an, httpHeaderSchema as at, nodeNameIssue as b, decisionQuestionIssues as bt, DialogTrigger as c, httpRequestInputs as ct, Field as d, withHttpParameterFields as dt, findItemIndex as en, defaultNodeRetry as et, FieldDescription as f, withHttpParameterSource as ft, FlowChangeError as g, renamedPort as gt, FieldLabel as h, renameOutputMapping as ht, DialogDescription as i, Input as in, checkJsonDepth as it, conditionInputPorts as j, isSchemaKeyword as jt, clearConditionBindings as k, schemaObject as kt, Icon as l, httpRequestIssues as lt, FieldGroup as m, remapInputMappings as mt, DialogClose as n, selectedValueIncludes as nn, retryConfigurationSchema as nt, DialogHeader as o, httpQuerySchema as ot, FieldError as p, httpHeaderNameIssue as pt, webhookHeadersPort as q, resolveAriaLabelledBy as qt, DialogContent as r, Label as rn, supportsNodeRetry as rt, DialogTitle as s, httpRequestConfigSchema as st, Dialog as t, removeItem as tn, nodeRetryLimits as tt, Spinner as u, httpRequestTask as ut, isTriggerNode as v, decisionLimits as vt, upgradeRevisionContent as w, webhookMethods as wt, normalizeNodeName as x, decisionTask as xt, nextNodeName as y, decisionModel as yt, agentToolFields as z, Select as zt };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { n as IconImageCrossOriginContext, o as IconifyProvider } from "./ContentIcon-CMn0c-kl.js";
|
|
2
|
-
import { t as FlowCanvas, x as CanvasStore } from "./FlowCanvas-
|
|
2
|
+
import { t as FlowCanvas, x as CanvasStore } from "./FlowCanvas-CrsiDHK8.js";
|
|
3
3
|
import { createElement } from "react";
|
|
4
|
+
import "react/jsx-runtime";
|
|
4
5
|
import { createRoot } from "react-dom/client";
|
|
5
6
|
//#region ../../node_modules/.bun/html-to-image@1.11.11/node_modules/html-to-image/es/util.js
|
|
6
7
|
function resolveUrl(url, baseUrl) {
|
|
@@ -547,30 +548,188 @@ async function toSvg(node, options = {}) {
|
|
|
547
548
|
return await nodeToDataURL(clonedNode, width, height);
|
|
548
549
|
}
|
|
549
550
|
//#endregion
|
|
550
|
-
//#region
|
|
551
|
+
//#region ../../assets/logo/open-flow-dark.svg
|
|
552
|
+
var open_flow_dark_default = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='100'%20height='100'%20viewBox='0%200%20100%20100'%20role='img'%20aria-label='Open%20Flow'%3e%3cpath%20fill='%23e0e2e6'%20d='M32%2016H68Q84%2016%2084%2032V36H68C46%2036%2054%2056%2032%2056H16V32Q16%2016%2032%2016Z'/%3e%3cpath%20fill='%238c919a'%20d='M16%2068H32C54%2068%2046%2048%2068%2048H84V68Q84%2084%2068%2084H32Q16%2084%2016%2068Z'/%3e%3c/svg%3e";
|
|
553
|
+
//#endregion
|
|
554
|
+
//#region ../../assets/logo/open-flow-light.svg
|
|
555
|
+
var open_flow_light_default = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='100'%20height='100'%20viewBox='0%200%20100%20100'%20role='img'%20aria-label='Open%20Flow'%3e%3cpath%20fill='%2344474c'%20d='M32%2016H68Q84%2016%2084%2032V36H68C46%2036%2054%2056%2032%2056H16V32Q16%2016%2032%2016Z'/%3e%3cpath%20fill='%2393979f'%20d='M16%2068H32C54%2068%2046%2048%2068%2048H84V68Q84%2084%2068%2084H32Q16%2084%2016%2068Z'/%3e%3c/svg%3e";
|
|
556
|
+
//#endregion
|
|
557
|
+
//#region src/ui/browser/logo.tsx
|
|
558
|
+
function openFlowLogoSource(theme) {
|
|
559
|
+
return theme === "dark" ? open_flow_dark_default : open_flow_light_default;
|
|
560
|
+
}
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/canvas/browser/graph/FlowCanvas/exportPng.ts
|
|
551
563
|
var padding = 64;
|
|
564
|
+
var maximumDimension = 16384;
|
|
565
|
+
var maximumPixels = 32e6;
|
|
566
|
+
var framePadding = 12;
|
|
567
|
+
var frameHeaderHeight = 40;
|
|
568
|
+
var frameTitleInset = 20;
|
|
569
|
+
var frameInset = 8;
|
|
570
|
+
var frameRadius = 16;
|
|
571
|
+
function drawFrame(context, image, logo, width, height, background, frame) {
|
|
572
|
+
const x = framePadding;
|
|
573
|
+
const y = framePadding;
|
|
574
|
+
const frameWidth = width - 24;
|
|
575
|
+
const frameHeight = height - 24;
|
|
576
|
+
const contentX = 20;
|
|
577
|
+
const contentY = 52;
|
|
578
|
+
const contentWidth = frameWidth - 16;
|
|
579
|
+
const contentHeight = frameHeight - frameHeaderHeight - frameInset;
|
|
580
|
+
context.save();
|
|
581
|
+
context.fillStyle = frame.background;
|
|
582
|
+
context.beginPath();
|
|
583
|
+
context.roundRect(x, y, frameWidth, frameHeight, frameRadius);
|
|
584
|
+
context.fill();
|
|
585
|
+
context.save();
|
|
586
|
+
context.beginPath();
|
|
587
|
+
context.roundRect(contentX, contentY, contentWidth, contentHeight, 8);
|
|
588
|
+
context.clip();
|
|
589
|
+
context.fillStyle = background;
|
|
590
|
+
context.fillRect(contentX, contentY, contentWidth, contentHeight);
|
|
591
|
+
context.drawImage(image, contentX, contentY, contentWidth, contentHeight);
|
|
592
|
+
context.restore();
|
|
593
|
+
context.strokeStyle = frame.border;
|
|
594
|
+
context.beginPath();
|
|
595
|
+
context.roundRect(20.5, 52.5, contentWidth - 1, contentHeight - 1, 7.5);
|
|
596
|
+
context.stroke();
|
|
597
|
+
context.drawImage(logo, 24, 22, 20, 20);
|
|
598
|
+
context.fillStyle = frame.foreground;
|
|
599
|
+
context.textBaseline = "middle";
|
|
600
|
+
context.font = `600 14px ${frame.fontFamily}`;
|
|
601
|
+
const wordmarkX = 52;
|
|
602
|
+
context.fillText("Open Flow", wordmarkX, 32);
|
|
603
|
+
const availableWidth = x + frameWidth - frameTitleInset - (wordmarkX + context.measureText("Open Flow").width + 24);
|
|
604
|
+
context.font = `400 12px ${frame.fontFamily}`;
|
|
605
|
+
if (availableWidth > 0) {
|
|
606
|
+
let title = frame.title.replace(/\s+/g, " ");
|
|
607
|
+
if (context.measureText(title).width > availableWidth) {
|
|
608
|
+
const characters = Array.from(title);
|
|
609
|
+
while (characters.length && context.measureText(characters.join("") + "…").width > availableWidth) characters.pop();
|
|
610
|
+
title = characters.join("") + "…";
|
|
611
|
+
}
|
|
612
|
+
context.textAlign = "right";
|
|
613
|
+
context.fillText(title, x + frameWidth - frameTitleInset, 32);
|
|
614
|
+
}
|
|
615
|
+
context.strokeStyle = frame.border;
|
|
616
|
+
context.beginPath();
|
|
617
|
+
context.roundRect(12.5, 12.5, frameWidth - 1, frameHeight - 1, 15.5);
|
|
618
|
+
context.stroke();
|
|
619
|
+
context.restore();
|
|
620
|
+
}
|
|
621
|
+
var imagePlaceholder = "data:image/svg+xml;charset=utf-8," + encodeURIComponent("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"160\" height=\"100\" viewBox=\"0 0 160 100\"><rect width=\"160\" height=\"100\" rx=\"6\" fill=\"#eeeeee\"/><text x=\"80\" y=\"50\" text-anchor=\"middle\" dominant-baseline=\"central\" font-family=\"sans-serif\" font-size=\"24\" fill=\"#777777\">?</text></svg>");
|
|
552
622
|
var nextFrame = () => new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
553
|
-
async function waitFor(ready) {
|
|
554
|
-
const started = performance.now();
|
|
623
|
+
async function waitFor(ready, deadline) {
|
|
555
624
|
while (!ready()) {
|
|
556
|
-
if (performance.now()
|
|
625
|
+
if (performance.now() > deadline) throw new Error("Timed out rendering the workflow image.");
|
|
557
626
|
await nextFrame();
|
|
558
627
|
}
|
|
559
628
|
}
|
|
629
|
+
async function decodeImage(image, timeout) {
|
|
630
|
+
let timer;
|
|
631
|
+
try {
|
|
632
|
+
return await Promise.race([image.decode().then(() => true, () => false), new Promise((resolve) => {
|
|
633
|
+
timer = setTimeout(() => resolve(false), Math.max(0, timeout));
|
|
634
|
+
})]);
|
|
635
|
+
} finally {
|
|
636
|
+
clearTimeout(timer);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
/** Image failures can mount another image or start loading a bundled fallback icon. */
|
|
640
|
+
async function prepareImages(host, deadline) {
|
|
641
|
+
let hasImagePlaceholders = false;
|
|
642
|
+
while (true) {
|
|
643
|
+
for (const image of host.querySelectorAll("img")) image.loading = "eager";
|
|
644
|
+
await waitFor(() => host.querySelector("[data-render-pending]") == null, deadline);
|
|
645
|
+
const images = [...host.querySelectorAll("img")].map((image) => ({
|
|
646
|
+
image,
|
|
647
|
+
source: image.src
|
|
648
|
+
}));
|
|
649
|
+
const decoded = await Promise.all(images.map(({ image }) => {
|
|
650
|
+
image.loading = "eager";
|
|
651
|
+
return decodeImage(image, Math.min(5e3, deadline - performance.now()));
|
|
652
|
+
}));
|
|
653
|
+
await nextFrame();
|
|
654
|
+
await nextFrame();
|
|
655
|
+
let replaced = false;
|
|
656
|
+
images.forEach(({ image, source }, index) => {
|
|
657
|
+
if (decoded[index] || !host.contains(image) || image.src !== source) return;
|
|
658
|
+
if (source === imagePlaceholder) throw new Error("Unable to load the image placeholder.");
|
|
659
|
+
image.removeAttribute("srcset");
|
|
660
|
+
image.src = imagePlaceholder;
|
|
661
|
+
hasImagePlaceholders = replaced = true;
|
|
662
|
+
});
|
|
663
|
+
const current = [...host.querySelectorAll("img")];
|
|
664
|
+
if (!replaced && host.querySelector("[data-render-pending]") == null && current.length === images.length && current.every((image, index) => image === images[index].image && image.src === images[index].source)) return hasImagePlaceholders;
|
|
665
|
+
if (performance.now() > deadline) throw new Error("Timed out preparing workflow images.");
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
async function renderPng(dataUrl, width, height, background, frame) {
|
|
669
|
+
const ratio = Math.min(2, maximumDimension / width, maximumDimension / height, Math.sqrt(maximumPixels / (width * height)));
|
|
670
|
+
let outputWidth = Math.max(1, Math.floor(width * ratio));
|
|
671
|
+
let outputHeight = Math.max(1, Math.floor(height * ratio));
|
|
672
|
+
const image = new Image();
|
|
673
|
+
const logo = frame ? new Image() : void 0;
|
|
674
|
+
image.decoding = "async";
|
|
675
|
+
image.src = dataUrl;
|
|
676
|
+
try {
|
|
677
|
+
await image.decode();
|
|
678
|
+
if (logo && frame) {
|
|
679
|
+
logo.src = frame.logo;
|
|
680
|
+
await logo.decode();
|
|
681
|
+
}
|
|
682
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
683
|
+
const canvas = document.createElement("canvas");
|
|
684
|
+
try {
|
|
685
|
+
canvas.width = outputWidth;
|
|
686
|
+
canvas.height = outputHeight;
|
|
687
|
+
const context = canvas.getContext("2d", { alpha: false });
|
|
688
|
+
if (!context) throw new Error("Unable to allocate the workflow image.");
|
|
689
|
+
context.fillStyle = frame?.outerBackground ?? background;
|
|
690
|
+
context.fillRect(0, 0, outputWidth, outputHeight);
|
|
691
|
+
if (frame && logo) {
|
|
692
|
+
context.scale(outputWidth / width, outputHeight / height);
|
|
693
|
+
drawFrame(context, image, logo, width, height, background, frame);
|
|
694
|
+
} else context.drawImage(image, 0, 0, outputWidth, outputHeight);
|
|
695
|
+
if (context.isContextLost?.()) throw new Error("The workflow image context was lost.");
|
|
696
|
+
const blob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
|
|
697
|
+
if (!blob || context.isContextLost?.()) throw new Error("Unable to encode the workflow image.");
|
|
698
|
+
return {
|
|
699
|
+
blob,
|
|
700
|
+
width: outputWidth,
|
|
701
|
+
height: outputHeight
|
|
702
|
+
};
|
|
703
|
+
} catch (error) {
|
|
704
|
+
if (attempt === 2 || outputWidth === 1 && outputHeight === 1) throw error;
|
|
705
|
+
outputWidth = Math.max(1, Math.floor(outputWidth / 2));
|
|
706
|
+
outputHeight = Math.max(1, Math.floor(outputHeight / 2));
|
|
707
|
+
} finally {
|
|
708
|
+
canvas.width = canvas.height = 0;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
throw new Error("Unable to render the workflow image.");
|
|
712
|
+
} finally {
|
|
713
|
+
image.src = "";
|
|
714
|
+
if (logo) logo.src = "";
|
|
715
|
+
}
|
|
716
|
+
}
|
|
560
717
|
/** Render the real canvas at full detail without moving or changing the user's canvas. */
|
|
561
|
-
async function
|
|
718
|
+
async function captureFlowImage(model, language, options) {
|
|
719
|
+
const contentPadding = options.frame ? padding / 2 : padding;
|
|
562
720
|
const positions = model.nodes.map((node) => node.position);
|
|
563
721
|
const minX = Math.min(0, ...positions.map((position) => position.x));
|
|
564
722
|
const minY = Math.min(0, ...positions.map((position) => position.y));
|
|
565
723
|
const snapshot = {
|
|
566
724
|
...model,
|
|
567
725
|
viewport: {
|
|
568
|
-
x:
|
|
569
|
-
y:
|
|
726
|
+
x: contentPadding - minX,
|
|
727
|
+
y: contentPadding - minY,
|
|
570
728
|
zoom: 1
|
|
571
729
|
}
|
|
572
730
|
};
|
|
573
731
|
const host = document.createElement("div");
|
|
732
|
+
host.lang = language;
|
|
574
733
|
Object.assign(host.style, {
|
|
575
734
|
position: "fixed",
|
|
576
735
|
left: "-100000px",
|
|
@@ -596,16 +755,13 @@ async function flowToSvg(model, title, language = "en", options = {}) {
|
|
|
596
755
|
layoutMotion: false,
|
|
597
756
|
nodesDraggable: false
|
|
598
757
|
}))));
|
|
599
|
-
|
|
600
|
-
await waitFor(() =>
|
|
601
|
-
|
|
602
|
-
await
|
|
603
|
-
await document.fonts.ready;
|
|
604
|
-
await nextFrame();
|
|
605
|
-
await nextFrame();
|
|
758
|
+
const deadline = performance.now() + 15e3;
|
|
759
|
+
await waitFor(() => store.$.initialized.value && store.$.rfNodes.value.every((node) => !!node.measured?.width && !!node.measured.height), deadline);
|
|
760
|
+
let hasImagePlaceholders = await prepareImages(host, deadline);
|
|
761
|
+
await waitFor(() => document.fonts.status === "loaded", deadline);
|
|
606
762
|
const viewport = host.querySelector(".react-flow__viewport");
|
|
607
763
|
const origin = viewport.getBoundingClientRect();
|
|
608
|
-
const bounds = [...viewport.querySelectorAll(".react-flow__node")].map((node) => {
|
|
764
|
+
const bounds = [...viewport.querySelectorAll(".react-flow__node, [data-canvas-bounds]")].map((node) => {
|
|
609
765
|
const rect = node.getBoundingClientRect();
|
|
610
766
|
return {
|
|
611
767
|
x: rect.left - origin.left,
|
|
@@ -619,41 +775,83 @@ async function flowToSvg(model, title, language = "en", options = {}) {
|
|
|
619
775
|
const top = bounds.length ? Math.min(...bounds.map((rect) => rect.y)) : 0;
|
|
620
776
|
const right = bounds.length ? Math.max(...bounds.map((rect) => rect.x + rect.width)) : 320;
|
|
621
777
|
const bottom = bounds.length ? Math.max(...bounds.map((rect) => rect.y + rect.height)) : 120;
|
|
778
|
+
const width = Math.ceil(right - left + contentPadding * 2);
|
|
779
|
+
const height = Math.ceil(bottom - top + contentPadding * 2);
|
|
780
|
+
const background = getComputedStyle(host.querySelector(".react-flow")).backgroundColor;
|
|
622
781
|
const dataUrl = await toSvg(viewport, {
|
|
623
|
-
width
|
|
624
|
-
height
|
|
782
|
+
width,
|
|
783
|
+
height,
|
|
784
|
+
imagePlaceholder,
|
|
625
785
|
preferredFontFormat: "woff2",
|
|
626
786
|
includeQueryParams: true,
|
|
627
787
|
style: {
|
|
628
|
-
transform: `translate(${
|
|
788
|
+
transform: `translate(${contentPadding - left}px, ${contentPadding - top}px)`,
|
|
629
789
|
transformOrigin: "0 0"
|
|
630
790
|
}
|
|
631
791
|
});
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
792
|
+
hasImagePlaceholders ||= dataUrl.includes(encodeURIComponent(imagePlaceholder));
|
|
793
|
+
let frame;
|
|
794
|
+
if (options.frame) {
|
|
795
|
+
const palette = document.createElement("div");
|
|
796
|
+
palette.className = "open-flow-theme";
|
|
797
|
+
palette.dataset.theme = options.dark ? "dark" : "light";
|
|
798
|
+
Object.assign(palette.style, {
|
|
799
|
+
backgroundColor: "var(--ui-background)",
|
|
800
|
+
color: "var(--ui-foreground)",
|
|
801
|
+
borderColor: "var(--ui-border)"
|
|
802
|
+
});
|
|
803
|
+
host.append(palette);
|
|
804
|
+
const style = getComputedStyle(palette);
|
|
805
|
+
const outerBackground = style.backgroundColor;
|
|
806
|
+
if (options.dark) palette.style.backgroundColor = "var(--ui-card)";
|
|
807
|
+
frame = {
|
|
808
|
+
title: options.title ?? "",
|
|
809
|
+
logo: openFlowLogoSource(options.dark ? "dark" : "light"),
|
|
810
|
+
background: getComputedStyle(palette).backgroundColor,
|
|
811
|
+
outerBackground,
|
|
812
|
+
foreground: style.color,
|
|
813
|
+
border: style.borderColor,
|
|
814
|
+
fontFamily: getComputedStyle(viewport).fontFamily || "sans-serif"
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
return {
|
|
818
|
+
dataUrl,
|
|
819
|
+
width: width + (frame ? 40 : 0),
|
|
820
|
+
height: height + (frame ? 72 : 0),
|
|
821
|
+
background,
|
|
822
|
+
frame,
|
|
823
|
+
hasImagePlaceholders
|
|
824
|
+
};
|
|
641
825
|
} finally {
|
|
642
826
|
root.unmount();
|
|
643
827
|
store.dispose();
|
|
644
828
|
host.remove();
|
|
645
829
|
}
|
|
646
830
|
}
|
|
647
|
-
async function
|
|
648
|
-
const
|
|
649
|
-
|
|
831
|
+
async function flowToPng(model, language = "en", options = {}) {
|
|
832
|
+
const { dataUrl, width, height, background, frame, hasImagePlaceholders } = await captureFlowImage(model, language, options);
|
|
833
|
+
return {
|
|
834
|
+
...await renderPng(dataUrl, width, height, background, frame),
|
|
835
|
+
hasImagePlaceholders
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
async function downloadFlowPng(model, title, language = "en", options = {}) {
|
|
839
|
+
const result = await flowToPng(model, language, {
|
|
840
|
+
...options,
|
|
841
|
+
title
|
|
842
|
+
});
|
|
843
|
+
const url = URL.createObjectURL(result.blob);
|
|
650
844
|
const link = document.createElement("a");
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
845
|
+
try {
|
|
846
|
+
link.download = `${title.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "_").replace(/[. ]+$/g, "") || "workflow"}.png`;
|
|
847
|
+
link.href = url;
|
|
848
|
+
document.body.append(link);
|
|
849
|
+
link.click();
|
|
850
|
+
} finally {
|
|
851
|
+
link.remove();
|
|
852
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
853
|
+
}
|
|
854
|
+
return result;
|
|
657
855
|
}
|
|
658
856
|
//#endregion
|
|
659
|
-
export {
|
|
857
|
+
export { downloadFlowPng };
|