Compare commits

...

4 Commits

Author SHA1 Message Date
小智
dc5e7eb023
feat(free-container-plugin): 添加子画布背景支持,使用inversify依赖注入 (#420)
* feat(free-container-plugin): add sub-canvas background support with inversify

- Add inversify dependency injection support for sub-canvas background
- Use useService to get BackgroundConfig from IoC container
- Support all background options: backgroundColor, dotColor, dotSize, etc.
- Add graceful fallback when BackgroundConfig is not registered
- Generate unique SVG pattern IDs to avoid conflicts
- Remove hardcoded background color from styles
- Add @flowgram.ai/background-plugin dependency

The sub-canvas now automatically inherits background configuration
from the main canvas.

* feat(background-plugin): export BackgroundConfig and bind to IoC container

- Export BackgroundConfig symbol for use by other plugins
- Add onBind method to register BackgroundConfig in IoC container
- Enable dependency injection pattern for background configuration access

This allows sub-canvas components to access background configuration
through inversify container.

* chore: update pnpm-lock.yaml after adding background-plugin dependency

- Update lock file to include @flowgram.ai/background-plugin dependency
- Ensures consistent dependency versions across environments

---------

Co-authored-by: husky-dot <xiaozhi@172-0-8-36.lightspeed.rcsntx.sbcglobal.net>
2025-06-27 06:34:44 +00:00
xiamidaxia
080d28ba1a
fix: node.toJSON use document.toNodeJSON (#418) 2025-06-27 06:26:53 +00:00
Yiwei Mao
bf69e6cb89
fix(node-engine): glob null drilldown error, typeof null is object (#417) 2025-06-27 03:54:25 +00:00
xiamidaxia
629a9e564f
feat(fixed-layout): add ConstantKeys.BRANCH_SPACING (#413) 2025-06-26 09:02:37 +00:00
14 changed files with 101 additions and 45 deletions

View File

@ -91,6 +91,7 @@ export function useEditorProps(
*/
constants: {
// [ConstantKeys.NODE_SPACING]: 24,
// [ConstantKeys.BRANCH_SPACING]: 20,
// [ConstantKeys.INLINE_SPACING_BOTTOM]: 24,
// [ConstantKeys.INLINE_BLOCKS_INLINE_SPACING_BOTTOM]: 13,
// [ConstantKeys.ROUNDED_LINE_X_RADIUS]: 8,

View File

@ -2667,6 +2667,9 @@ importers:
../../packages/plugins/free-container-plugin:
dependencies:
'@flowgram.ai/background-plugin':
specifier: workspace:*
version: link:../background-plugin
'@flowgram.ai/core':
specifier: workspace:*
version: link:../../canvas-engine/core

View File

@ -337,37 +337,7 @@ export class FlowNodeEntity extends Entity<FlowNodeEntityConfig> {
* @param newId
*/
toJSON(): FlowNodeJSON {
if (this.document.options.toNodeJSON) {
return this.document.options.toNodeJSON(this);
}
const nodesMap: Record<string, FlowNodeJSON> = {};
let startNodeJSON: FlowNodeJSON;
this.document.traverse((node) => {
const isSystemNode = node.id.startsWith('$');
if (isSystemNode) return;
const nodeJSONData = this.getJSONData();
const nodeJSON: FlowNodeJSON = {
id: node.id,
type: node.flowNodeType,
};
if (nodeJSONData !== undefined) {
nodeJSON.data = nodeJSONData;
}
if (!startNodeJSON) startNodeJSON = nodeJSON;
let { parent } = node;
if (parent && parent.id.startsWith('$')) {
parent = parent.originParent;
}
const parentJSON = parent ? nodesMap[parent.id] : undefined;
if (parentJSON) {
if (!parentJSON.blocks) {
parentJSON.blocks = [];
}
parentJSON.blocks.push(nodeJSON);
}
nodesMap[node.id] = nodeJSON;
}, this);
return startNodeJSON!;
return this.document.toNodeJSON(this);
}
get isVertical(): boolean {

View File

@ -593,6 +593,40 @@ export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
return result;
}
toNodeJSON(node: FlowNodeEntity): FlowNodeJSON {
if (this.options.toNodeJSON) {
return this.options.toNodeJSON(node);
}
const nodesMap: Record<string, FlowNodeJSON> = {};
let startNodeJSON: FlowNodeJSON;
this.traverse((node) => {
const isSystemNode = node.id.startsWith('$');
if (isSystemNode) return;
const nodeJSONData = node.getJSONData();
const nodeJSON: FlowNodeJSON = {
id: node.id,
type: node.flowNodeType,
};
if (nodeJSONData !== undefined) {
nodeJSON.data = nodeJSONData;
}
if (!startNodeJSON) startNodeJSON = nodeJSON;
let { parent } = node;
if (parent && parent.id.startsWith('$')) {
parent = parent.originParent;
}
const parentJSON = parent ? nodesMap[parent.id] : undefined;
if (parentJSON) {
if (!parentJSON.blocks) {
parentJSON.blocks = [];
}
parentJSON.blocks.push(nodeJSON);
}
nodesMap[node.id] = nodeJSON;
}, node);
return startNodeJSON!;
}
/**
*
* @param param0

View File

@ -56,6 +56,10 @@ export const DefaultSpacingKey = {
* /
*/
NODE_SPACING: 'SPACING',
/**
*
*/
BRANCH_SPACING: 'BRANCH_SPACING',
/**
* 线 x radius
*/
@ -85,7 +89,11 @@ export const DefaultSpacingKey = {
export const DEFAULT_SPACING = {
NULL: 0,
[DefaultSpacingKey.NODE_SPACING]: 32, // 普通节点间距。垂直 / 水平
MARGIN_RIGHT: 20, // 普通节点右边间距
[DefaultSpacingKey.BRANCH_SPACING]: 20, // 分支节点间距
/**
* @deprecated use 'BRANCH_SPACING' instead
*/
MARGIN_RIGHT: 20, // 分支节点右边间距
INLINE_BLOCK_PADDING_BOTTOM: 16, // block 底部留白
INLINE_BLOCKS_PADDING_TOP: 30, // block list 上部留白间距
[DefaultSpacingKey.INLINE_BLOCKS_PADDING_BOTTOM]: 40, // block lit 下部留白间距,因为有两个拐弯,所以翻一倍

View File

@ -43,7 +43,7 @@ export const InlineBlocksRegistry: FlowNodeRegistry = {
// 如果小于最小宽度,偏移最小宽度的距离
const delta = Math.max(
child.parent!.minInlineBlockSpacing - leftSpacing,
DEFAULT_SPACING.MARGIN_RIGHT - child.originDeltaX
getDefaultSpacing(child.entity, ConstantKeys.BRANCH_SPACING) - child.originDeltaX
);
return {
@ -57,7 +57,7 @@ export const InlineBlocksRegistry: FlowNodeRegistry = {
// 如果小于最小高度,偏移最小高度的距离
const delta = Math.max(
child.parent!.minInlineBlockSpacing - bottomSpacing,
DEFAULT_SPACING.MARGIN_RIGHT - child.originDeltaY
getDefaultSpacing(child.entity, ConstantKeys.BRANCH_SPACING) - child.originDeltaY
);
return {

View File

@ -1,12 +1,13 @@
import { FlowRendererKey, FlowTextKey } from '@flowgram.ai/renderer';
import {
DEFAULT_SPACING,
FlowNodeBaseType,
type FlowNodeRegistry,
FlowNodeTransformData,
FlowTransitionLabelEnum,
FlowTransitionLineEnum,
FlowLayoutDefault,
getDefaultSpacing,
ConstantKeys,
} from '@flowgram.ai/document';
import { TryCatchSpacings, TryCatchTypeEnum } from './constants';
@ -118,12 +119,12 @@ export const MainInlineBlocksRegistry: FlowNodeRegistry = {
if (isVertical) {
delta = Math.max(
child.parent!.minInlineBlockSpacing,
-child.originDeltaX + DEFAULT_SPACING.MARGIN_RIGHT
-child.originDeltaX + getDefaultSpacing(child.entity, ConstantKeys.BRANCH_SPACING)
);
} else {
delta = Math.max(
child.parent!.minInlineBlockSpacing,
-child.originDeltaY + DEFAULT_SPACING.MARGIN_RIGHT
-child.originDeltaY + getDefaultSpacing(child.entity, ConstantKeys.BRANCH_SPACING)
);
}

View File

@ -68,4 +68,4 @@ export function ConditionRow({ style, value, onChange, readonly }: PropTypes) {
);
}
export { ConditionRowValueType };
export { type ConditionRowValueType };

View File

@ -146,7 +146,7 @@ export namespace Glob {
let curPaths: string[] = [];
let curValue = obj;
while (curKey) {
let isObject = typeof curValue === 'object';
let isObject = typeof curValue === 'object' && curValue !== null;
if (!isObject) return [];
// 匹配 *
if (curKey === ALL) {

View File

@ -12,6 +12,7 @@ const DEFAULT_RENDER_SIZE = 20;
const DEFAULT_DOT_SIZE = 1;
let id = 0;
export const BackgroundConfig = Symbol('BackgroundConfig');
export interface BackgroundLayerOptions {
/** 网格间距,默认 20px */
gridSize?: number;

View File

@ -1,11 +1,14 @@
import { definePluginCreator } from '@flowgram.ai/core';
import { BackgroundLayer, BackgroundLayerOptions } from './background-layer';
import { BackgroundConfig, BackgroundLayer, BackgroundLayerOptions } from './background-layer';
/**
*
*/
export const createBackgroundPlugin = definePluginCreator<BackgroundLayerOptions>({
onBind: (bindConfig, opts) => {
bindConfig.bind(BackgroundConfig).toConstantValue(opts);
},
onInit: (ctx, opts) => {
ctx.playground.registerLayer(BackgroundLayer, opts);
},

View File

@ -35,6 +35,7 @@
"@flowgram.ai/renderer": "workspace:*",
"@flowgram.ai/utils": "workspace:*",
"@flowgram.ai/i18n": "workspace:*",
"@flowgram.ai/background-plugin": "workspace:*",
"inversify": "^6.0.1",
"reflect-metadata": "~0.2.2",
"lodash": "^4.17.21"

View File

@ -1,21 +1,55 @@
import React, { type FC } from 'react';
import { useCurrentEntity } from '@flowgram.ai/free-layout-core';
import { useService } from '@flowgram.ai/core';
import { BackgroundConfig, BackgroundLayerOptions } from '@flowgram.ai/background-plugin';
import { SubCanvasBackgroundStyle } from './style';
export const SubCanvasBackground: FC = () => {
const node = useCurrentEntity();
// 通过 inversify 获取背景配置,如果没有配置则使用默认值
let backgroundConfig: BackgroundLayerOptions = {};
try {
backgroundConfig = useService<BackgroundLayerOptions>(BackgroundConfig);
} catch (error) {
// 如果 BackgroundConfig 没有注册,使用默认配置
// 静默处理,使用默认配置
}
// 获取配置值,如果没有则使用默认值
const gridSize = backgroundConfig.gridSize ?? 20;
const dotSize = backgroundConfig.dotSize ?? 1;
const dotColor = backgroundConfig.dotColor ?? '#eceeef';
const dotOpacity = backgroundConfig.dotOpacity ?? 0.5;
const backgroundColor = backgroundConfig.backgroundColor ?? '#f2f3f5';
const dotFillColor = backgroundConfig.dotFillColor ?? dotColor;
// 生成唯一的 pattern ID
const patternId = `sub-canvas-dot-pattern-${node.id}`;
return (
<SubCanvasBackgroundStyle className="sub-canvas-background" data-flow-editor-selectable="true">
<SubCanvasBackgroundStyle
className="sub-canvas-background"
data-flow-editor-selectable="true"
style={{ backgroundColor: backgroundColor }}
>
<svg width="100%" height="100%">
<pattern id="sub-canvas-dot-pattern" width="20" height="20" patternUnits="userSpaceOnUse">
<circle cx="1" cy="1" r="1" stroke="#eceeef" fillOpacity="0.5" />
<pattern id={patternId} width={gridSize} height={gridSize} patternUnits="userSpaceOnUse">
<circle
cx={dotSize}
cy={dotSize}
r={dotSize}
stroke={dotColor}
fill={dotFillColor}
fillOpacity={dotOpacity}
/>
</pattern>
<rect
width="100%"
height="100%"
fill="url(#sub-canvas-dot-pattern)"
fill={`url(#${patternId})`}
data-node-panel-container={node.id}
/>
</svg>

View File

@ -4,5 +4,5 @@ export const SubCanvasBackgroundStyle = styled.div`
width: 100%;
height: 100%;
inset: 56px 18px 18px;
background-color: #f2f3f5;
/* 背景色现在通过 style 属性动态设置 */
`;