mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
chore: rename variable-plugin in demo to sync-variable-plugin
This commit is contained in:
parent
700221c69b
commit
f9febe5fc3
@ -3,7 +3,7 @@ import { type SVGProps } from 'react';
|
|||||||
import { Input, Button } from '@douyinfe/semi-ui';
|
import { Input, Button } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
import { FlowValueSchema, FlowRefValueSchema } from '../../typings';
|
import { FlowValueSchema, FlowRefValueSchema } from '../../typings';
|
||||||
import { VariableSelector } from '../../plugins/variable-plugin/variable-selector';
|
import { VariableSelector } from '../../plugins/sync-variable-plugin/variable-selector';
|
||||||
|
|
||||||
export function FxIcon(props: SVGProps<SVGSVGElement>) {
|
export function FxIcon(props: SVGProps<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
@ -43,7 +43,7 @@ export function FxExpression(props: FxExpressionProps) {
|
|||||||
<VariableSelector
|
<VariableSelector
|
||||||
value={value.content}
|
value={value.content}
|
||||||
style={{ flexGrow: 1 }}
|
style={{ flexGrow: 1 }}
|
||||||
onChange={v => onChange({ type: 'expression', content: v })}
|
onChange={(v) => onChange({ type: 'expression', content: v })}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { IconCrossCircleStroked } from '@douyinfe/semi-icons';
|
|||||||
|
|
||||||
import { TypeSelector } from '../type-selector';
|
import { TypeSelector } from '../type-selector';
|
||||||
import { JsonSchema } from '../../typings';
|
import { JsonSchema } from '../../typings';
|
||||||
import { VariableSelector } from '../../plugins/variable-plugin/variable-selector';
|
import { VariableSelector } from '../../plugins/sync-variable-plugin/variable-selector';
|
||||||
import { LeftColumn, Row } from './styles';
|
import { LeftColumn, Row } from './styles';
|
||||||
|
|
||||||
export interface PropertyEditProps {
|
export interface PropertyEditProps {
|
||||||
@ -17,7 +17,7 @@ export interface PropertyEditProps {
|
|||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PropertyEdit: React.FC<PropertyEditProps> = props => {
|
export const PropertyEdit: React.FC<PropertyEditProps> = (props) => {
|
||||||
const { value, disabled } = props;
|
const { value, disabled } = props;
|
||||||
const [inputKey, updateKey] = useState(props.propertyKey);
|
const [inputKey, updateKey] = useState(props.propertyKey);
|
||||||
const updateProperty = (key: keyof JsonSchema, val: any) => {
|
const updateProperty = (key: keyof JsonSchema, val: any) => {
|
||||||
@ -34,12 +34,12 @@ export const PropertyEdit: React.FC<PropertyEditProps> = props => {
|
|||||||
value={value.type}
|
value={value.type}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
style={{ position: 'absolute', top: 6, left: 4, zIndex: 1 }}
|
style={{ position: 'absolute', top: 6, left: 4, zIndex: 1 }}
|
||||||
onChange={val => updateProperty('type', val)}
|
onChange={(val) => updateProperty('type', val)}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
value={inputKey}
|
value={inputKey}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={v => updateKey(v.trim())}
|
onChange={(v) => updateKey(v.trim())}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
if (inputKey !== '') {
|
if (inputKey !== '') {
|
||||||
props.onChange(value, props.propertyKey, inputKey);
|
props.onChange(value, props.propertyKey, inputKey);
|
||||||
@ -54,14 +54,14 @@ export const PropertyEdit: React.FC<PropertyEditProps> = props => {
|
|||||||
<VariableSelector
|
<VariableSelector
|
||||||
value={value.default}
|
value={value.default}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={val => updateProperty('default', val)}
|
onChange={(val) => updateProperty('default', val)}
|
||||||
style={{ flexGrow: 1, height: 32 }}
|
style={{ flexGrow: 1, height: 32 }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Input
|
<Input
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
value={value.default}
|
value={value.default}
|
||||||
onChange={val => updateProperty('default', val)}
|
onChange={(val) => updateProperty('default', val)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{props.onDelete && !disabled && (
|
{props.onDelete && !disabled && (
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import { Tag, Dropdown } from '@douyinfe/semi-ui';
|
import { Tag, Dropdown } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
import { VariableTypeIcons } from '../plugins/variable-plugin/icons';
|
import { VariableTypeIcons } from '../plugins/sync-variable-plugin/icons';
|
||||||
|
|
||||||
export interface TypeSelectorProps {
|
export interface TypeSelectorProps {
|
||||||
value?: string;
|
value?: string;
|
||||||
@ -12,7 +12,7 @@ export interface TypeSelectorProps {
|
|||||||
}
|
}
|
||||||
const dropdownMenus = ['object', 'boolean', 'array', 'string', 'integer', 'number'];
|
const dropdownMenus = ['object', 'boolean', 'array', 'string', 'integer', 'number'];
|
||||||
|
|
||||||
export const TypeSelector: React.FC<TypeSelectorProps> = props => {
|
export const TypeSelector: React.FC<TypeSelectorProps> = (props) => {
|
||||||
const { value, disabled } = props;
|
const { value, disabled } = props;
|
||||||
const icon = VariableTypeIcons[value as any];
|
const icon = VariableTypeIcons[value as any];
|
||||||
return (
|
return (
|
||||||
@ -22,7 +22,7 @@ export const TypeSelector: React.FC<TypeSelectorProps> = props => {
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
render={
|
render={
|
||||||
<Dropdown.Menu>
|
<Dropdown.Menu>
|
||||||
{dropdownMenus.map(key => (
|
{dropdownMenus.map((key) => (
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
key={key}
|
key={key}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -39,7 +39,7 @@ export const TypeSelector: React.FC<TypeSelectorProps> = props => {
|
|||||||
<Tag
|
<Tag
|
||||||
color="white"
|
color="white"
|
||||||
style={props.style}
|
style={props.style}
|
||||||
onClick={e => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Tag, Tooltip } from '@douyinfe/semi-ui';
|
import { Tag, Tooltip } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
import { VariableTypeIcons, ArrayIcons } from '../plugins/variable-plugin/icons';
|
import { VariableTypeIcons, ArrayIcons } from '../plugins/sync-variable-plugin/icons';
|
||||||
|
|
||||||
interface PropsType {
|
interface PropsType {
|
||||||
name?: string | JSX.Element;
|
name?: string | JSX.Element;
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
import { type FlowNodeRegistry } from '../typings';
|
import { type FlowNodeRegistry } from '../typings';
|
||||||
import { shortcutGetter } from '../shortcuts';
|
import { shortcutGetter } from '../shortcuts';
|
||||||
import { GroupBoxHeader, GroupNode } from '../plugins/group-plugin';
|
import { GroupBoxHeader, GroupNode } from '../plugins/group-plugin';
|
||||||
import { createVariablePlugin, createClipboardPlugin } from '../plugins';
|
import { createSyncVariablePlugin, createClipboardPlugin } from '../plugins';
|
||||||
import { defaultFormMeta } from '../nodes';
|
import { defaultFormMeta } from '../nodes';
|
||||||
import { SelectorBoxPopover } from '../components/selector-box-popover';
|
import { SelectorBoxPopover } from '../components/selector-box-popover';
|
||||||
import NodeAdder from '../components/node-adder';
|
import NodeAdder from '../components/node-adder';
|
||||||
@ -227,7 +227,7 @@ export function useEditorProps(
|
|||||||
* Variable plugin
|
* Variable plugin
|
||||||
* 变量插件
|
* 变量插件
|
||||||
*/
|
*/
|
||||||
createVariablePlugin({}),
|
createSyncVariablePlugin({}),
|
||||||
/**
|
/**
|
||||||
* Clipboard plugin
|
* Clipboard plugin
|
||||||
* 剪切板插件
|
* 剪切板插件
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
export { createClipboardPlugin } from './clipboard-plugin/create-clipboard-plugin';
|
export { createClipboardPlugin } from './clipboard-plugin/create-clipboard-plugin';
|
||||||
export { createVariablePlugin } from './variable-plugin/variable-plugin';
|
export { createSyncVariablePlugin } from './sync-variable-plugin/sync-variable-plugin';
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './sync-variable-plugin';
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
import {
|
||||||
|
definePluginCreator,
|
||||||
|
FlowNodeVariableData,
|
||||||
|
getNodeForm,
|
||||||
|
PluginCreator,
|
||||||
|
FixedLayoutPluginContext,
|
||||||
|
ASTFactory,
|
||||||
|
} from '@flowgram.ai/fixed-layout-editor';
|
||||||
|
|
||||||
|
import { createASTFromJSONSchema } from './utils';
|
||||||
|
|
||||||
|
export interface SyncVariablePluginOptions {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a plugin to synchronize output data to the variable engine when nodes are created or updated.
|
||||||
|
* @param ctx - The plugin context, containing the document and other relevant information.
|
||||||
|
* @param options - Plugin options, currently an empty object.
|
||||||
|
*/
|
||||||
|
export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
|
||||||
|
definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
|
||||||
|
onInit(ctx, options) {
|
||||||
|
const flowDocument = ctx.document;
|
||||||
|
|
||||||
|
// Listen for node creation events
|
||||||
|
flowDocument.onNodeCreate(({ node }) => {
|
||||||
|
const form = getNodeForm(node);
|
||||||
|
const variableData = node.getData(FlowNodeVariableData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronizes output data to the variable engine.
|
||||||
|
* @param value - The output data to synchronize.
|
||||||
|
*/
|
||||||
|
const syncOutputs = (value: any) => {
|
||||||
|
if (!value) {
|
||||||
|
// If the output data is empty, clear the variable
|
||||||
|
variableData.clearVar();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an Type AST from the output data's JSON schema
|
||||||
|
// NOTICE: You can create a new function to generate an AST based on YOUR CUSTOM DSL
|
||||||
|
const typeAST = createASTFromJSONSchema(value);
|
||||||
|
|
||||||
|
if (typeAST) {
|
||||||
|
// Use the node's title or its ID as the title for the variable
|
||||||
|
const title = form?.getValueIn('title') || node.id;
|
||||||
|
|
||||||
|
// Set the variable in the variable engine
|
||||||
|
variableData.setVar(
|
||||||
|
ASTFactory.createVariableDeclaration({
|
||||||
|
meta: {
|
||||||
|
title: `${title}.outputs`,
|
||||||
|
// NOTICE: You can add more metadata here as needed
|
||||||
|
},
|
||||||
|
key: `${node.id}.outputs`,
|
||||||
|
type: typeAST,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// If the AST cannot be created, clear the variable
|
||||||
|
variableData.clearVar();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (form) {
|
||||||
|
// Initially synchronize the output data
|
||||||
|
syncOutputs(form.getValueIn('outputs'));
|
||||||
|
|
||||||
|
// Listen for changes in the form values and re-synchronize when outputs change
|
||||||
|
form.onFormValuesChange((props) => {
|
||||||
|
if (props.name.match(/^outputs/)) {
|
||||||
|
syncOutputs(form.getValueIn('outputs'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -3,12 +3,32 @@ import { ASTFactory, type ASTNodeJSON } from '@flowgram.ai/fixed-layout-editor';
|
|||||||
|
|
||||||
import { type JsonSchema } from '../../typings';
|
import { type JsonSchema } from '../../typings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts the properties of a JSON schema based on the 'extra.index' field.
|
||||||
|
* If the 'extra.index' field is not present, the property will be treated as having an index of 0.
|
||||||
|
*
|
||||||
|
* @param properties - The properties of the JSON schema to sort.
|
||||||
|
* @returns A sorted array of property entries.
|
||||||
|
*/
|
||||||
function sortProperties(properties: Record<string, JsonSchema>) {
|
function sortProperties(properties: Record<string, JsonSchema>) {
|
||||||
return Object.entries(properties).sort(
|
return Object.entries(properties).sort(
|
||||||
(a, b) => (get(a?.[1], 'extra.index') || 0) - (get(b?.[1], 'extra.index') || 0)
|
(a, b) => (get(a?.[1], 'extra.index') || 0) - (get(b?.[1], 'extra.index') || 0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a JSON schema to an Abstract Syntax Tree (AST) representation.
|
||||||
|
* This function recursively processes the JSON schema and creates corresponding AST nodes.
|
||||||
|
*
|
||||||
|
* For more information on JSON Schema, refer to the official documentation:
|
||||||
|
* https://json-schema.org/
|
||||||
|
*
|
||||||
|
* Note: Depending on your business needs, you can use your own Domain-Specific Language (DSL)
|
||||||
|
* Create a new function to convert your custom DSL to AST directly.
|
||||||
|
*
|
||||||
|
* @param jsonSchema - The JSON schema to convert.
|
||||||
|
* @returns An AST node representing the JSON schema, or undefined if the schema type is not recognized.
|
||||||
|
*/
|
||||||
export function createASTFromJSONSchema(jsonSchema: JsonSchema): ASTNodeJSON | undefined {
|
export function createASTFromJSONSchema(jsonSchema: JsonSchema): ASTNodeJSON | undefined {
|
||||||
const { type } = jsonSchema || {};
|
const { type } = jsonSchema || {};
|
||||||
|
|
||||||
@ -39,7 +59,8 @@ export function createASTFromJSONSchema(jsonSchema: JsonSchema): ASTNodeJSON | u
|
|||||||
return ASTFactory.createInteger();
|
return ASTFactory.createInteger();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Camel case to variable-core type
|
// If the type is not recognized, return undefined.
|
||||||
|
// You can extend this function to handle custom types if needed.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from './variable-plugin';
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
import {
|
|
||||||
definePluginCreator,
|
|
||||||
FixedLayoutPluginContext,
|
|
||||||
FlowNodeVariableData,
|
|
||||||
getNodeForm,
|
|
||||||
PluginCreator,
|
|
||||||
ASTFactory,
|
|
||||||
} from '@flowgram.ai/fixed-layout-editor';
|
|
||||||
|
|
||||||
import { createASTFromJSONSchema } from './utils';
|
|
||||||
|
|
||||||
export interface VariablePluginOptions {}
|
|
||||||
|
|
||||||
export const createVariablePlugin: PluginCreator<VariablePluginOptions> = definePluginCreator<
|
|
||||||
VariablePluginOptions,
|
|
||||||
FixedLayoutPluginContext
|
|
||||||
>({
|
|
||||||
onInit(ctx, options) {
|
|
||||||
const flowDocument = ctx.document;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listens to the creation of nodes and synchronizes outputs data to the variable engine
|
|
||||||
* 监听节点的创建,并将 outputs 数据同步给变量引擎
|
|
||||||
*/
|
|
||||||
flowDocument.onNodeCreate(({ node }) => {
|
|
||||||
const form = getNodeForm(node);
|
|
||||||
const variableData = node.getData<FlowNodeVariableData>(FlowNodeVariableData);
|
|
||||||
|
|
||||||
const syncOutputs = (value: any) => {
|
|
||||||
if (!value) {
|
|
||||||
variableData.clearVar();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const typeAST = createASTFromJSONSchema(value);
|
|
||||||
|
|
||||||
if (typeAST) {
|
|
||||||
const title = form?.getValueIn('title') || node.id;
|
|
||||||
|
|
||||||
variableData.setVar(
|
|
||||||
ASTFactory.createVariableDeclaration({
|
|
||||||
meta: {
|
|
||||||
title: `${title}.outputs`,
|
|
||||||
},
|
|
||||||
key: `${node.id}.outputs`,
|
|
||||||
type: typeAST,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
variableData.clearVar();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (form) {
|
|
||||||
syncOutputs(form.getValueIn('outputs'));
|
|
||||||
// Listen outputs change
|
|
||||||
form.onFormValuesChange((props) => {
|
|
||||||
if (props.name.match(/^outputs/)) {
|
|
||||||
syncOutputs(form.getValueIn('outputs'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -3,7 +3,7 @@ import React, { type SVGProps } from 'react';
|
|||||||
import { Input, Button } from '@douyinfe/semi-ui';
|
import { Input, Button } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
import { FlowRefValueSchema, FlowLiteralValueSchema } from '../../typings';
|
import { FlowRefValueSchema, FlowLiteralValueSchema } from '../../typings';
|
||||||
import { VariableSelector } from '../../plugins/variable-plugin/variable-selector';
|
import { VariableSelector } from '../../plugins/sync-variable-plugin/variable-selector';
|
||||||
|
|
||||||
export function FxIcon(props: SVGProps<SVGSVGElement>) {
|
export function FxIcon(props: SVGProps<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
@ -45,7 +45,7 @@ export function FxExpression(props: FxExpressionProps) {
|
|||||||
value={value.content}
|
value={value.content}
|
||||||
hasError={props.hasError}
|
hasError={props.hasError}
|
||||||
style={{ flexGrow: 1 }}
|
style={{ flexGrow: 1 }}
|
||||||
onChange={v => onChange({ type: 'expression', content: v })}
|
onChange={(v) => onChange({ type: 'expression', content: v })}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { IconCrossCircleStroked } from '@douyinfe/semi-icons';
|
|||||||
|
|
||||||
import { TypeSelector } from '../type-selector';
|
import { TypeSelector } from '../type-selector';
|
||||||
import { JsonSchema } from '../../typings';
|
import { JsonSchema } from '../../typings';
|
||||||
import { VariableSelector } from '../../plugins/variable-plugin/variable-selector';
|
import { VariableSelector } from '../../plugins/sync-variable-plugin/variable-selector';
|
||||||
import { LeftColumn, Row } from './styles';
|
import { LeftColumn, Row } from './styles';
|
||||||
|
|
||||||
export interface PropertyEditProps {
|
export interface PropertyEditProps {
|
||||||
@ -17,7 +17,7 @@ export interface PropertyEditProps {
|
|||||||
onDelete?: () => void;
|
onDelete?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PropertyEdit: React.FC<PropertyEditProps> = props => {
|
export const PropertyEdit: React.FC<PropertyEditProps> = (props) => {
|
||||||
const { value, disabled } = props;
|
const { value, disabled } = props;
|
||||||
const [inputKey, updateKey] = useState(props.propertyKey);
|
const [inputKey, updateKey] = useState(props.propertyKey);
|
||||||
const updateProperty = (key: keyof JsonSchema, val: any) => {
|
const updateProperty = (key: keyof JsonSchema, val: any) => {
|
||||||
@ -34,12 +34,12 @@ export const PropertyEdit: React.FC<PropertyEditProps> = props => {
|
|||||||
value={value.type}
|
value={value.type}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
style={{ position: 'absolute', top: 6, left: 4, zIndex: 1 }}
|
style={{ position: 'absolute', top: 6, left: 4, zIndex: 1 }}
|
||||||
onChange={val => updateProperty('type', val)}
|
onChange={(val) => updateProperty('type', val)}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
value={inputKey}
|
value={inputKey}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={v => updateKey(v.trim())}
|
onChange={(v) => updateKey(v.trim())}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
if (inputKey !== '') {
|
if (inputKey !== '') {
|
||||||
props.onChange(value, props.propertyKey, inputKey);
|
props.onChange(value, props.propertyKey, inputKey);
|
||||||
@ -54,14 +54,14 @@ export const PropertyEdit: React.FC<PropertyEditProps> = props => {
|
|||||||
<VariableSelector
|
<VariableSelector
|
||||||
value={value.default}
|
value={value.default}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={val => updateProperty('default', val)}
|
onChange={(val) => updateProperty('default', val)}
|
||||||
style={{ flexGrow: 1, height: 32 }}
|
style={{ flexGrow: 1, height: 32 }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Input
|
<Input
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
value={value.default}
|
value={value.default}
|
||||||
onChange={val => updateProperty('default', val)}
|
onChange={(val) => updateProperty('default', val)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{props.onDelete && !disabled && (
|
{props.onDelete && !disabled && (
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
|
|
||||||
import { Tag, Dropdown } from '@douyinfe/semi-ui';
|
import { Tag, Dropdown } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
import { VariableTypeIcons } from '../plugins/variable-plugin/icons';
|
import { VariableTypeIcons } from '../plugins/sync-variable-plugin/icons';
|
||||||
|
|
||||||
export interface TypeSelectorProps {
|
export interface TypeSelectorProps {
|
||||||
value?: string;
|
value?: string;
|
||||||
@ -12,7 +12,7 @@ export interface TypeSelectorProps {
|
|||||||
}
|
}
|
||||||
const dropdownMenus = ['object', 'boolean', 'array', 'string', 'integer', 'number'];
|
const dropdownMenus = ['object', 'boolean', 'array', 'string', 'integer', 'number'];
|
||||||
|
|
||||||
export const TypeSelector: React.FC<TypeSelectorProps> = props => {
|
export const TypeSelector: React.FC<TypeSelectorProps> = (props) => {
|
||||||
const { value, disabled } = props;
|
const { value, disabled } = props;
|
||||||
const icon = VariableTypeIcons[value as any];
|
const icon = VariableTypeIcons[value as any];
|
||||||
return (
|
return (
|
||||||
@ -22,7 +22,7 @@ export const TypeSelector: React.FC<TypeSelectorProps> = props => {
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
render={
|
render={
|
||||||
<Dropdown.Menu>
|
<Dropdown.Menu>
|
||||||
{dropdownMenus.map(key => (
|
{dropdownMenus.map((key) => (
|
||||||
<Dropdown.Item
|
<Dropdown.Item
|
||||||
key={key}
|
key={key}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -39,7 +39,7 @@ export const TypeSelector: React.FC<TypeSelectorProps> = props => {
|
|||||||
<Tag
|
<Tag
|
||||||
color="white"
|
color="white"
|
||||||
style={props.style}
|
style={props.style}
|
||||||
onClick={e => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Tag, Tooltip } from '@douyinfe/semi-ui';
|
import { Tag, Tooltip } from '@douyinfe/semi-ui';
|
||||||
|
|
||||||
import { VariableTypeIcons, ArrayIcons } from '../plugins/variable-plugin/icons';
|
import { VariableTypeIcons, ArrayIcons } from '../plugins/sync-variable-plugin/icons';
|
||||||
|
|
||||||
interface PropsType {
|
interface PropsType {
|
||||||
name?: string | JSX.Element;
|
name?: string | JSX.Element;
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { FreeLayoutProps } from '@flowgram.ai/free-layout-editor';
|
|||||||
|
|
||||||
import { FlowNodeRegistry, FlowDocumentJSON } from '../typings';
|
import { FlowNodeRegistry, FlowDocumentJSON } from '../typings';
|
||||||
import { shortcuts } from '../shortcuts';
|
import { shortcuts } from '../shortcuts';
|
||||||
import { createVariablePlugin } from '../plugins';
|
import { createSyncVariablePlugin } from '../plugins';
|
||||||
import { defaultFormMeta } from '../nodes/default-form-meta';
|
import { defaultFormMeta } from '../nodes/default-form-meta';
|
||||||
import { SelectorBoxPopover } from '../components/selector-box-popover';
|
import { SelectorBoxPopover } from '../components/selector-box-popover';
|
||||||
import { BaseNode, LineAddButton, NodePanel } from '../components';
|
import { BaseNode, LineAddButton, NodePanel } from '../components';
|
||||||
@ -184,7 +184,7 @@ export function useEditorProps(
|
|||||||
* Variable plugin
|
* Variable plugin
|
||||||
* 变量插件
|
* 变量插件
|
||||||
*/
|
*/
|
||||||
createVariablePlugin({}),
|
createSyncVariablePlugin({}),
|
||||||
/**
|
/**
|
||||||
* Snap plugin
|
* Snap plugin
|
||||||
* 自动对齐及辅助线插件
|
* 自动对齐及辅助线插件
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
export { createVariablePlugin } from './variable-plugin/variable-plugin';
|
export { createSyncVariablePlugin } from './sync-variable-plugin/sync-variable-plugin';
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export * from './sync-variable-plugin';
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
import {
|
||||||
|
definePluginCreator,
|
||||||
|
FlowNodeVariableData,
|
||||||
|
getNodeForm,
|
||||||
|
PluginCreator,
|
||||||
|
FreeLayoutPluginContext,
|
||||||
|
ASTFactory,
|
||||||
|
} from '@flowgram.ai/free-layout-editor';
|
||||||
|
|
||||||
|
import { createASTFromJSONSchema } from './utils';
|
||||||
|
|
||||||
|
export interface SyncVariablePluginOptions {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a plugin to synchronize output data to the variable engine when nodes are created or updated.
|
||||||
|
* @param ctx - The plugin context, containing the document and other relevant information.
|
||||||
|
* @param options - Plugin options, currently an empty object.
|
||||||
|
*/
|
||||||
|
export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
|
||||||
|
definePluginCreator<SyncVariablePluginOptions, FreeLayoutPluginContext>({
|
||||||
|
onInit(ctx, options) {
|
||||||
|
const flowDocument = ctx.document;
|
||||||
|
|
||||||
|
// Listen for node creation events
|
||||||
|
flowDocument.onNodeCreate(({ node }) => {
|
||||||
|
const form = getNodeForm(node);
|
||||||
|
const variableData = node.getData(FlowNodeVariableData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronizes output data to the variable engine.
|
||||||
|
* @param value - The output data to synchronize.
|
||||||
|
*/
|
||||||
|
const syncOutputs = (value: any) => {
|
||||||
|
if (!value) {
|
||||||
|
// If the output data is empty, clear the variable
|
||||||
|
variableData.clearVar();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an Type AST from the output data's JSON schema
|
||||||
|
// NOTICE: You can create a new function to generate an AST based on YOUR CUSTOM DSL
|
||||||
|
const typeAST = createASTFromJSONSchema(value);
|
||||||
|
|
||||||
|
if (typeAST) {
|
||||||
|
// Use the node's title or its ID as the title for the variable
|
||||||
|
const title = form?.getValueIn('title') || node.id;
|
||||||
|
|
||||||
|
// Set the variable in the variable engine
|
||||||
|
variableData.setVar(
|
||||||
|
ASTFactory.createVariableDeclaration({
|
||||||
|
meta: {
|
||||||
|
title: `${title}.outputs`,
|
||||||
|
// NOTICE: You can add more metadata here as needed
|
||||||
|
},
|
||||||
|
key: `${node.id}.outputs`,
|
||||||
|
type: typeAST,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// If the AST cannot be created, clear the variable
|
||||||
|
variableData.clearVar();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (form) {
|
||||||
|
// Initially synchronize the output data
|
||||||
|
syncOutputs(form.getValueIn('outputs'));
|
||||||
|
|
||||||
|
// Listen for changes in the form values and re-synchronize when outputs change
|
||||||
|
form.onFormValuesChange((props) => {
|
||||||
|
if (props.name.match(/^outputs/)) {
|
||||||
|
syncOutputs(form.getValueIn('outputs'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -3,12 +3,32 @@ import { ASTFactory, type ASTNodeJSON } from '@flowgram.ai/free-layout-editor';
|
|||||||
|
|
||||||
import { type JsonSchema } from '../../typings';
|
import { type JsonSchema } from '../../typings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts the properties of a JSON schema based on the 'extra.index' field.
|
||||||
|
* If the 'extra.index' field is not present, the property will be treated as having an index of 0.
|
||||||
|
*
|
||||||
|
* @param properties - The properties of the JSON schema to sort.
|
||||||
|
* @returns A sorted array of property entries.
|
||||||
|
*/
|
||||||
function sortProperties(properties: Record<string, JsonSchema>) {
|
function sortProperties(properties: Record<string, JsonSchema>) {
|
||||||
return Object.entries(properties).sort(
|
return Object.entries(properties).sort(
|
||||||
(a, b) => (get(a?.[1], 'extra.index') || 0) - (get(b?.[1], 'extra.index') || 0)
|
(a, b) => (get(a?.[1], 'extra.index') || 0) - (get(b?.[1], 'extra.index') || 0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a JSON schema to an Abstract Syntax Tree (AST) representation.
|
||||||
|
* This function recursively processes the JSON schema and creates corresponding AST nodes.
|
||||||
|
*
|
||||||
|
* For more information on JSON Schema, refer to the official documentation:
|
||||||
|
* https://json-schema.org/
|
||||||
|
*
|
||||||
|
* Note: Depending on your business needs, you can use your own Domain-Specific Language (DSL)
|
||||||
|
* Create a new function to convert your custom DSL to AST directly.
|
||||||
|
*
|
||||||
|
* @param jsonSchema - The JSON schema to convert.
|
||||||
|
* @returns An AST node representing the JSON schema, or undefined if the schema type is not recognized.
|
||||||
|
*/
|
||||||
export function createASTFromJSONSchema(jsonSchema: JsonSchema): ASTNodeJSON | undefined {
|
export function createASTFromJSONSchema(jsonSchema: JsonSchema): ASTNodeJSON | undefined {
|
||||||
const { type } = jsonSchema || {};
|
const { type } = jsonSchema || {};
|
||||||
|
|
||||||
@ -39,7 +59,8 @@ export function createASTFromJSONSchema(jsonSchema: JsonSchema): ASTNodeJSON | u
|
|||||||
return ASTFactory.createInteger();
|
return ASTFactory.createInteger();
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Camel case to variable-core type
|
// If the type is not recognized, return undefined.
|
||||||
|
// You can extend this function to handle custom types if needed.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1 +0,0 @@
|
|||||||
export * from './variable-plugin';
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
import {
|
|
||||||
definePluginCreator,
|
|
||||||
FlowNodeVariableData,
|
|
||||||
getNodeForm,
|
|
||||||
PluginCreator,
|
|
||||||
FreeLayoutPluginContext,
|
|
||||||
ASTFactory,
|
|
||||||
} from '@flowgram.ai/free-layout-editor';
|
|
||||||
|
|
||||||
import { createASTFromJSONSchema } from './utils';
|
|
||||||
|
|
||||||
export interface VariablePluginOptions {}
|
|
||||||
|
|
||||||
export const createVariablePlugin: PluginCreator<VariablePluginOptions> = definePluginCreator<
|
|
||||||
VariablePluginOptions,
|
|
||||||
FreeLayoutPluginContext
|
|
||||||
>({
|
|
||||||
onInit(ctx, options) {
|
|
||||||
const flowDocument = ctx.document;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listens to the creation of nodes and synchronizes outputs data to the variable engine
|
|
||||||
* 监听节点的创建,并将 outputs 数据同步给变量引擎
|
|
||||||
*/
|
|
||||||
flowDocument.onNodeCreate(({ node }) => {
|
|
||||||
const form = getNodeForm(node);
|
|
||||||
const variableData = node.getData<FlowNodeVariableData>(FlowNodeVariableData);
|
|
||||||
|
|
||||||
const syncOutputs = (value: any) => {
|
|
||||||
if (!value) {
|
|
||||||
variableData.clearVar();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const typeAST = createASTFromJSONSchema(value);
|
|
||||||
|
|
||||||
if (typeAST) {
|
|
||||||
const title = form?.getValueIn('title') || node.id;
|
|
||||||
|
|
||||||
variableData.setVar(
|
|
||||||
ASTFactory.createVariableDeclaration({
|
|
||||||
meta: {
|
|
||||||
title: `${title}.outputs`,
|
|
||||||
},
|
|
||||||
key: `${node.id}.outputs`,
|
|
||||||
type: typeAST,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
variableData.clearVar();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (form) {
|
|
||||||
syncOutputs(form.getValueIn('outputs'));
|
|
||||||
// Listen outputs change
|
|
||||||
form.onFormValuesChange((props) => {
|
|
||||||
if (props.name.match(/^outputs/)) {
|
|
||||||
syncOutputs(form.getValueIn('outputs'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Loading…
x
Reference in New Issue
Block a user