chore: add raw-loader fixed demo (#79)

This commit is contained in:
chenjiawei.inizio 2025-03-21 18:20:51 +08:00 committed by GitHub
parent d90fdad677
commit b77dfc9faf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 16 additions and 614 deletions

View File

@ -1,624 +1,22 @@
import { PreviewEditor } from '../preview-editor';
import { FixedLayoutSimple } from './index';
import nodeRegistriesCode from '!!raw-loader!@flowgram.ai/demo-fixed-layout-simple/src/node-registries.ts';
import initialDataCode from '!!raw-loader!@flowgram.ai/demo-fixed-layout-simple/src/initial-data.ts';
import indexCssCode from '!!raw-loader!@flowgram.ai/demo-fixed-layout-simple/src/index.css';
import useEditorPropsCode from '!!raw-loader!@flowgram.ai/demo-fixed-layout-simple/src/hooks/use-editor-props.tsx';
import editorCode from '!!raw-loader!@flowgram.ai/demo-fixed-layout-simple/src/editor.tsx';
import toolsCode from '!!raw-loader!@flowgram.ai/demo-fixed-layout-simple/src/components/tools.tsx';
import nodeAdderCode from '!!raw-loader!@flowgram.ai/demo-fixed-layout-simple/src/components/node-adder.tsx';
import miniMapCode from '!!raw-loader!@flowgram.ai/demo-fixed-layout-simple/src/components/minimap.tsx';
import branchAdderCode from '!!raw-loader!@flowgram.ai/demo-fixed-layout-simple/src/components/branch-adder.tsx';
import baseNodeCode from '!!raw-loader!@flowgram.ai/demo-fixed-layout-simple/src/components/base-node.tsx';
const indexCode = {
code: `import { FixedLayoutEditorProvider, EditorRenderer } from '@flowgram.ai/fixed-layout-editor';
import '@flowgram.ai/fixed-layout-editor/index.css';
import './index.css'
import { useEditorProps } from './use-editor-props';
import { initialData } from './initial-data'
import { nodeRegistries } from './node-registries'
import { Tools } from './tools'
import { Minimap } from './minimap'
export const Editor = () => {
const editorProps = useEditorProps(initialData, nodeRegistries);
return (
<FixedLayoutEditorProvider {...editorProps}>
<div className="demo-fixed-container">
<EditorRenderer>{/* add child panel here */}</EditorRenderer>
</div>
<Tools />
<Minimap />
</FixedLayoutEditorProvider>
);
}`,
code: editorCode,
active: true,
};
const indexCssCode = `.demo-fixed-node {
align-items: flex-start;
background-color: #fff;
border: 1px solid rgba(6, 7, 9, 0.15);
border-radius: 8px;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02);
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
width: 360px;
transition: all 0.3s ease;
}
.demo-fixed-node-title {
background-color: #93bfe2;
width: 100%;
border-radius: 8px 8px 0 0;
padding: 4px 12px;
}
.demo-fixed-node-content {
padding: 16px;
flex-grow: 1;
width: 100%;
}
.demo-fixed-adder {
width: 28px;
height: 18px;
background: rgb(187, 191, 196);
display: flex;
border-radius: 9px;
justify-content: space-evenly;
align-items: center;
color: #fff;
font-size: 10px;
font-weight: bold;
div {
display: flex;
justify-content: center;
align-items: center;
svg {
width: 12px;
height: 12px;
}
}
}
.demo-fixed-adder.activated {
background: #82A7FC
}
.demo-fixed-adder.isHorizontal {
transform: rotate(90deg);
}
.gedit-playground * {
box-sizing: border-box;
}`;
const initialDataCode = `import { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
/**
* blocks
*/
export const initialData: FlowDocumentJSON = {
nodes: [
// 开始节点
{
id: 'start_0',
type: 'start',
data: {
title: 'Start',
content: 'start content'
},
blocks: [],
},
// 分支节点
{
id: 'condition_0',
type: 'condition',
data: {
title: 'Condition'
},
blocks: [
{
id: 'branch_0',
type: 'block',
data: {
title: 'Branch 0',
content: 'branch 1 content'
},
blocks: [
{
id: 'custom_0',
type: 'custom',
data: {
title: 'Custom',
content: 'custrom content'
},
},
],
},
{
id: 'branch_1',
type: 'block',
data: {
title: 'Branch 1',
content: 'branch 1 content'
},
blocks: [],
},
],
},
// 结束节点
{
id: 'end_0',
type: 'end',
data: {
title: 'End',
content: 'end content'
},
},
],
};`;
const nodeRegistriesCode = `import { FlowNodeRegistry } from '@flowgram.ai/fixed-layout-editor';
import { nanoid } from 'nanoid';
/**
*
*/
export const nodeRegistries: FlowNodeRegistry[] = [
{
/**
*
*/
type: 'condition',
/**
* :
* - loop: 扩展为循环节点
* - start: 扩展为开始节点
* - dynamicSplit: 扩展为分支节点
* - end: 扩展为结束节点
* - tryCatch: 扩展为 tryCatch
* - default: ()
*/
extend: 'dynamicSplit',
/**
*
*/
meta: {
// isStart: false, // 是否为开始节点
// isNodeEnd: false, // 是否为结束节点,结束节点后边无法再添加节点
// draggable: false, // 是否可拖拽,如开始节点和结束节点无法拖拽
// selectable: false, // 触发器等开始节点不能被框选
// deleteDisable: true, // 禁止删除
// copyDisable: true, // 禁止copy
// addDisable: true, // 禁止添加
},
onAdd() {
return {
id: \`condition_\${nanoid(5)}\`,
type: 'condition',
data: {
title: 'Condition',
},
blocks: [
{
id: nanoid(5),
type: 'block',
data: {
title: 'If_0',
},
},
{
id: nanoid(5),
type: 'block',
data: {
title: 'If_1',
},
},
],
};
},
},
{
type: 'custom',
meta: {},
onAdd() {
return {
id: \`custom_\${nanoid(5)}\`,
type: 'custom',
data: {
title: 'Custom',
content: 'this is custom content'
}
}
}
}
];`;
const useEditorPropsCode = `import { useMemo } from 'react';
import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
import {
type FixedLayoutProps,
FlowDocumentJSON,
FlowNodeRegistry,
FlowTextKey,
Field,
FlowRendererKey,
} from '@flowgram.ai/fixed-layout-editor';
import { BaseNode } from './base-node'
import { BranchAdder } from './branch-adder'
import { NodeAdder } from '../components/node-adder';
/** semi materials */
export function useEditorProps(
initialData: FlowDocumentJSON, // 初始化数据
nodeRegistries: FlowNodeRegistry[], // 节点定义
): FixedLayoutProps {
return useMemo<FixedLayoutProps>(
() => ({
/**
* Whether to enable the background
*/
background: true,
/**
* Whether it is read-only or not, the node cannot be dragged in read-only mode
*/
readonly: false,
/**
* Initial data
*
*/
initialData,
/**
*
*/
nodeRegistries,
/**
* Get the default node registry, which will be merged with the 'nodeRegistries'
* nodeRegistries
*/
getNodeDefaultRegistry(type) {
return {
type,
meta: {
defaultExpanded: true,
},
formMeta: {
/**
* Render form
*/
render: () => <>
<Field<string> name="title">
{({ field }) => <div className="demo-fixed-node-title">{field.value}</div>}
</Field>
<div className="demo-fixed-node-content">
<Field<string> name="content">
<input />
</Field>
</div>
</>
}
};
},
/**
* Materials, components can be customized based on the key
* key UI
*/
materials: {
renderNodes: {
...defaultFixedSemiMaterials,
/**
* Components can be customized based on key business-side requirements.
* key
*/
[FlowRendererKey.ADDER]: NodeAdder,
[FlowRendererKey.BRANCH_ADDER]: BranchAdder,
// [FlowRendererKey.DRAG_NODE]: DragNode,
},
renderDefaultNode: BaseNode, // 节点渲染
renderTexts: {
[FlowTextKey.LOOP_END_TEXT]: 'loop end',
[FlowTextKey.LOOP_TRAVERSE_TEXT]: 'looping',
},
},
/**
* Node engine enable, you can configure formMeta in the FlowNodeRegistry
*/
nodeEngine: {
enable: true,
},
history: {
enable: true,
enableChangeNode: true, // Listen Node engine data change
onApply(ctx, opt) {
// Listen change to trigger auto save
// console.log('auto save: ', ctx.document.toJSON(), opt);
},
},
/**
*
*/
onInit: ctx => {
/**
* Data can also be dynamically loaded via fromJSON
* fromJSON
*/
// ctx.document.fromJSON(initialData)
console.log('---- Playground Init ----');
},
/**
*
*/
onDispose: () => {
console.log('---- Playground Dispose ----');
},
plugins: () => [
/**
* Minimap plugin
*
*/
createMinimapPlugin({
disableLayer: true,
enableDisplayAllNodes: true,
canvasStyle: {
canvasWidth: 182,
canvasHeight: 102,
canvasPadding: 50,
canvasBackground: 'rgba(245, 245, 245, 1)',
canvasBorderRadius: 10,
viewportBackground: 'rgba(235, 235, 235, 1)',
viewportBorderRadius: 4,
viewportBorderColor: 'rgba(201, 201, 201, 1)',
viewportBorderWidth: 1,
viewportBorderDashLength: 2,
nodeColor: 'rgba(255, 255, 255, 1)',
nodeBorderRadius: 2,
nodeBorderWidth: 0.145,
nodeBorderColor: 'rgba(6, 7, 9, 0.10)',
overlayColor: 'rgba(255, 255, 255, 0)',
},
inactiveDebounceTime: 1,
}),
],
}),
[],
);
}
`;
const baseNodeCode = `import { FlowNodeEntity, useNodeRender } from '@flowgram.ai/fixed-layout-editor';
export const BaseNode = ({ node }: { node: FlowNodeEntity }) => {
/**
* Provides methods related to node rendering
*
*/
const nodeRender = useNodeRender();
/**
* It can only be used when nodeEngine is enabled
* 使
*/
const form = nodeRender.form;
return (
<div
className="demo-fixed-node"
onMouseEnter={nodeRender.onMouseEnter}
onMouseLeave={nodeRender.onMouseLeave}
onMouseDown={e => {
// trigger drag node
nodeRender.startDrag(e);
e.stopPropagation();
}}
style={{
...(nodeRender.isBlockOrderIcon || nodeRender.isBlockIcon ? { width: 260 } : {}),
}}
>
{form?.render()}
</div>
);
};
`;
const branchAdderCode = `import { type FlowNodeEntity, useClientContext } from '@flowgram.ai/fixed-layout-editor';
import { IconPlus } from '@douyinfe/semi-icons';
import { nanoid } from 'nanoid';
interface PropsType {
activated?: boolean;
node: FlowNodeEntity;
}
export function BranchAdder(props: PropsType) {
const { activated, node } = props;
const nodeData = node.firstChild!.renderData;
const ctx = useClientContext();
const { operation, playground } = ctx;
const { isVertical } = node;
function addBranch() {
const block = operation.addBlock(node, {
id: \`branch_\${nanoid(5)}\`,
type: 'block',
data: {
title: 'New Branch',
content: ''
}
});
setTimeout(() => {
playground.scrollToView({
bounds: block.bounds,
scrollToCenter: true,
});
}, 10);
}
if (playground.config.readonlyOrDisabled) return null;
const className = [
'demo-fixed-adder',
isVertical ? '' : 'isHorizontal',
activated ? 'activated' : ''
].join(' ');
return (
<div
className={className}
onMouseEnter={() => nodeData?.toggleMouseEnter()}
onMouseLeave={() => nodeData?.toggleMouseLeave()}
>
<div
onClick={() => {
addBranch();
}}
aria-hidden="true"
style={{ flexGrow: 1, textAlign: 'center' }}
>
<IconPlus />
</div>
</div>
);
}
`;
const miniMapCode = `import { FlowMinimapService, MinimapRender } from '@flowgram.ai/minimap-plugin';
import { useService } from '@flowgram.ai/fixed-layout-editor';
export const Minimap = () => {
const minimapService = useService(FlowMinimapService);
return (
<div
style={{
position: 'absolute',
left: 16,
bottom: 51,
zIndex: 100,
width: 182,
}}
>
<MinimapRender
service={minimapService}
containerStyles={{
pointerEvents: 'auto',
position: 'relative',
top: 'unset',
right: 'unset',
bottom: 'unset',
left: 'unset',
}}
inactiveStyle={{
opacity: 1,
scale: 1,
translateX: 0,
translateY: 0,
}}
/>
</div>
);
};
`;
const nodeAdderCode = `import { FlowNodeEntity, FlowOperationService, useClientContext, usePlayground, useService } from "@flowgram.ai/fixed-layout-editor"
import { Dropdown } from '@douyinfe/semi-ui'
import { IconPlusCircle } from "@douyinfe/semi-icons";
import { nodeRegistries } from '../node-registries';
export const NodeAdder = (props: {
from: FlowNodeEntity;
to?: FlowNodeEntity;
hoverActivated: boolean;
}) => {
const { from, hoverActivated } = props;
const playground = usePlayground();
const context = useClientContext();
const flowOperationService = useService(FlowOperationService) as FlowOperationService;
const add = (addProps: any) => {
const blocks = addProps.blocks ? addProps.blocks : undefined;
const block = flowOperationService.addFromNode(from, {
...addProps,
blocks,
});
setTimeout(() => {
playground.scrollToView({
bounds: block.bounds,
scrollToCenter: true,
});
}, 10);
};
if (playground.config.readonlyOrDisabled) return null;
return (
<Dropdown
render={
<Dropdown.Menu>
{nodeRegistries.map(registry => <Dropdown.Item onClick={() => {
const props = registry?.onAdd(context, from);
add(props);
}}>{registry.type}</Dropdown.Item>)}
</Dropdown.Menu>
}
>
<div
style={{
width: hoverActivated ? 15 : 6,
height: hoverActivated ? 15 : 6,
backgroundColor: 'rgb(143, 149, 158)',
color: '#fff',
borderRadius: '50%',
cursor: 'pointer'
}}
>
{hoverActivated ?
<IconPlusCircle
style={{
color: '#3370ff',
backgroundColor: '#fff',
borderRadius: 15
}}
/> : null
}
</div>
</Dropdown>
);
}
`;
const toolsCode = `import { useEffect, useState } from 'react'
import { usePlaygroundTools, useClientContext } from '@flowgram.ai/fixed-layout-editor';
export function Tools() {
const { history } = useClientContext();
const tools = usePlaygroundTools();
const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false);
useEffect(() => {
const disposable = history.undoRedoService.onChange(() => {
setCanUndo(history.canUndo());
setCanRedo(history.canRedo());
});
return () => disposable.dispose();
}, [history]);
return <div style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 16, display: 'flex', gap: 8 }}>
<button onClick={() => tools.zoomin()}>ZoomIn</button>
<button onClick={() => tools.zoomout()}>ZoomOut</button>
<button onClick={() => tools.fitView()}>Fitview</button>
<button onClick={() => tools.changeLayout()}>ChangeLayout</button>
<button onClick={() => history.undo()} disabled={!canUndo}>Undo</button>
<button onClick={() => history.redo()} disabled={!canRedo}>Redo</button>
<span>{Math.floor(tools.zoom * 100)}%</span>
</div>
}
`;
export const FixedLayoutSimplePreview = () => (
<PreviewEditor
files={{

4
apps/docs/src/global.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module "!!raw-loader!*" {
const content: string;
export default content;
}