mirror of
https://gitee.com/ByteDance/flowgram.ai.git
synced 2025-07-07 17:43:29 +08:00
Compare commits
3 Commits
7c43b0cfd0
...
099fd445f2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
099fd445f2 | ||
|
|
488013bb0b | ||
|
|
233b9befab |
@ -33,7 +33,7 @@ export function FormInputs() {
|
||||
hasError={Object.keys(fieldState?.errors || {}).length > 0}
|
||||
schema={property}
|
||||
/>
|
||||
<Feedback errors={fieldState?.errors} />
|
||||
<Feedback errors={fieldState?.errors} warnings={fieldState?.warnings} />
|
||||
</FormItem>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@ -3,7 +3,12 @@ import {
|
||||
provideJsonSchemaOutputs,
|
||||
syncVariableTitle,
|
||||
} from '@flowgram.ai/form-materials';
|
||||
import { FormRenderProps, FormMeta, ValidateTrigger } from '@flowgram.ai/fixed-layout-editor';
|
||||
import {
|
||||
FormRenderProps,
|
||||
FormMeta,
|
||||
ValidateTrigger,
|
||||
FeedbackLevel,
|
||||
} from '@flowgram.ai/fixed-layout-editor';
|
||||
|
||||
import { FlowNodeJSON } from '../typings';
|
||||
import { FormHeader, FormContent, FormInputs, FormOutputs } from '../form-components';
|
||||
@ -30,7 +35,10 @@ export const defaultFormMeta: FormMeta<FlowNodeJSON['data']> = {
|
||||
required.includes(valuePropetyKey) &&
|
||||
(value === '' || value === undefined || value?.content === '')
|
||||
) {
|
||||
return `${valuePropetyKey} is required`;
|
||||
return {
|
||||
message: `${valuePropetyKey} is required`,
|
||||
level: FeedbackLevel.Error, // Error || Warning
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
@ -33,7 +33,7 @@ export function FormInputs() {
|
||||
hasError={Object.keys(fieldState?.errors || {}).length > 0}
|
||||
schema={property}
|
||||
/>
|
||||
<Feedback errors={fieldState?.errors} />
|
||||
<Feedback errors={fieldState?.errors} warnings={fieldState?.warnings} />
|
||||
</FormItem>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import { FormRenderProps, FormMeta, ValidateTrigger } from '@flowgram.ai/free-layout-editor';
|
||||
import {
|
||||
FormRenderProps,
|
||||
FormMeta,
|
||||
ValidateTrigger,
|
||||
FeedbackLevel,
|
||||
} from '@flowgram.ai/free-layout-editor';
|
||||
import {
|
||||
autoRenameRefEffect,
|
||||
provideJsonSchemaOutputs,
|
||||
@ -30,7 +35,10 @@ export const defaultFormMeta: FormMeta<FlowNodeJSON> = {
|
||||
required.includes(valuePropetyKey) &&
|
||||
(value === '' || value === undefined || value?.content === '')
|
||||
) {
|
||||
return `${valuePropetyKey} is required`;
|
||||
return {
|
||||
message: `${valuePropetyKey} is required`,
|
||||
level: FeedbackLevel.Error, // Error || Warning
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
@ -3,5 +3,6 @@
|
||||
"node",
|
||||
"line",
|
||||
"port",
|
||||
"sub-canvas"
|
||||
"sub-canvas",
|
||||
"background"
|
||||
]
|
||||
|
||||
270
apps/docs/src/en/guide/advanced/free-layout/background.mdx
Normal file
270
apps/docs/src/en/guide/advanced/free-layout/background.mdx
Normal file
@ -0,0 +1,270 @@
|
||||
# Background
|
||||
|
||||
The background plugin is used to customize canvas background effects, supporting dot patterns, logo display, and neumorphism visual effects.
|
||||
|
||||
## Background Configuration
|
||||
|
||||
The background plugin is provided through `BackgroundPlugin`, configuration options include:
|
||||
|
||||
### Basic Configuration
|
||||
|
||||
<img loading="lazy" className="invert-img" src="/free-layout/background-color.png"/>
|
||||
|
||||
```ts pure
|
||||
{
|
||||
// Background color
|
||||
backgroundColor: '#1a1a1a',
|
||||
|
||||
// Dot color
|
||||
dotColor: '#ffffff',
|
||||
|
||||
// Dot size (pixels)
|
||||
dotSize: 1,
|
||||
|
||||
// Grid spacing (pixels)
|
||||
gridSize: 20,
|
||||
|
||||
// Dot opacity (0-1)
|
||||
dotOpacity: 0.5,
|
||||
|
||||
// Dot fill color
|
||||
dotFillColor: '#ffffff'
|
||||
}
|
||||
```
|
||||
|
||||
### Logo Configuration
|
||||
|
||||
Supports both text and image logo types:
|
||||
|
||||
<img loading="lazy" className="invert-img" src="/free-layout/background-logo.png"/>
|
||||
|
||||
```ts pure
|
||||
{
|
||||
logo: {
|
||||
// Logo text
|
||||
text: 'FLOWGRAM.AI',
|
||||
|
||||
// Image URL (optional, higher priority than text)
|
||||
imageUrl: 'https://example.com/logo.png',
|
||||
|
||||
// Position: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
|
||||
position: 'center',
|
||||
|
||||
// Size
|
||||
size: 200,
|
||||
|
||||
// Opacity (0-1)
|
||||
opacity: 0.25,
|
||||
|
||||
// Color
|
||||
color: '#ffffff',
|
||||
|
||||
// Font family
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
|
||||
// Font weight
|
||||
fontWeight: 'bold',
|
||||
|
||||
// Custom offset
|
||||
offset: { x: 0, y: 0 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Neumorphism Effect
|
||||
|
||||
Neumorphism is a modern visual design style that creates depth through dual soft shadows:
|
||||
|
||||
<img loading="lazy" className="invert-img" src="/free-layout/background-neumorphism.png"/>
|
||||
|
||||
```ts pure
|
||||
{
|
||||
logo: {
|
||||
neumorphism: {
|
||||
// Enable neumorphism effect
|
||||
enabled: true,
|
||||
|
||||
// Text color
|
||||
textColor: '#E0E0E0',
|
||||
|
||||
// Light shadow color
|
||||
lightShadowColor: 'rgba(255,255,255,0.9)',
|
||||
|
||||
// Dark shadow color
|
||||
darkShadowColor: 'rgba(0,0,0,0.15)',
|
||||
|
||||
// Shadow offset distance
|
||||
shadowOffset: 6,
|
||||
|
||||
// Shadow blur radius
|
||||
shadowBlur: 12,
|
||||
|
||||
// Shadow intensity
|
||||
intensity: 0.6,
|
||||
|
||||
// Raised effect (true=raised, false=inset)
|
||||
raised: true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Usage Example
|
||||
|
||||
```tsx pure
|
||||
// Use background property directly in editor configuration
|
||||
const editorProps = {
|
||||
// Background configuration
|
||||
background: {
|
||||
// Dark theme background
|
||||
backgroundColor: '#1a1a1a',
|
||||
dotColor: '#ffffff',
|
||||
dotSize: 1,
|
||||
gridSize: 20,
|
||||
dotOpacity: 0.3,
|
||||
|
||||
// Brand logo
|
||||
logo: {
|
||||
text: 'FLOWGRAM.AI',
|
||||
position: 'center',
|
||||
size: 200,
|
||||
opacity: 0.25,
|
||||
color: '#ffffff',
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
fontWeight: 'bold',
|
||||
|
||||
// Neumorphism effect
|
||||
neumorphism: {
|
||||
enabled: true,
|
||||
textColor: '#E0E0E0',
|
||||
lightShadowColor: 'rgba(255,255,255,0.9)',
|
||||
darkShadowColor: 'rgba(0,0,0,0.15)',
|
||||
shadowOffset: 6,
|
||||
shadowBlur: 12,
|
||||
intensity: 0.6,
|
||||
raised: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Preset Styles
|
||||
|
||||
### Classic Dark Theme
|
||||
|
||||
```tsx pure
|
||||
const editorProps = {
|
||||
background: {
|
||||
backgroundColor: '#1a1a1a',
|
||||
dotColor: '#ffffff',
|
||||
dotSize: 1,
|
||||
gridSize: 20,
|
||||
dotOpacity: 0.3,
|
||||
logo: {
|
||||
text: 'Your Brand',
|
||||
position: 'center',
|
||||
size: 200,
|
||||
opacity: 0.25,
|
||||
color: '#ffffff',
|
||||
neumorphism: {
|
||||
enabled: true,
|
||||
textColor: '#E0E0E0',
|
||||
lightShadowColor: 'rgba(255,255,255,0.9)',
|
||||
darkShadowColor: 'rgba(0,0,0,0.15)',
|
||||
shadowOffset: 6,
|
||||
shadowBlur: 12,
|
||||
intensity: 0.6,
|
||||
raised: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Minimal White Theme
|
||||
|
||||
```tsx pure
|
||||
const editorProps = {
|
||||
background: {
|
||||
backgroundColor: '#ffffff',
|
||||
dotColor: '#000000',
|
||||
dotSize: 1,
|
||||
gridSize: 20,
|
||||
dotOpacity: 0.1,
|
||||
logo: {
|
||||
text: 'Your Brand',
|
||||
position: 'center',
|
||||
size: 200,
|
||||
opacity: 0.1,
|
||||
color: '#000000'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
1. **Color Matching**: Ensure sufficient contrast between logo color and background color
|
||||
2. **Opacity Settings**: Logo opacity should not be too high to avoid affecting content readability
|
||||
3. **Neumorphism Effect**: Shadow parameters should be adjusted reasonably, overly strong effects may distract attention
|
||||
4. **Performance Considerations**: Complex shadow effects may impact rendering performance, consider simplifying on low-end devices
|
||||
|
||||
## Type Definitions
|
||||
|
||||
```ts
|
||||
interface BackgroundLayerOptions {
|
||||
/** Grid spacing, default 20px */
|
||||
gridSize?: number;
|
||||
/** Dot size, default 1px */
|
||||
dotSize?: number;
|
||||
/** Dot color, default "#eceeef" */
|
||||
dotColor?: string;
|
||||
/** Dot opacity, default 0.5 */
|
||||
dotOpacity?: number;
|
||||
/** Background color, default transparent */
|
||||
backgroundColor?: string;
|
||||
/** Dot fill color, default same as stroke color */
|
||||
dotFillColor?: string;
|
||||
/** Logo configuration */
|
||||
logo?: {
|
||||
/** Logo text content */
|
||||
text?: string;
|
||||
/** Logo image URL */
|
||||
imageUrl?: string;
|
||||
/** Logo position, default 'center' */
|
||||
position?: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
||||
/** Logo size, default 'medium' */
|
||||
size?: 'small' | 'medium' | 'large' | number;
|
||||
/** Logo opacity, default 0.1 */
|
||||
opacity?: number;
|
||||
/** Logo color (text only), default "#cccccc" */
|
||||
color?: string;
|
||||
/** Logo font family (text only), default 'Arial, sans-serif' */
|
||||
fontFamily?: string;
|
||||
/** Logo font weight (text only), default 'normal' */
|
||||
fontWeight?: 'normal' | 'bold' | 'lighter' | number;
|
||||
/** Custom offset */
|
||||
offset?: { x: number; y: number };
|
||||
/** Neumorphism effect configuration */
|
||||
neumorphism?: {
|
||||
/** Enable neumorphism effect */
|
||||
enabled: boolean;
|
||||
/** Text color */
|
||||
textColor?: string;
|
||||
/** Light shadow color */
|
||||
lightShadowColor?: string;
|
||||
/** Dark shadow color */
|
||||
darkShadowColor?: string;
|
||||
/** Shadow offset distance */
|
||||
shadowOffset?: number;
|
||||
/** Shadow blur radius */
|
||||
shadowBlur?: number;
|
||||
/** Shadow intensity */
|
||||
intensity?: number;
|
||||
/** Raised effect (true=raised, false=inset) */
|
||||
raised?: boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
BIN
apps/docs/src/public/free-layout/background-color.png
Normal file
BIN
apps/docs/src/public/free-layout/background-color.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 143 KiB |
BIN
apps/docs/src/public/free-layout/background-logo.png
Normal file
BIN
apps/docs/src/public/free-layout/background-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 181 KiB |
BIN
apps/docs/src/public/free-layout/background-neumorphism.png
Normal file
BIN
apps/docs/src/public/free-layout/background-neumorphism.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
@ -3,5 +3,6 @@
|
||||
"node",
|
||||
"line",
|
||||
"port",
|
||||
"sub-canvas"
|
||||
"sub-canvas",
|
||||
"background"
|
||||
]
|
||||
|
||||
270
apps/docs/src/zh/guide/advanced/free-layout/background.mdx
Normal file
270
apps/docs/src/zh/guide/advanced/free-layout/background.mdx
Normal file
@ -0,0 +1,270 @@
|
||||
# 背景
|
||||
|
||||
背景插件用于自定义画布的背景效果,支持点阵背景、Logo显示和新拟态(Neumorphism)视觉效果。
|
||||
|
||||
## 背景配置
|
||||
|
||||
背景插件通过 `BackgroundPlugin` 提供,配置项包括:
|
||||
|
||||
### 基础配置
|
||||
|
||||
<img loading="lazy" className="invert-img" src="/free-layout/background-color.png"/>
|
||||
|
||||
```ts pure
|
||||
{
|
||||
// 背景颜色
|
||||
backgroundColor: '#1a1a1a',
|
||||
|
||||
// 点的颜色
|
||||
dotColor: '#ffffff',
|
||||
|
||||
// 点的大小(像素)
|
||||
dotSize: 1,
|
||||
|
||||
// 网格间距(像素)
|
||||
gridSize: 20,
|
||||
|
||||
// 点的透明度(0-1)
|
||||
dotOpacity: 0.5,
|
||||
|
||||
// 点的填充颜色
|
||||
dotFillColor: '#ffffff'
|
||||
}
|
||||
```
|
||||
|
||||
### Logo配置
|
||||
|
||||
支持文本和图片两种Logo类型:
|
||||
|
||||
<img loading="lazy" className="invert-img" src="/free-layout/background-logo.png"/>
|
||||
|
||||
```ts pure
|
||||
{
|
||||
logo: {
|
||||
// Logo文本
|
||||
text: 'FLOWGRAM.AI',
|
||||
|
||||
// 图片URL (可选,优先级高于文本)
|
||||
imageUrl: 'https://example.com/logo.png',
|
||||
|
||||
// 位置:'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
|
||||
position: 'center',
|
||||
|
||||
// 大小
|
||||
size: 200,
|
||||
|
||||
// 透明度(0-1)
|
||||
opacity: 0.25,
|
||||
|
||||
// 颜色
|
||||
color: '#ffffff',
|
||||
|
||||
// 字体
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
|
||||
// 字体粗细
|
||||
fontWeight: 'bold',
|
||||
|
||||
// 自定义偏移量
|
||||
offset: { x: 0, y: 0 }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 新拟态效果
|
||||
|
||||
新拟态(Neumorphism)是一种现代化的视觉设计风格,通过双层柔和阴影创造立体感:
|
||||
|
||||
<img loading="lazy" className="invert-img" src="/free-layout/background-neumorphism.png"/>
|
||||
|
||||
```ts pure
|
||||
{
|
||||
logo: {
|
||||
neumorphism: {
|
||||
// 启用新拟态效果
|
||||
enabled: true,
|
||||
|
||||
// 文字颜色
|
||||
textColor: '#E0E0E0',
|
||||
|
||||
// 高光阴影颜色
|
||||
lightShadowColor: 'rgba(255,255,255,0.9)',
|
||||
|
||||
// 暗色阴影颜色
|
||||
darkShadowColor: 'rgba(0,0,0,0.15)',
|
||||
|
||||
// 阴影偏移距离
|
||||
shadowOffset: 6,
|
||||
|
||||
// 阴影模糊半径
|
||||
shadowBlur: 12,
|
||||
|
||||
// 阴影强度
|
||||
intensity: 0.6,
|
||||
|
||||
// 凸起效果(true=凸起, false=凹陷)
|
||||
raised: true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
```tsx pure
|
||||
// 在编辑器配置中直接使用 background 属性
|
||||
const editorProps = {
|
||||
// 背景配置
|
||||
background: {
|
||||
// 深色主题背景
|
||||
backgroundColor: '#1a1a1a',
|
||||
dotColor: '#ffffff',
|
||||
dotSize: 1,
|
||||
gridSize: 20,
|
||||
dotOpacity: 0.3,
|
||||
|
||||
// 品牌Logo
|
||||
logo: {
|
||||
text: 'FLOWGRAM.AI',
|
||||
position: 'center',
|
||||
size: 200,
|
||||
opacity: 0.25,
|
||||
color: '#ffffff',
|
||||
fontFamily: 'Arial, sans-serif',
|
||||
fontWeight: 'bold',
|
||||
|
||||
// 新拟态效果
|
||||
neumorphism: {
|
||||
enabled: true,
|
||||
textColor: '#E0E0E0',
|
||||
lightShadowColor: 'rgba(255,255,255,0.9)',
|
||||
darkShadowColor: 'rgba(0,0,0,0.15)',
|
||||
shadowOffset: 6,
|
||||
shadowBlur: 12,
|
||||
intensity: 0.6,
|
||||
raised: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 预设样式
|
||||
|
||||
### 经典黑色主题
|
||||
|
||||
```tsx pure
|
||||
const editorProps = {
|
||||
background: {
|
||||
backgroundColor: '#1a1a1a',
|
||||
dotColor: '#ffffff',
|
||||
dotSize: 1,
|
||||
gridSize: 20,
|
||||
dotOpacity: 0.3,
|
||||
logo: {
|
||||
text: '您的品牌',
|
||||
position: 'center',
|
||||
size: 200,
|
||||
opacity: 0.25,
|
||||
color: '#ffffff',
|
||||
neumorphism: {
|
||||
enabled: true,
|
||||
textColor: '#E0E0E0',
|
||||
lightShadowColor: 'rgba(255,255,255,0.9)',
|
||||
darkShadowColor: 'rgba(0,0,0,0.15)',
|
||||
shadowOffset: 6,
|
||||
shadowBlur: 12,
|
||||
intensity: 0.6,
|
||||
raised: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 简约白色主题
|
||||
|
||||
```tsx pure
|
||||
const editorProps = {
|
||||
background: {
|
||||
backgroundColor: '#ffffff',
|
||||
dotColor: '#000000',
|
||||
dotSize: 1,
|
||||
gridSize: 20,
|
||||
dotOpacity: 0.1,
|
||||
logo: {
|
||||
text: '您的品牌',
|
||||
position: 'center',
|
||||
size: 200,
|
||||
opacity: 0.1,
|
||||
color: '#000000'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **颜色搭配**:确保Logo颜色与背景色有足够的对比度
|
||||
2. **透明度设置**:Logo透明度不宜过高,以免影响内容可读性
|
||||
3. **新拟态效果**:需要合理调整阴影参数,过强的效果可能分散注意力
|
||||
4. **性能考虑**:复杂的阴影效果可能影响渲染性能,建议在低端设备上适当简化
|
||||
|
||||
## 类型定义
|
||||
|
||||
```ts
|
||||
interface BackgroundLayerOptions {
|
||||
/** 网格间距,默认 20px */
|
||||
gridSize?: number;
|
||||
/** 点的大小,默认 1px */
|
||||
dotSize?: number;
|
||||
/** 点的颜色,默认 "#eceeef" */
|
||||
dotColor?: string;
|
||||
/** 点的透明度,默认 0.5 */
|
||||
dotOpacity?: number;
|
||||
/** 背景颜色,默认透明 */
|
||||
backgroundColor?: string;
|
||||
/** 点的填充颜色,默认与stroke颜色相同 */
|
||||
dotFillColor?: string;
|
||||
/** Logo 配置 */
|
||||
logo?: {
|
||||
/** Logo 文本内容 */
|
||||
text?: string;
|
||||
/** Logo 图片 URL */
|
||||
imageUrl?: string;
|
||||
/** Logo 位置,默认 'center' */
|
||||
position?: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
||||
/** Logo 大小,默认 'medium' */
|
||||
size?: 'small' | 'medium' | 'large' | number;
|
||||
/** Logo 透明度,默认 0.1 */
|
||||
opacity?: number;
|
||||
/** Logo 颜色(仅文本),默认 "#cccccc" */
|
||||
color?: string;
|
||||
/** Logo 字体家族(仅文本),默认 'Arial, sans-serif' */
|
||||
fontFamily?: string;
|
||||
/** Logo 字体粗细(仅文本),默认 'normal' */
|
||||
fontWeight?: 'normal' | 'bold' | 'lighter' | number;
|
||||
/** 自定义偏移 */
|
||||
offset?: { x: number; y: number };
|
||||
/** 新拟态效果配置 */
|
||||
neumorphism?: {
|
||||
/** 启用新拟态效果 */
|
||||
enabled: boolean;
|
||||
/** 文字颜色 */
|
||||
textColor?: string;
|
||||
/** 高光阴影颜色 */
|
||||
lightShadowColor?: string;
|
||||
/** 暗色阴影颜色 */
|
||||
darkShadowColor?: string;
|
||||
/** 阴影偏移距离 */
|
||||
shadowOffset?: number;
|
||||
/** 阴影模糊半径 */
|
||||
shadowBlur?: number;
|
||||
/** 阴影强度 */
|
||||
intensity?: number;
|
||||
/** 凸起效果(true=凸起, false=凹陷) */
|
||||
raised?: boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
@ -53,6 +53,7 @@ export {
|
||||
useFieldValidate,
|
||||
useWatch,
|
||||
ValidateTrigger,
|
||||
FeedbackLevel,
|
||||
} from '@flowgram.ai/form';
|
||||
export * from '@flowgram.ai/node';
|
||||
export { FormModelV2 as FormModel };
|
||||
|
||||
@ -21,7 +21,8 @@ export function createPlaygroundReactPreset<CTX extends PluginContext = PluginCo
|
||||
* 注册背景 (放前面插入), 默认打开
|
||||
*/
|
||||
if (opts.background || opts.background === undefined) {
|
||||
plugins.push(createBackgroundPlugin(opts.background || {}));
|
||||
const backgroundOptions = typeof opts.background === 'object' ? opts.background : {};
|
||||
plugins.push(createBackgroundPlugin(backgroundOptions));
|
||||
}
|
||||
/**
|
||||
* 注册快捷键
|
||||
|
||||
@ -38,6 +38,7 @@ export function JsonSchemaEditor(props: {
|
||||
value?: IJsonSchema;
|
||||
onChange?: (value: IJsonSchema) => void;
|
||||
config?: ConfigType;
|
||||
className?: string;
|
||||
}) {
|
||||
const { value = { type: 'object' }, config = {}, onChange: onChangeProps } = props;
|
||||
const { propertyList, onAddProperty, onRemoveProperty, onEditProperty } = usePropertiesEdit(
|
||||
@ -46,7 +47,7 @@ export function JsonSchemaEditor(props: {
|
||||
);
|
||||
|
||||
return (
|
||||
<UIContainer>
|
||||
<UIContainer className={props.className}>
|
||||
<UIProperties>
|
||||
{propertyList.map((_property, index) => (
|
||||
<PropertyEdit
|
||||
@ -63,7 +64,12 @@ export function JsonSchemaEditor(props: {
|
||||
/>
|
||||
))}
|
||||
</UIProperties>
|
||||
<Button size="small" style={{ marginTop: 10 }} icon={<IconPlus />} onClick={onAddProperty}>
|
||||
<Button
|
||||
size="small"
|
||||
style={{ marginTop: 10, marginLeft: 16 }}
|
||||
icon={<IconPlus />}
|
||||
onClick={onAddProperty}
|
||||
>
|
||||
{config?.addButtonText ?? 'Add'}
|
||||
</Button>
|
||||
</UIContainer>
|
||||
|
||||
@ -361,8 +361,8 @@ export class FieldModel<TValue extends FieldValue = FieldValue> implements Dispo
|
||||
|
||||
const groupedFeedbacks = groupBy(feedbacks, 'level');
|
||||
|
||||
warnings = warnings.concat(groupedFeedbacks[FeedbackLevel.Warning] as FieldWarning[]);
|
||||
errors = errors.concat(groupedFeedbacks[FeedbackLevel.Error] as FieldError[]);
|
||||
warnings = warnings.concat((groupedFeedbacks[FeedbackLevel.Warning] as FieldWarning[]) || []);
|
||||
errors = errors.concat((groupedFeedbacks[FeedbackLevel.Error] as FieldError[]) || []);
|
||||
}
|
||||
|
||||
return { errors, warnings };
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
OnFormValuesUpdatedPayload,
|
||||
} from '../types/form';
|
||||
import { FieldName, FieldValue } from '../types/field';
|
||||
import { Errors, FormValidateReturn, Warnings } from '../types';
|
||||
import { Errors, FeedbackLevel, FormValidateReturn, Warnings } from '../types';
|
||||
import { createFormModelState } from '../constants';
|
||||
import { getValidByErrors, mergeFeedbacks } from './utils';
|
||||
import { Store } from './store';
|
||||
@ -281,8 +281,14 @@ export class FormModel<TValues = any> implements Disposable {
|
||||
const feedback = toFeedback(result, path);
|
||||
const field = this.getField(path);
|
||||
|
||||
const errors = feedbackToFieldErrorsOrWarnings<Errors>(path, feedback);
|
||||
const warnings = feedbackToFieldErrorsOrWarnings<Warnings>(path, feedback);
|
||||
const errors = feedbackToFieldErrorsOrWarnings<Errors>(
|
||||
path,
|
||||
feedback?.level === FeedbackLevel.Error ? feedback : undefined
|
||||
);
|
||||
const warnings = feedbackToFieldErrorsOrWarnings<Warnings>(
|
||||
path,
|
||||
feedback?.level === FeedbackLevel.Warning ? feedback : undefined
|
||||
);
|
||||
|
||||
if (field) {
|
||||
field.state.errors = errors;
|
||||
|
||||
@ -19,7 +19,7 @@ export type {
|
||||
Warnings,
|
||||
} from './types';
|
||||
|
||||
export { ValidateTrigger } from './types';
|
||||
export { ValidateTrigger, FeedbackLevel } from './types';
|
||||
export { createForm, type CreateFormOptions } from './core/create-form';
|
||||
export { Glob } from './utils';
|
||||
export * from './core';
|
||||
|
||||
@ -24,7 +24,7 @@ export interface Feedback<FeedbackLevel> {
|
||||
/**
|
||||
* Feedback message
|
||||
*/
|
||||
message: string;
|
||||
message: string | React.ReactNode;
|
||||
}
|
||||
|
||||
export type FieldError = Feedback<FeedbackLevel.Error>;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Layer, observeEntity, PlaygroundConfigEntity, SCALE_WIDTH } from '@flowgram.ai/core';
|
||||
import { domUtils } from '@flowgram.ai/utils';
|
||||
import { Layer, observeEntity, PlaygroundConfigEntity, SCALE_WIDTH } from '@flowgram.ai/core';
|
||||
|
||||
interface BackgroundScaleUnit {
|
||||
realSize: number;
|
||||
@ -8,12 +8,67 @@ interface BackgroundScaleUnit {
|
||||
}
|
||||
|
||||
const PATTERN_PREFIX = 'gedit-background-pattern-';
|
||||
const RENDER_SIZE = 20;
|
||||
const DOT_SIZE = 1;
|
||||
const DEFAULT_RENDER_SIZE = 20;
|
||||
const DEFAULT_DOT_SIZE = 1;
|
||||
let id = 0;
|
||||
|
||||
export interface BackgroundLayerOptions {
|
||||
// 预留配置项目
|
||||
/** 网格间距,默认 20px */
|
||||
gridSize?: number;
|
||||
/** 点的大小,默认 1px */
|
||||
dotSize?: number;
|
||||
/** 点的颜色,默认 "#eceeef" */
|
||||
dotColor?: string;
|
||||
/** 点的透明度,默认 0.5 */
|
||||
dotOpacity?: number;
|
||||
/** 背景颜色,默认透明 */
|
||||
backgroundColor?: string;
|
||||
/** 点的填充颜色,默认与stroke颜色相同 */
|
||||
dotFillColor?: string;
|
||||
/** Logo 配置 */
|
||||
logo?: {
|
||||
/** Logo 文本内容 */
|
||||
text?: string;
|
||||
/** Logo 图片 URL */
|
||||
imageUrl?: string;
|
||||
/** Logo 位置,默认 'center' */
|
||||
position?: 'center' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
||||
/** Logo 大小,默认 'medium' */
|
||||
size?: 'small' | 'medium' | 'large' | number;
|
||||
/** Logo 透明度,默认 0.1 */
|
||||
opacity?: number;
|
||||
/** Logo 颜色(仅文本),默认 "#cccccc" */
|
||||
color?: string;
|
||||
/** Logo 字体大小(仅文本),默认根据 size 计算 */
|
||||
fontSize?: number;
|
||||
/** Logo 字体家族(仅文本),默认 'Arial, sans-serif' */
|
||||
fontFamily?: string;
|
||||
/** Logo 字体粗细(仅文本),默认 'normal' */
|
||||
fontWeight?: 'normal' | 'bold' | 'lighter' | number;
|
||||
/** 自定义偏移 */
|
||||
offset?: { x: number; y: number };
|
||||
/** 新拟态(Neumorphism)效果配置 */
|
||||
neumorphism?: {
|
||||
/** 是否启用新拟态效果,默认 false */
|
||||
enabled?: boolean;
|
||||
/** 主要文字颜色,应该与背景色接近,默认自动计算 */
|
||||
textColor?: string;
|
||||
/** 亮色阴影颜色,默认自动计算(背景色的亮色版本) */
|
||||
lightShadowColor?: string;
|
||||
/** 暗色阴影颜色,默认自动计算(背景色的暗色版本) */
|
||||
darkShadowColor?: string;
|
||||
/** 阴影偏移距离,默认 6 */
|
||||
shadowOffset?: number;
|
||||
/** 阴影模糊半径,默认 12 */
|
||||
shadowBlur?: number;
|
||||
/** 效果强度(0-1),影响阴影的透明度,默认 0.3 */
|
||||
intensity?: number;
|
||||
/** 凸起效果(true)还是凹陷效果(false),默认 true */
|
||||
raised?: boolean;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* dot 网格背景
|
||||
*/
|
||||
@ -29,6 +84,55 @@ export class BackgroundLayer extends Layer<BackgroundLayerOptions> {
|
||||
|
||||
grid: HTMLElement = document.createElement('div');
|
||||
|
||||
/**
|
||||
* 获取网格大小配置
|
||||
*/
|
||||
private get gridSize(): number {
|
||||
return this.options.gridSize ?? DEFAULT_RENDER_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取点大小配置
|
||||
*/
|
||||
private get dotSize(): number {
|
||||
return this.options.dotSize ?? DEFAULT_DOT_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取点颜色配置
|
||||
*/
|
||||
private get dotColor(): string {
|
||||
return this.options.dotColor ?? '#eceeef';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取点透明度配置
|
||||
*/
|
||||
private get dotOpacity(): number {
|
||||
return this.options.dotOpacity ?? 0.5;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取背景颜色配置
|
||||
*/
|
||||
private get backgroundColor(): string {
|
||||
return this.options.backgroundColor ?? 'transparent';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取点填充颜色配置
|
||||
*/
|
||||
private get dotFillColor(): string {
|
||||
return this.options.dotFillColor ?? this.dotColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Logo配置
|
||||
*/
|
||||
private get logoConfig() {
|
||||
return this.options.logo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前缩放比
|
||||
*/
|
||||
@ -50,6 +154,11 @@ export class BackgroundLayer extends Layer<BackgroundLayerOptions> {
|
||||
this.grid.style.position = 'relative';
|
||||
this.node.appendChild(this.grid);
|
||||
this.grid.className = 'gedit-grid-svg';
|
||||
|
||||
// 设置背景颜色
|
||||
if (this.backgroundColor !== 'transparent') {
|
||||
this.node.style.backgroundColor = this.backgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,8 +168,8 @@ export class BackgroundLayer extends Layer<BackgroundLayerOptions> {
|
||||
const { zoom } = this;
|
||||
|
||||
return {
|
||||
realSize: RENDER_SIZE, // 一个单元格代表的真实大小
|
||||
renderSize: Math.round(RENDER_SIZE * zoom * 100) / 100, // 一个单元格渲染的大小值
|
||||
realSize: this.gridSize, // 使用配置的网格大小
|
||||
renderSize: Math.round(this.gridSize * zoom * 100) / 100, // 一个单元格渲染的大小值
|
||||
zoom, // 缩放比
|
||||
};
|
||||
}
|
||||
@ -82,7 +191,7 @@ export class BackgroundLayer extends Layer<BackgroundLayerOptions> {
|
||||
left: scrollX - SCALE_WIDTH,
|
||||
top: scrollY - SCALE_WIDTH,
|
||||
});
|
||||
this.drawGrid(scaleUnit);
|
||||
this.drawGrid(scaleUnit, viewBoxWidth, viewBoxHeight);
|
||||
// 设置网格
|
||||
this.setSVGStyle(this.grid, {
|
||||
width: viewBoxWidth,
|
||||
@ -92,34 +201,294 @@ export class BackgroundLayer extends Layer<BackgroundLayerOptions> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算Logo位置
|
||||
*/
|
||||
private calculateLogoPosition(
|
||||
viewBoxWidth: number,
|
||||
viewBoxHeight: number
|
||||
): { x: number; y: number } {
|
||||
if (!this.logoConfig) return { x: 0, y: 0 };
|
||||
|
||||
const { position = 'center', offset = { x: 0, y: 0 } } = this.logoConfig;
|
||||
const playgroundConfig = this.playgroundConfigEntity.config;
|
||||
const scaleUnit = this.getScaleUnit();
|
||||
const mod = scaleUnit.renderSize * 10;
|
||||
|
||||
// 计算SVG内的相对位置,使Logo相对于可视区域固定
|
||||
const { scrollX, scrollY } = playgroundConfig;
|
||||
const scrollXDelta = this.getScrollDelta(scrollX, mod);
|
||||
const scrollYDelta = this.getScrollDelta(scrollY, mod);
|
||||
|
||||
// 可视区域的基准点(相对于SVG坐标系)
|
||||
const visibleLeft = mod + scrollXDelta;
|
||||
const visibleTop = mod + scrollYDelta;
|
||||
const visibleCenterX = visibleLeft + playgroundConfig.width / 2;
|
||||
const visibleCenterY = visibleTop + playgroundConfig.height / 2;
|
||||
|
||||
let x = 0,
|
||||
y = 0;
|
||||
|
||||
switch (position) {
|
||||
case 'center':
|
||||
x = visibleCenterX;
|
||||
y = visibleCenterY;
|
||||
break;
|
||||
case 'top-left':
|
||||
x = visibleLeft + 100;
|
||||
y = visibleTop + 100;
|
||||
break;
|
||||
case 'top-right':
|
||||
x = visibleLeft + playgroundConfig.width - 100;
|
||||
y = visibleTop + 100;
|
||||
break;
|
||||
case 'bottom-left':
|
||||
x = visibleLeft + 100;
|
||||
y = visibleTop + playgroundConfig.height - 100;
|
||||
break;
|
||||
case 'bottom-right':
|
||||
x = visibleLeft + playgroundConfig.width - 100;
|
||||
y = visibleTop + playgroundConfig.height - 100;
|
||||
break;
|
||||
}
|
||||
|
||||
return { x: x + offset.x, y: y + offset.y };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Logo大小
|
||||
*/
|
||||
private getLogoSize(): number {
|
||||
if (!this.logoConfig) return 0;
|
||||
|
||||
const { size = 'medium' } = this.logoConfig;
|
||||
|
||||
if (typeof size === 'number') {
|
||||
return size;
|
||||
}
|
||||
|
||||
switch (size) {
|
||||
case 'small':
|
||||
return 24;
|
||||
case 'medium':
|
||||
return 48;
|
||||
case 'large':
|
||||
return 72;
|
||||
default:
|
||||
return 48;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 颜色工具函数:将十六进制颜色转换为RGB
|
||||
*/
|
||||
private hexToRgb(hex: string): { r: number; g: number; b: number } | null {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result
|
||||
? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16),
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 颜色工具函数:调整颜色亮度
|
||||
*/
|
||||
private adjustBrightness(hex: string, percent: number): string {
|
||||
const rgb = this.hexToRgb(hex);
|
||||
if (!rgb) return hex;
|
||||
|
||||
const adjust = (value: number) => {
|
||||
const adjusted = Math.round(value + (255 - value) * percent);
|
||||
return Math.max(0, Math.min(255, adjusted));
|
||||
};
|
||||
|
||||
return `#${adjust(rgb.r).toString(16).padStart(2, '0')}${adjust(rgb.g)
|
||||
.toString(16)
|
||||
.padStart(2, '0')}${adjust(rgb.b).toString(16).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成新拟态阴影滤镜
|
||||
*/
|
||||
private generateNeumorphismFilter(
|
||||
filterId: string,
|
||||
lightShadow: string,
|
||||
darkShadow: string,
|
||||
offset: number,
|
||||
blur: number,
|
||||
intensity: number,
|
||||
raised: boolean
|
||||
): string {
|
||||
const lightOffset = raised ? -offset : offset;
|
||||
const darkOffset = raised ? offset : -offset;
|
||||
|
||||
return `
|
||||
<defs>
|
||||
<filter id="${filterId}" x="-50%" y="-50%" width="200%" height="200%">
|
||||
<feDropShadow dx="${lightOffset}" dy="${lightOffset}" stdDeviation="${blur}" flood-color="${lightShadow}" flood-opacity="${intensity}"/>
|
||||
<feDropShadow dx="${darkOffset}" dy="${darkOffset}" stdDeviation="${blur}" flood-color="${darkShadow}" flood-opacity="${intensity}"/>
|
||||
</filter>
|
||||
</defs>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制Logo SVG内容
|
||||
*/
|
||||
private generateLogoSVG(viewBoxWidth: number, viewBoxHeight: number): string {
|
||||
if (!this.logoConfig) return '';
|
||||
|
||||
const {
|
||||
text,
|
||||
imageUrl,
|
||||
opacity = 0.1,
|
||||
color = '#cccccc',
|
||||
fontSize,
|
||||
fontFamily = 'Arial, sans-serif',
|
||||
fontWeight = 'normal',
|
||||
neumorphism,
|
||||
} = this.logoConfig;
|
||||
const position = this.calculateLogoPosition(viewBoxWidth, viewBoxHeight);
|
||||
const logoSize = this.getLogoSize();
|
||||
|
||||
let logoSVG = '';
|
||||
|
||||
if (imageUrl) {
|
||||
// 图片Logo(暂不支持3D效果)
|
||||
logoSVG = `
|
||||
<image
|
||||
href="${imageUrl}"
|
||||
x="${position.x - logoSize / 2}"
|
||||
y="${position.y - logoSize / 2}"
|
||||
width="${logoSize}"
|
||||
height="${logoSize}"
|
||||
opacity="${opacity}"
|
||||
/>`;
|
||||
} else if (text) {
|
||||
// 文本Logo
|
||||
const actualFontSize = fontSize ?? Math.max(logoSize / 2, 12);
|
||||
|
||||
// 检查是否启用新拟态效果
|
||||
if (neumorphism?.enabled) {
|
||||
const {
|
||||
textColor,
|
||||
lightShadowColor,
|
||||
darkShadowColor,
|
||||
shadowOffset = 6,
|
||||
shadowBlur = 12,
|
||||
intensity = 0.3,
|
||||
raised = true,
|
||||
} = neumorphism;
|
||||
|
||||
// 自动计算颜色(如果未提供)
|
||||
const bgColor = this.backgroundColor !== 'transparent' ? this.backgroundColor : '#f0f0f0';
|
||||
const finalTextColor = textColor || bgColor;
|
||||
const finalLightShadow = lightShadowColor || this.adjustBrightness(bgColor, 0.2);
|
||||
const finalDarkShadow = darkShadowColor || this.adjustBrightness(bgColor, -0.2);
|
||||
|
||||
const filterId = `neumorphism-${this._patternId}`;
|
||||
|
||||
// 添加新拟态滤镜定义
|
||||
logoSVG += this.generateNeumorphismFilter(
|
||||
filterId,
|
||||
finalLightShadow,
|
||||
finalDarkShadow,
|
||||
shadowOffset,
|
||||
shadowBlur,
|
||||
intensity,
|
||||
raised
|
||||
);
|
||||
|
||||
// 创建新拟态文本
|
||||
logoSVG += `
|
||||
<text
|
||||
x="${position.x}"
|
||||
y="${position.y}"
|
||||
font-family="${fontFamily}"
|
||||
font-size="${actualFontSize}"
|
||||
font-weight="${fontWeight}"
|
||||
fill="${finalTextColor}"
|
||||
opacity="${opacity}"
|
||||
text-anchor="middle"
|
||||
dominant-baseline="middle"
|
||||
filter="url(#${filterId})"
|
||||
>${text}</text>`;
|
||||
} else {
|
||||
// 普通文本(无3D效果)
|
||||
logoSVG = `
|
||||
<text
|
||||
x="${position.x}"
|
||||
y="${position.y}"
|
||||
font-family="${fontFamily}"
|
||||
font-size="${actualFontSize}"
|
||||
font-weight="${fontWeight}"
|
||||
fill="${color}"
|
||||
opacity="${opacity}"
|
||||
text-anchor="middle"
|
||||
dominant-baseline="middle"
|
||||
>${text}</text>`;
|
||||
}
|
||||
}
|
||||
|
||||
return logoSVG;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制网格
|
||||
*/
|
||||
protected drawGrid(unit: BackgroundScaleUnit): void {
|
||||
protected drawGrid(unit: BackgroundScaleUnit, viewBoxWidth: number, viewBoxHeight: number): void {
|
||||
const minor = unit.renderSize;
|
||||
if (!this.grid) {
|
||||
return;
|
||||
}
|
||||
const patternSize = DOT_SIZE * this.zoom;
|
||||
const newContent = `
|
||||
<svg width="100%" height="100%">
|
||||
const patternSize = this.dotSize * this.zoom;
|
||||
|
||||
// 构建SVG内容,根据是否有背景颜色决定是否添加背景矩形
|
||||
let svgContent = `<svg width="100%" height="100%">`;
|
||||
|
||||
// 如果设置了背景颜色,先绘制背景矩形
|
||||
if (this.backgroundColor !== 'transparent') {
|
||||
svgContent += `<rect width="100%" height="100%" fill="${this.backgroundColor}"/>`;
|
||||
}
|
||||
|
||||
// 添加点阵图案
|
||||
// 构建圆圈属性,保持与原始实现的兼容性
|
||||
const circleAttributes = [
|
||||
`cx="${patternSize}"`,
|
||||
`cy="${patternSize}"`,
|
||||
`r="${patternSize}"`,
|
||||
`stroke="${this.dotColor}"`,
|
||||
// 只有当 dotFillColor 被明确设置且与 dotColor 不同时才添加 fill 属性
|
||||
this.options.dotFillColor && this.dotFillColor !== this.dotColor
|
||||
? `fill="${this.dotFillColor}"`
|
||||
: '',
|
||||
`fill-opacity="${this.dotOpacity}"`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
|
||||
svgContent += `
|
||||
<pattern id="${this._patternId}" width="${minor}" height="${minor}" patternUnits="userSpaceOnUse">
|
||||
<circle
|
||||
cx="${patternSize}"
|
||||
cy="${patternSize}"
|
||||
r="${patternSize}"
|
||||
stroke="#eceeef"
|
||||
fill-opacity="0.5"
|
||||
/>
|
||||
<circle ${circleAttributes} />
|
||||
</pattern>
|
||||
<rect width="100%" height="100%" fill="url(#${this._patternId})"/>
|
||||
</svg>`;
|
||||
this.grid.innerHTML = newContent;
|
||||
<rect width="100%" height="100%" fill="url(#${this._patternId})"/>`;
|
||||
|
||||
// 添加Logo
|
||||
const logoSVG = this.generateLogoSVG(viewBoxWidth, viewBoxHeight);
|
||||
if (logoSVG) {
|
||||
svgContent += logoSVG;
|
||||
}
|
||||
|
||||
svgContent += `</svg>`;
|
||||
|
||||
this.grid.innerHTML = svgContent;
|
||||
}
|
||||
|
||||
protected setSVGStyle(
|
||||
svgElement: HTMLElement | undefined,
|
||||
style: { width: number; height: number; left: number; top: number },
|
||||
style: { width: number; height: number; left: number; top: number }
|
||||
): void {
|
||||
if (!svgElement) {
|
||||
return;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user