step-3.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import '@flowgram.ai/fixed-layout-editor/index.css';
  2. import { FC } from 'react';
  3. import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
  4. import {
  5. FixedLayoutEditorProvider,
  6. EditorRenderer,
  7. FlowNodeEntity,
  8. useNodeRender,
  9. FlowNodeJSON,
  10. FlowOperationService,
  11. usePlayground,
  12. useService,
  13. FlowRendererKey,
  14. useClientContext,
  15. } from '@flowgram.ai/fixed-layout-editor';
  16. export const NodeRender = ({ node }: { node: FlowNodeEntity }) => {
  17. const {
  18. onMouseEnter,
  19. onMouseLeave,
  20. startDrag,
  21. form,
  22. dragging,
  23. isBlockOrderIcon,
  24. isBlockIcon,
  25. activated,
  26. } = useNodeRender();
  27. const ctx = useClientContext();
  28. return (
  29. <div
  30. onMouseEnter={onMouseEnter}
  31. onMouseLeave={onMouseLeave}
  32. onMouseDown={(e) => {
  33. startDrag(e);
  34. e.stopPropagation();
  35. }}
  36. style={{
  37. width: 280,
  38. minHeight: 88,
  39. height: 'auto',
  40. background: '#fff',
  41. border: '1px solid rgba(6, 7, 9, 0.15)',
  42. borderColor: activated ? '#82a7fc' : 'rgba(6, 7, 9, 0.15)',
  43. borderRadius: 8,
  44. boxShadow: '0 2px 6px 0 rgba(0, 0, 0, 0.04), 0 4px 12px 0 rgba(0, 0, 0, 0.02)',
  45. display: 'flex',
  46. flexDirection: 'column',
  47. justifyContent: 'center',
  48. position: 'relative',
  49. padding: 12,
  50. cursor: 'move',
  51. opacity: dragging ? 0.3 : 1,
  52. ...(isBlockOrderIcon || isBlockIcon ? { width: 260 } : {}),
  53. }}
  54. >
  55. {form?.render()}
  56. {/* 删除按钮 */}
  57. <button
  58. onClick={(e) => {
  59. e.stopPropagation();
  60. ctx.operation.deleteNode(node);
  61. }}
  62. style={{
  63. position: 'absolute',
  64. top: 4,
  65. right: 4,
  66. width: 20,
  67. height: 20,
  68. border: 'none',
  69. borderRadius: '50%',
  70. background: '#fff',
  71. color: '#666',
  72. fontSize: 12,
  73. cursor: 'pointer',
  74. display: 'flex',
  75. alignItems: 'center',
  76. justifyContent: 'center',
  77. boxShadow: '0 1px 3px rgba(0,0,0,0.12)',
  78. transition: 'all 0.2s',
  79. }}
  80. >
  81. ×
  82. </button>
  83. </div>
  84. );
  85. };
  86. const useAddNode = () => {
  87. const playground = usePlayground();
  88. const flowOperationService = useService(FlowOperationService) as FlowOperationService;
  89. const handleAdd = (addProps: FlowNodeJSON, dropNode: FlowNodeEntity) => {
  90. const blocks = addProps.blocks ? addProps.blocks : undefined;
  91. const entity = flowOperationService.addFromNode(dropNode, {
  92. ...addProps,
  93. blocks,
  94. });
  95. setTimeout(() => {
  96. playground.scrollToView({
  97. bounds: entity.bounds,
  98. scrollToCenter: true,
  99. });
  100. }, 10);
  101. return entity;
  102. };
  103. const handleAddBranch = (addProps: FlowNodeJSON, dropNode: FlowNodeEntity) => {
  104. const index = dropNode.index + 1;
  105. const entity = flowOperationService.addBlock(dropNode.originParent!, addProps, {
  106. index,
  107. });
  108. return entity;
  109. };
  110. return {
  111. handleAdd,
  112. handleAddBranch,
  113. };
  114. };
  115. const Adder: FC<{
  116. from: FlowNodeEntity;
  117. to?: FlowNodeEntity;
  118. hoverActivated: boolean;
  119. }> = ({ from, hoverActivated }) => {
  120. const playground = usePlayground();
  121. const { handleAdd } = useAddNode();
  122. if (playground.config.readonlyOrDisabled) return null;
  123. return (
  124. <div
  125. style={{
  126. width: hoverActivated ? 15 : 6,
  127. height: hoverActivated ? 15 : 6,
  128. backgroundColor: hoverActivated ? '#fff' : 'rgb(143, 149, 158)',
  129. color: '#fff',
  130. borderRadius: '50%',
  131. display: 'flex',
  132. alignItems: 'center',
  133. justifyContent: 'center',
  134. cursor: 'pointer',
  135. }}
  136. onClick={() => {
  137. handleAdd({ type: 'custom', id: `custom_${Date.now()}` }, from);
  138. }}
  139. >
  140. {hoverActivated ? (
  141. <span
  142. style={{
  143. color: '#3370ff',
  144. fontSize: 12,
  145. }}
  146. >
  147. +
  148. </span>
  149. ) : null}
  150. </div>
  151. );
  152. };
  153. const FlowGramApp = () => (
  154. <FixedLayoutEditorProvider
  155. nodeRegistries={[
  156. {
  157. type: 'custom',
  158. },
  159. ]}
  160. initialData={{
  161. nodes: [
  162. {
  163. id: 'start_0',
  164. type: 'start',
  165. },
  166. {
  167. id: 'custom_1',
  168. type: 'custom',
  169. },
  170. {
  171. id: 'end_2',
  172. type: 'end',
  173. },
  174. ],
  175. }}
  176. onAllLayersRendered={(ctx) => {
  177. setTimeout(() => {
  178. ctx.playground.config.fitView(ctx.document.root.bounds.pad(30));
  179. }, 10);
  180. }}
  181. materials={{
  182. renderDefaultNode: NodeRender,
  183. components: {
  184. ...defaultFixedSemiMaterials,
  185. [FlowRendererKey.ADDER]: Adder,
  186. },
  187. }}
  188. >
  189. <EditorRenderer />
  190. </FixedLayoutEditorProvider>
  191. );
  192. export default FlowGramApp;