preview.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. /**
  2. * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
  3. * SPDX-License-Identifier: MIT
  4. */
  5. import {
  6. DEFAULT_INITIAL_DATA,
  7. defaultInitialDataTs,
  8. fieldWrapperCss,
  9. fieldWrapperTs,
  10. } from '@flowgram.ai/demo-node-form';
  11. import { Editor } from '../editor.tsx';
  12. import { PreviewEditor } from '../../preview-editor.tsx';
  13. import { nodeRegistry } from './node-registry.tsx';
  14. const nodeRegistryFile = {
  15. code: `import {
  16. DataEvent,
  17. EffectFuncProps,
  18. Field,
  19. FieldRenderProps,
  20. FormMeta,
  21. ValidateTrigger,
  22. WorkflowNodeRegistry,
  23. FieldArray,
  24. FieldArrayRenderProps,
  25. } from '@flowgram.ai/free-layout-editor';
  26. import { FieldWrapper } from '@flowgram.ai/demo-node-form';
  27. import { Input, Button, Popover } from '@douyinfe/semi-ui';
  28. import { IconPlus, IconCrossCircleStroked, IconArrowDown } from '@douyinfe/semi-icons';
  29. import './index.css';
  30. import '../index.css';
  31. export const render = () => (
  32. <div className="demo-node-content">
  33. <div className="demo-node-title">Array Examples</div>
  34. <FieldArray name="array">
  35. {({ field, fieldState }: FieldArrayRenderProps<string>) => (
  36. <FieldWrapper title={'My Array'}>
  37. {field.map((child, index) => (
  38. <Field name={child.name} key={child.key}>
  39. {({ field: childField, fieldState: childState }: FieldRenderProps<string>) => (
  40. <FieldWrapper error={childState.errors?.[0]?.message}>
  41. <div className="array-item-wrapper">
  42. <Input {...childField} size={'small'} />
  43. {index < field.value!.length - 1 ? (
  44. <Popover
  45. content={'swap with next element'}
  46. className={'icon-button-popover'}
  47. showArrow
  48. position={'topLeft'}
  49. >
  50. <Button
  51. theme="borderless"
  52. size={'small'}
  53. icon={<IconArrowDown />}
  54. onClick={() => field.swap(index, index + 1)}
  55. />
  56. </Popover>
  57. ) : null}
  58. <Popover
  59. content={'delete current element'}
  60. className={'icon-button-popover'}
  61. showArrow
  62. position={'topLeft'}
  63. >
  64. <Button
  65. theme="borderless"
  66. size={'small'}
  67. icon={<IconCrossCircleStroked />}
  68. onClick={() => field.delete(index)}
  69. />
  70. </Popover>
  71. </div>
  72. </FieldWrapper>
  73. )}
  74. </Field>
  75. ))}
  76. <div>
  77. <Button
  78. size={'small'}
  79. theme="borderless"
  80. icon={<IconPlus />}
  81. onClick={() => field.append('default')}
  82. >
  83. Add
  84. </Button>
  85. </div>
  86. </FieldWrapper>
  87. )}
  88. </FieldArray>
  89. </div>
  90. );
  91. interface FormData {
  92. array: string[];
  93. }
  94. const formMeta: FormMeta<FormData> = {
  95. render,
  96. validateTrigger: ValidateTrigger.onChange,
  97. defaultValues: {
  98. array: ['default'],
  99. },
  100. validate: {
  101. 'array.*': ({ value }) =>
  102. value.length > 8 ? 'max length exceeded: current length is ' + value.length : undefined,
  103. },
  104. effect: {
  105. 'array.*': [
  106. {
  107. event: DataEvent.onValueInit,
  108. effect: ({ value, name }: EffectFuncProps<string, FormData>) => {
  109. console.log(name + ' value init to ', value);
  110. },
  111. },
  112. {
  113. event: DataEvent.onValueChange,
  114. effect: ({ value, name }: EffectFuncProps<string, FormData>) => {
  115. console.log(name + ' value changed to ', value);
  116. },
  117. },
  118. ],
  119. },
  120. };
  121. export const nodeRegistry: WorkflowNodeRegistry = {
  122. type: 'custom',
  123. meta: {},
  124. defaultPorts: [{ type: 'output' }, { type: 'input' }],
  125. formMeta,
  126. };
  127. `,
  128. active: true,
  129. };
  130. export const NodeFormArrayPreview = () => {
  131. const files = {
  132. 'node-registry.tsx': nodeRegistryFile,
  133. 'initial-data.ts': { code: defaultInitialDataTs, active: true },
  134. 'field-wrapper.tsx': { code: fieldWrapperTs, active: true },
  135. 'field-wrapper.css': { code: fieldWrapperCss, active: true },
  136. };
  137. return (
  138. <PreviewEditor files={files} previewStyle={{ height: 500 }} editorStyle={{ height: 500 }}>
  139. <Editor registry={nodeRegistry} initialData={DEFAULT_INITIAL_DATA} />
  140. </PreviewEditor>
  141. );
  142. };