preview.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. import React from 'react'
  2. import { Table, TableProps } from 'antd'
  3. import { TreeNode, createBehavior, createResource } from '@designable/core'
  4. import {
  5. useTreeNode,
  6. TreeNodeWidget,
  7. DroppableWidget,
  8. useNodeIdProps,
  9. DnFC
  10. } from '@designable/react'
  11. import { ArrayBase } from '@formily/antd'
  12. import { observer } from '@formily/react'
  13. import { LoadTemplate } from '../../common/LoadTemplate'
  14. import cls from 'classnames'
  15. import {
  16. queryNodesByComponentPath,
  17. hasNodeByComponentPath,
  18. findNodeByComponentPath,
  19. createEnsureTypeItemsNode
  20. } from '../../shared'
  21. import { useDropTemplate } from '../../hooks'
  22. import { createArrayBehavior } from '../ArrayBase'
  23. import './styles.less'
  24. import { createVoidFieldSchema } from '../Field'
  25. import { AllSchemas } from '../../schemas'
  26. import { AllLocales } from '../../locales'
  27. const ensureObjectItemsNode = createEnsureTypeItemsNode('object')
  28. const HeaderCell: React.FC = (props: any) => {
  29. return (
  30. <th {...props} data-designer-node-id={props.className.match(/data-id\:([^\s]+)/)?.[1]}>
  31. {props.children}
  32. </th>
  33. )
  34. }
  35. const BodyCell: React.FC = (props: any) => {
  36. return (
  37. <td {...props} data-designer-node-id={props.className.match(/data-id\:([^\s]+)/)?.[1]}>
  38. {props.children}
  39. </td>
  40. )
  41. }
  42. export const ArrayTable: DnFC<TableProps<any>> = observer(props => {
  43. const node = useTreeNode()
  44. const nodeId = useNodeIdProps()
  45. useDropTemplate('ArrayTable', source => {
  46. const sortHandleNode = new TreeNode({
  47. componentName: 'Field',
  48. props: {
  49. type: 'void',
  50. 'x-component': 'ArrayTable.Column',
  51. 'x-component-props': {
  52. title: `Title`
  53. }
  54. },
  55. children: [
  56. {
  57. componentName: 'Field',
  58. props: {
  59. type: 'void',
  60. 'x-component': 'ArrayTable.SortHandle'
  61. }
  62. }
  63. ]
  64. })
  65. const indexNode = new TreeNode({
  66. componentName: 'Field',
  67. props: {
  68. type: 'void',
  69. 'x-component': 'ArrayTable.Column',
  70. 'x-component-props': {
  71. title: `Title`
  72. }
  73. },
  74. children: [
  75. {
  76. componentName: 'Field',
  77. props: {
  78. type: 'void',
  79. 'x-component': 'ArrayTable.Index'
  80. }
  81. }
  82. ]
  83. })
  84. const columnNode = new TreeNode({
  85. componentName: 'Field',
  86. props: {
  87. type: 'void',
  88. 'x-component': 'ArrayTable.Column',
  89. 'x-component-props': {
  90. title: `Title`
  91. }
  92. },
  93. children: source.map(node => {
  94. node.props.title = undefined
  95. return node
  96. })
  97. })
  98. const operationNode = new TreeNode({
  99. componentName: 'Field',
  100. props: {
  101. type: 'void',
  102. 'x-component': 'ArrayTable.Column',
  103. 'x-component-props': {
  104. title: `Title`
  105. }
  106. },
  107. children: [
  108. {
  109. componentName: 'Field',
  110. props: {
  111. type: 'void',
  112. 'x-component': 'ArrayTable.Remove'
  113. }
  114. },
  115. {
  116. componentName: 'Field',
  117. props: {
  118. type: 'void',
  119. 'x-component': 'ArrayTable.MoveDown'
  120. }
  121. },
  122. {
  123. componentName: 'Field',
  124. props: {
  125. type: 'void',
  126. 'x-component': 'ArrayTable.MoveUp'
  127. }
  128. }
  129. ]
  130. })
  131. const objectNode = new TreeNode({
  132. componentName: 'Field',
  133. props: {
  134. type: 'object'
  135. },
  136. children: [sortHandleNode, indexNode, columnNode, operationNode]
  137. })
  138. const additionNode = new TreeNode({
  139. componentName: 'Field',
  140. props: {
  141. type: 'void',
  142. title: 'Addition',
  143. 'x-component': 'ArrayTable.Addition'
  144. }
  145. })
  146. return [objectNode, additionNode]
  147. })
  148. const columns = queryNodesByComponentPath(node, ['ArrayTable', '*', 'ArrayTable.Column'])
  149. const additions = queryNodesByComponentPath(node, ['ArrayTable', 'ArrayTable.Addition'])
  150. const defaultRowKey = () => {
  151. return node.id
  152. }
  153. const renderTable = () => {
  154. if (node.children.length === 0) return <DroppableWidget />
  155. return (
  156. <ArrayBase disabled>
  157. <Table
  158. size="small"
  159. bordered
  160. {...props}
  161. scroll={{ x: '100%' }}
  162. className={cls('ant-formily-array-table', props.className)}
  163. style={{ marginBottom: 10, ...props.style }}
  164. rowKey={defaultRowKey}
  165. dataSource={[{ id: '1' }]}
  166. pagination={false}
  167. components={{
  168. header: {
  169. cell: HeaderCell
  170. },
  171. body: {
  172. cell: BodyCell
  173. }
  174. }}
  175. >
  176. {columns.map(node => {
  177. const children = node.children.map(child => {
  178. return <TreeNodeWidget node={child} key={child.id} />
  179. })
  180. const props = node.props['x-component-props']
  181. return (
  182. <Table.Column
  183. {...props}
  184. title={<div data-content-editable="x-component-props.title">{props.title}</div>}
  185. dataIndex={node.id}
  186. className={`data-id:${node.id}`}
  187. key={node.id}
  188. render={(value, record, key) => {
  189. return (
  190. <ArrayBase.Item key={key} index={key} record={null}>
  191. {children.length > 0 ? children : 'Droppable'}
  192. </ArrayBase.Item>
  193. )
  194. }}
  195. />
  196. )
  197. })}
  198. {columns.length === 0 && <Table.Column render={() => <DroppableWidget />} />}
  199. </Table>
  200. {additions.map(child => {
  201. return <TreeNodeWidget node={child} key={child.id} />
  202. })}
  203. </ArrayBase>
  204. )
  205. }
  206. useDropTemplate('ArrayTable.Column', source => {
  207. return source.map(node => {
  208. node.props.title = undefined
  209. return node
  210. })
  211. })
  212. return (
  213. <div {...nodeId} className="dn-array-table">
  214. {renderTable()}
  215. <LoadTemplate
  216. actions={[
  217. {
  218. title: node.getMessage('addSortHandle'),
  219. icon: 'AddSort',
  220. onClick: () => {
  221. if (
  222. hasNodeByComponentPath(node, [
  223. 'ArrayTable',
  224. '*',
  225. 'ArrayTable.Column',
  226. 'ArrayTable.SortHandle'
  227. ])
  228. )
  229. return
  230. const tableColumn = new TreeNode({
  231. componentName: 'Field',
  232. props: {
  233. type: 'void',
  234. 'x-component': 'ArrayTable.Column',
  235. 'x-component-props': {
  236. title: `Title`
  237. }
  238. },
  239. children: [
  240. {
  241. componentName: 'Field',
  242. props: {
  243. type: 'void',
  244. 'x-component': 'ArrayTable.SortHandle'
  245. }
  246. }
  247. ]
  248. })
  249. ensureObjectItemsNode(node).prepend(tableColumn)
  250. }
  251. },
  252. {
  253. title: node.getMessage('addIndex'),
  254. icon: 'AddIndex',
  255. onClick: () => {
  256. if (
  257. hasNodeByComponentPath(node, [
  258. 'ArrayTable',
  259. '*',
  260. 'ArrayTable.Column',
  261. 'ArrayTable.Index'
  262. ])
  263. )
  264. return
  265. const tableColumn = new TreeNode({
  266. componentName: 'Field',
  267. props: {
  268. type: 'void',
  269. 'x-component': 'ArrayTable.Column',
  270. 'x-component-props': {
  271. title: `Title`
  272. }
  273. },
  274. children: [
  275. {
  276. componentName: 'Field',
  277. props: {
  278. type: 'void',
  279. 'x-component': 'ArrayTable.Index'
  280. }
  281. }
  282. ]
  283. })
  284. const sortNode = findNodeByComponentPath(node, [
  285. 'ArrayTable',
  286. '*',
  287. 'ArrayTable.Column',
  288. 'ArrayTable.SortHandle'
  289. ])
  290. if (sortNode) {
  291. sortNode.parent.insertAfter(tableColumn)
  292. } else {
  293. ensureObjectItemsNode(node).prepend(tableColumn)
  294. }
  295. }
  296. },
  297. {
  298. title: node.getMessage('addColumn'),
  299. icon: 'AddColumn',
  300. onClick: () => {
  301. const operationNode = findNodeByComponentPath(node, [
  302. 'ArrayTable',
  303. '*',
  304. 'ArrayTable.Column',
  305. name => {
  306. return (
  307. name === 'ArrayTable.Remove' ||
  308. name === 'ArrayTable.MoveDown' ||
  309. name === 'ArrayTable.MoveUp'
  310. )
  311. }
  312. ])
  313. const tableColumn = new TreeNode({
  314. componentName: 'Field',
  315. props: {
  316. type: 'void',
  317. 'x-component': 'ArrayTable.Column',
  318. 'x-component-props': {
  319. title: `Title`
  320. }
  321. }
  322. })
  323. if (operationNode) {
  324. operationNode.parent.insertBefore(tableColumn)
  325. } else {
  326. ensureObjectItemsNode(node).append(tableColumn)
  327. }
  328. }
  329. },
  330. {
  331. title: node.getMessage('addOperation'),
  332. icon: 'AddOperation',
  333. onClick: () => {
  334. const oldOperationNode = findNodeByComponentPath(node, [
  335. 'ArrayTable',
  336. '*',
  337. 'ArrayTable.Column',
  338. name => {
  339. return (
  340. name === 'ArrayTable.Remove' ||
  341. name === 'ArrayTable.MoveDown' ||
  342. name === 'ArrayTable.MoveUp'
  343. )
  344. }
  345. ])
  346. const oldAdditionNode = findNodeByComponentPath(node, [
  347. 'ArrayTable',
  348. 'ArrayTable.Addition'
  349. ])
  350. if (!oldOperationNode) {
  351. const operationNode = new TreeNode({
  352. componentName: 'Field',
  353. props: {
  354. type: 'void',
  355. 'x-component': 'ArrayTable.Column',
  356. 'x-component-props': {
  357. title: `Title`
  358. }
  359. },
  360. children: [
  361. {
  362. componentName: 'Field',
  363. props: {
  364. type: 'void',
  365. 'x-component': 'ArrayTable.Remove'
  366. }
  367. },
  368. {
  369. componentName: 'Field',
  370. props: {
  371. type: 'void',
  372. 'x-component': 'ArrayTable.MoveDown'
  373. }
  374. },
  375. {
  376. componentName: 'Field',
  377. props: {
  378. type: 'void',
  379. 'x-component': 'ArrayTable.MoveUp'
  380. }
  381. }
  382. ]
  383. })
  384. ensureObjectItemsNode(node).append(operationNode)
  385. }
  386. if (!oldAdditionNode) {
  387. const additionNode = new TreeNode({
  388. componentName: 'Field',
  389. props: {
  390. type: 'void',
  391. title: 'Addition',
  392. 'x-component': 'ArrayTable.Addition'
  393. }
  394. })
  395. ensureObjectItemsNode(node).insertAfter(additionNode)
  396. }
  397. }
  398. }
  399. ]}
  400. />
  401. </div>
  402. )
  403. })
  404. ArrayBase.mixin(ArrayTable)
  405. ArrayTable.Behavior = createBehavior(createArrayBehavior('ArrayTable'), {
  406. name: 'ArrayTable.Column',
  407. extends: ['Field'],
  408. selector: node => node.props['x-component'] === 'ArrayTable.Column',
  409. designerProps: {
  410. droppable: true,
  411. allowDrop: node =>
  412. node.props['type'] === 'object' && node.parent?.props?.['x-component'] === 'ArrayTable',
  413. propsSchema: createVoidFieldSchema(AllSchemas.ArrayTable.Column)
  414. },
  415. designerLocales: AllLocales.ArrayTableColumn
  416. })
  417. ArrayTable.Resource = createResource({
  418. icon: 'ArrayTableSource',
  419. elements: [
  420. {
  421. componentName: 'Field',
  422. props: {
  423. type: 'array',
  424. 'x-decorator': 'FormItem',
  425. 'x-component': 'ArrayTable'
  426. }
  427. }
  428. ]
  429. })