StackContainer.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import React, { useCallback } from 'react'
  2. import produce from 'immer'
  3. import { DndProvider } from 'react-dnd'
  4. import HTML5Backend from 'react-dnd-html5-backend'
  5. import { Icon, Tooltip } from 'antd'
  6. import { StackGroup, IStackMetrics } from './types'
  7. import MetricItem from './MetricItem'
  8. import StackItem from './StackItem'
  9. import Styles from './Stack.less'
  10. interface IStackContainerProps {
  11. metrics: IStackMetrics
  12. group: StackGroup
  13. onStackGroupChange: (stack: StackGroup) => void
  14. }
  15. const findMetricLocation = (metricId: string, stack: string[][]) => {
  16. let location = null
  17. stack.some((group, grpIdx) => {
  18. const metricIdx = group.indexOf(metricId)
  19. if (metricIdx >= 0) {
  20. location = [grpIdx, metricIdx]
  21. return true
  22. }
  23. })
  24. return location
  25. }
  26. const StackContainer: React.FC<IStackContainerProps> = (props) => {
  27. const { group: stack, metrics, onStackGroupChange } = props
  28. const removeMetric = useCallback(
  29. (item) => {
  30. const { id } = item
  31. const nextStack = produce(stack, (draft) => {
  32. const prevLoc = findMetricLocation(id, stack)
  33. if (prevLoc) {
  34. draft[prevLoc[0]].splice(prevLoc[1], 1)
  35. if (!draft[prevLoc[0]].length) {
  36. draft.splice(prevLoc[0], 1)
  37. }
  38. }
  39. })
  40. onStackGroupChange(nextStack)
  41. },
  42. [stack]
  43. )
  44. const flatStack = stack.reduce((acc, stackItem) => acc.concat(stackItem), [])
  45. const metricList = (
  46. <StackItem className={Styles.list} onDrop={removeMetric}>
  47. {Object.entries(metrics)
  48. .filter(([id]) => !flatStack.includes(id))
  49. .map(([id, name]) => (
  50. <MetricItem key={id} id={id} name={name} />
  51. ))}
  52. </StackItem>
  53. )
  54. const handleDrop = useCallback(
  55. (grpIdx: number) => (item) => {
  56. const { id } = item
  57. const nextStack = produce(stack, (draft) => {
  58. const prevLoc = findMetricLocation(id, stack)
  59. if (prevLoc) {
  60. draft[prevLoc[0]].splice(prevLoc[1], 1)
  61. }
  62. draft[grpIdx].push(id)
  63. })
  64. onStackGroupChange(nextStack)
  65. },
  66. [stack]
  67. )
  68. const stackGroups = stack.map((group, grpIdx) => (
  69. <StackItem
  70. key={grpIdx}
  71. className={Styles.stackItem}
  72. onDrop={handleDrop(grpIdx)}
  73. >
  74. {group.map((id) => (
  75. <MetricItem key={id} id={id} name={metrics[id]} />
  76. ))}
  77. </StackItem>
  78. ))
  79. const addStackGroup = useCallback(
  80. () => {
  81. const nextStack = produce(stack, (draft) => {
  82. draft.push([])
  83. })
  84. onStackGroupChange(nextStack)
  85. },
  86. [stack]
  87. )
  88. return (
  89. <DndProvider backend={HTML5Backend}>
  90. {metricList}
  91. <div className={Styles.groups}>
  92. {stackGroups}
  93. <div className={`${Styles.stackItem} ${Styles.add}`}>
  94. <Tooltip title="点击添加堆叠分组">
  95. <Icon onClick={addStackGroup} type="plus" />
  96. </Tooltip>
  97. </div>
  98. </div>
  99. </DndProvider>
  100. )
  101. }
  102. export default StackContainer