eshape.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import { useEffect, useMemo, useState } from "react"
  2. import { Button, Form, Input, Modal, Select } from 'antd'
  3. import { CloseOutlined, PlusOutlined } from '@ant-design/icons'
  4. import InputColor from 'react-input-color';
  5. import style from './style.module.scss'
  6. import ReactEditeTable, { InputEditor } from 'react-edit-table'
  7. import type { Board, BoardShape, ShapeType, ExtractShape } from "./board"
  8. import type { RefObject, ComponentType } from 'react'
  9. const ColorInput = ({ shape }: { shape: BoardShape }) => (
  10. <Form.Item label="颜色" className={style['def-color-item']}>
  11. <InputColor
  12. initialValue={shape.data.color || '#000'}
  13. onChange={(color) => shape.setColor(color.rgba)}
  14. />
  15. </Form.Item>
  16. )
  17. const sizeOptions = [6,7,8,9,10,11,12,13,14,16,18,20,28,36,48,72]
  18. .map(size => ({ label: `${size}px`, value: size }))
  19. const FontSizeInput = ({ shape }: { shape: ExtractShape<'fontSize'> }) => (
  20. <Form.Item label="字号">
  21. <Select
  22. defaultValue={shape.data.fontSize}
  23. style={{ width: 80 }}
  24. onChange={(size) => shape.setFontSize(size)}
  25. options={sizeOptions}
  26. />
  27. </Form.Item>
  28. )
  29. const TextInput = ({ shape }: { shape: ExtractShape<'text'> }) => (
  30. <Form.Item label="内容">
  31. <Input
  32. style={{ width: 120 }}
  33. defaultValue={shape.data.text}
  34. onChange={(ev) => shape.setText(ev.target.value)}
  35. />
  36. </Form.Item>
  37. )
  38. const ContentInput = ({ shape }: { shape: ExtractShape<'content'> }) => {
  39. const [edit, setEdit] = useState(false)
  40. const [content, setContent] = useState(shape.data.content || [['', '']])
  41. // eslint-disable-next-line react-hooks/exhaustive-deps
  42. const refer = content[0] || ['', '']
  43. const transformRow = (columns: any[], row?: string[]) => {
  44. const source: { [key in number]: string } = {}
  45. for (let i = 0; i < columns.length; i++) {
  46. source[i] = (row && row[i]) || ''
  47. }
  48. return source
  49. }
  50. const tableAttrs = useMemo(() => ({
  51. columns: refer.map((item, i) => ({
  52. title: `列${i + 1}`,
  53. dataIndex: i,
  54. key: i,
  55. editor: {
  56. type: 'input',
  57. component: InputEditor
  58. }
  59. })),
  60. dataSource: content.map(item => transformRow(refer, item))
  61. }), [content, refer])
  62. const onChange = (data: any) => {
  63. const newContent = [...content]
  64. newContent[data.rowIndex] = [...newContent[data.rowIndex]]
  65. newContent[data.rowIndex][data.key] = data.newValue
  66. setContent(newContent)
  67. }
  68. const onDelete = (data: any) => {
  69. const newContent = [...content]
  70. newContent.splice(data.rowIndex, 1)
  71. setContent(newContent)
  72. }
  73. const onInsert = () => {
  74. setContent([...content, refer.map(() => '')])
  75. }
  76. const onSubmit = () => {
  77. const rowEls = Array.from(document.querySelectorAll('#edit-table .body-container .row-container')) as HTMLDivElement[]
  78. const bound = rowEls.map(row => {
  79. const cells = Array.from(row.querySelectorAll('.cell')) as HTMLDivElement[]
  80. return {
  81. height: row.offsetHeight - 1,
  82. cellWidths: cells.slice(0, -1).map(cell => cell.offsetWidth)
  83. }
  84. })
  85. console.log(bound, content)
  86. }
  87. useEffect(() => {
  88. if (!edit) {
  89. setContent(shape.data.content || [['', '']])
  90. }
  91. }, [edit, shape.data.content])
  92. return (
  93. <Form.Item label="内容">
  94. <Button type="primary" onClick={() => setEdit(true)}>编辑</Button>
  95. <Modal open={edit} onCancel={() => setEdit(false)} onOk={onSubmit}>
  96. <div id="edit-table">
  97. <ReactEditeTable
  98. {...tableAttrs}
  99. onDelete={onDelete}
  100. onChange={onChange}
  101. />
  102. <div className={style['add-table-row']}>
  103. <Button onClick={onInsert} type="primary">
  104. <PlusOutlined className="icon" /> 行
  105. </Button>
  106. </div>
  107. </div>
  108. </Modal>
  109. </Form.Item>
  110. )
  111. }
  112. const shapeCompontes: { [key in ShapeType]: ComponentType<{ shape: any }> } = {
  113. Wall: ColorInput,
  114. Tag: (props) => <><ColorInput {...props} /><FontSizeInput {...props} /><TextInput {...props} /> </>,
  115. Table: (props) => <><ColorInput {...props} /><FontSizeInput {...props} /><ContentInput {...props} /> </>,
  116. Rectangle: ColorInput,
  117. Circle: ColorInput,
  118. Arrow: ColorInput,
  119. Icon: ColorInput,
  120. Cigaret: ColorInput,
  121. FirePoint: ColorInput,
  122. LeftFootPrint: ColorInput,
  123. RightFootPrint: ColorInput,
  124. LeftShoePrint: ColorInput,
  125. RightShoePrint: ColorInput,
  126. FingerPrint: ColorInput,
  127. DeadBody: ColorInput,
  128. BloodStain: ColorInput
  129. }
  130. export type EShapeProps = {
  131. board: RefObject<Board>
  132. }
  133. export const EShape = ({ board }: EShapeProps) => {
  134. const [shape, setShape] = useState<BoardShape | null>(null)
  135. useEffect(() => {
  136. if (board.current) {
  137. const boardRef = board.current
  138. boardRef.bus.on('selectShape', setShape)
  139. return () => boardRef.bus.off('selectShape', setShape)
  140. }
  141. }, [board])
  142. const Edit = shape && shapeCompontes[shape.data.type]
  143. return Edit && (
  144. <div className={style['def-shape-edit']}>
  145. <Edit shape={shape} />
  146. <div className={`ant-form-item ${style['def-close-shape-edit']}`} onClick={() => setShape(null)}>
  147. <CloseOutlined className={`${style['icon']}`} />
  148. </div>
  149. </div>
  150. )
  151. }
  152. export default EShape