chore: rename variable-plugin in demo to sync-variable-plugin

This commit is contained in:
sanmaopep 2025-03-04 10:24:05 +08:00
parent 700221c69b
commit f9febe5fc3
30 changed files with 234 additions and 166 deletions

View File

@ -3,7 +3,7 @@ import { type SVGProps } from 'react';
import { Input, Button } from '@douyinfe/semi-ui';
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>) {
return (
@ -43,7 +43,7 @@ export function FxExpression(props: FxExpressionProps) {
<VariableSelector
value={value.content}
style={{ flexGrow: 1 }}
onChange={v => onChange({ type: 'expression', content: v })}
onChange={(v) => onChange({ type: 'expression', content: v })}
disabled={disabled}
/>
) : (

View File

@ -5,7 +5,7 @@ import { IconCrossCircleStroked } from '@douyinfe/semi-icons';
import { TypeSelector } from '../type-selector';
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';
export interface PropertyEditProps {
@ -17,7 +17,7 @@ export interface PropertyEditProps {
onDelete?: () => void;
}
export const PropertyEdit: React.FC<PropertyEditProps> = props => {
export const PropertyEdit: React.FC<PropertyEditProps> = (props) => {
const { value, disabled } = props;
const [inputKey, updateKey] = useState(props.propertyKey);
const updateProperty = (key: keyof JsonSchema, val: any) => {
@ -34,12 +34,12 @@ export const PropertyEdit: React.FC<PropertyEditProps> = props => {
value={value.type}
disabled={disabled}
style={{ position: 'absolute', top: 6, left: 4, zIndex: 1 }}
onChange={val => updateProperty('type', val)}
onChange={(val) => updateProperty('type', val)}
/>
<Input
value={inputKey}
disabled={disabled}
onChange={v => updateKey(v.trim())}
onChange={(v) => updateKey(v.trim())}
onBlur={() => {
if (inputKey !== '') {
props.onChange(value, props.propertyKey, inputKey);
@ -54,14 +54,14 @@ export const PropertyEdit: React.FC<PropertyEditProps> = props => {
<VariableSelector
value={value.default}
disabled={disabled}
onChange={val => updateProperty('default', val)}
onChange={(val) => updateProperty('default', val)}
style={{ flexGrow: 1, height: 32 }}
/>
) : (
<Input
disabled={disabled}
value={value.default}
onChange={val => updateProperty('default', val)}
onChange={(val) => updateProperty('default', val)}
/>
)}
{props.onDelete && !disabled && (

View File

@ -2,7 +2,7 @@ import React from 'react';
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 {
value?: string;
@ -12,7 +12,7 @@ export interface TypeSelectorProps {
}
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 icon = VariableTypeIcons[value as any];
return (
@ -22,7 +22,7 @@ export const TypeSelector: React.FC<TypeSelectorProps> = props => {
disabled={disabled}
render={
<Dropdown.Menu>
{dropdownMenus.map(key => (
{dropdownMenus.map((key) => (
<Dropdown.Item
key={key}
onClick={() => {
@ -39,7 +39,7 @@ export const TypeSelector: React.FC<TypeSelectorProps> = props => {
<Tag
color="white"
style={props.style}
onClick={e => {
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}

View File

@ -1,7 +1,7 @@
import styled from 'styled-components';
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 {
name?: string | JSX.Element;

View File

@ -14,7 +14,7 @@ import {
import { type FlowNodeRegistry } from '../typings';
import { shortcutGetter } from '../shortcuts';
import { GroupBoxHeader, GroupNode } from '../plugins/group-plugin';
import { createVariablePlugin, createClipboardPlugin } from '../plugins';
import { createSyncVariablePlugin, createClipboardPlugin } from '../plugins';
import { defaultFormMeta } from '../nodes';
import { SelectorBoxPopover } from '../components/selector-box-popover';
import NodeAdder from '../components/node-adder';
@ -227,7 +227,7 @@ export function useEditorProps(
* Variable plugin
*
*/
createVariablePlugin({}),
createSyncVariablePlugin({}),
/**
* Clipboard plugin
*

View File

@ -1,2 +1,2 @@
export { createClipboardPlugin } from './clipboard-plugin/create-clipboard-plugin';
export { createVariablePlugin } from './variable-plugin/variable-plugin';
export { createSyncVariablePlugin } from './sync-variable-plugin/sync-variable-plugin';

View File

@ -0,0 +1 @@
export * from './sync-variable-plugin';

View File

@ -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'));
}
});
}
});
},
});

View File

@ -3,12 +3,32 @@ import { ASTFactory, type ASTNodeJSON } from '@flowgram.ai/fixed-layout-editor';
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>) {
return Object.entries(properties).sort(
(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 {
const { type } = jsonSchema || {};
@ -39,7 +59,8 @@ export function createASTFromJSONSchema(jsonSchema: JsonSchema): ASTNodeJSON | u
return ASTFactory.createInteger();
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;
}
}

View File

@ -1 +0,0 @@
export * from './variable-plugin';

View File

@ -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'));
}
});
}
});
},
});

View File

@ -3,7 +3,7 @@ import React, { type SVGProps } from 'react';
import { Input, Button } from '@douyinfe/semi-ui';
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>) {
return (
@ -45,7 +45,7 @@ export function FxExpression(props: FxExpressionProps) {
value={value.content}
hasError={props.hasError}
style={{ flexGrow: 1 }}
onChange={v => onChange({ type: 'expression', content: v })}
onChange={(v) => onChange({ type: 'expression', content: v })}
disabled={disabled}
/>
) : (

View File

@ -5,7 +5,7 @@ import { IconCrossCircleStroked } from '@douyinfe/semi-icons';
import { TypeSelector } from '../type-selector';
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';
export interface PropertyEditProps {
@ -17,7 +17,7 @@ export interface PropertyEditProps {
onDelete?: () => void;
}
export const PropertyEdit: React.FC<PropertyEditProps> = props => {
export const PropertyEdit: React.FC<PropertyEditProps> = (props) => {
const { value, disabled } = props;
const [inputKey, updateKey] = useState(props.propertyKey);
const updateProperty = (key: keyof JsonSchema, val: any) => {
@ -34,12 +34,12 @@ export const PropertyEdit: React.FC<PropertyEditProps> = props => {
value={value.type}
disabled={disabled}
style={{ position: 'absolute', top: 6, left: 4, zIndex: 1 }}
onChange={val => updateProperty('type', val)}
onChange={(val) => updateProperty('type', val)}
/>
<Input
value={inputKey}
disabled={disabled}
onChange={v => updateKey(v.trim())}
onChange={(v) => updateKey(v.trim())}
onBlur={() => {
if (inputKey !== '') {
props.onChange(value, props.propertyKey, inputKey);
@ -54,14 +54,14 @@ export const PropertyEdit: React.FC<PropertyEditProps> = props => {
<VariableSelector
value={value.default}
disabled={disabled}
onChange={val => updateProperty('default', val)}
onChange={(val) => updateProperty('default', val)}
style={{ flexGrow: 1, height: 32 }}
/>
) : (
<Input
disabled={disabled}
value={value.default}
onChange={val => updateProperty('default', val)}
onChange={(val) => updateProperty('default', val)}
/>
)}
{props.onDelete && !disabled && (

View File

@ -2,7 +2,7 @@ import React from 'react';
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 {
value?: string;
@ -12,7 +12,7 @@ export interface TypeSelectorProps {
}
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 icon = VariableTypeIcons[value as any];
return (
@ -22,7 +22,7 @@ export const TypeSelector: React.FC<TypeSelectorProps> = props => {
disabled={disabled}
render={
<Dropdown.Menu>
{dropdownMenus.map(key => (
{dropdownMenus.map((key) => (
<Dropdown.Item
key={key}
onClick={() => {
@ -39,7 +39,7 @@ export const TypeSelector: React.FC<TypeSelectorProps> = props => {
<Tag
color="white"
style={props.style}
onClick={e => {
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
}}

View File

@ -1,7 +1,7 @@
import styled from 'styled-components';
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 {
name?: string | JSX.Element;

View File

@ -12,7 +12,7 @@ import { FreeLayoutProps } from '@flowgram.ai/free-layout-editor';
import { FlowNodeRegistry, FlowDocumentJSON } from '../typings';
import { shortcuts } from '../shortcuts';
import { createVariablePlugin } from '../plugins';
import { createSyncVariablePlugin } from '../plugins';
import { defaultFormMeta } from '../nodes/default-form-meta';
import { SelectorBoxPopover } from '../components/selector-box-popover';
import { BaseNode, LineAddButton, NodePanel } from '../components';
@ -184,7 +184,7 @@ export function useEditorProps(
* Variable plugin
*
*/
createVariablePlugin({}),
createSyncVariablePlugin({}),
/**
* Snap plugin
* 线

View File

@ -1 +1 @@
export { createVariablePlugin } from './variable-plugin/variable-plugin';
export { createSyncVariablePlugin } from './sync-variable-plugin/sync-variable-plugin';

View File

@ -0,0 +1 @@
export * from './sync-variable-plugin';

View File

@ -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'));
}
});
}
});
},
});

View File

@ -3,12 +3,32 @@ import { ASTFactory, type ASTNodeJSON } from '@flowgram.ai/free-layout-editor';
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>) {
return Object.entries(properties).sort(
(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 {
const { type } = jsonSchema || {};
@ -39,7 +59,8 @@ export function createASTFromJSONSchema(jsonSchema: JsonSchema): ASTNodeJSON | u
return ASTFactory.createInteger();
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;
}
}

View File

@ -1 +0,0 @@
export * from './variable-plugin';

View File

@ -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'));
}
});
}
});
},
});