Block.jsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. import React from 'react'
  2. import { shallowEqualImmutable, getY, isMac } from 'utils/utils'
  3. import uiActions from 'actions/ui'
  4. import newEventActions from 'actions/newEvent'
  5. import newEventStores from 'stores/newEvent'
  6. import {
  7. getEventBlockByBid,
  8. filterBlocks,
  9. getBidList
  10. } from 'stores/funcs/newEvent'
  11. import { dragPosition, dragUtils } from 'stores/funcs/event/dragUtils'
  12. import { CONTEXT_MENU_TYPE, EVENT_BLOCK_TYPE, BLOCK_OP_TYPE } from 'const/const'
  13. export default class Block extends React.Component {
  14. constructor(props) {
  15. super(props)
  16. this.curBlockId = newEventStores.curBlockId
  17. this.multiBlockIds = newEventStores.multiBlockIds
  18. this.state = {
  19. isActive: this.isActive(props.bid),
  20. isEnable: this.isEnable(props.bid),
  21. isExpand: this.isExpand(props.bid),
  22. hasChildren: this.hasChildren(props.bid)
  23. }
  24. this.onEventChange = this.onEventChange.bind(this)
  25. this.isActive = this.isActive.bind(this)
  26. this.isEnable = this.isEnable.bind(this)
  27. this.isExpand = this.isExpand.bind(this)
  28. this.onSelectBlock = this.onSelectBlock.bind(this)
  29. this.onToggleBlockExpand = this.onToggleBlockExpand.bind(this)
  30. this.getLineColor = this.getLineColor.bind(this)
  31. this.getGapLeft = this.getGapLeft.bind(this)
  32. // 拖拽
  33. this.allowDrag = this.allowDrag.bind(this)
  34. this.allowDrop = this.allowDrop.bind(this)
  35. this.getPosition = this.getPosition.bind(this)
  36. this.onDragStart = this.onDragStart.bind(this)
  37. this.onDragEnd = this.onDragEnd.bind(this)
  38. this.onDragOver = this.onDragOver.bind(this)
  39. this.onDrop = this.onDrop.bind(this)
  40. // 右键
  41. this.onContextMenu = this.onContextMenu.bind(this)
  42. }
  43. shouldComponentUpdate(nextProps, nextState) {
  44. return (
  45. !shallowEqualImmutable(this.props, nextProps) ||
  46. !shallowEqualImmutable(this.state, nextState)
  47. )
  48. }
  49. componentDidUpdate(preProps) {
  50. let obj = {}
  51. // bid改变时需要更新
  52. if (preProps.bid !== this.props.bid) {
  53. obj.isActive = this.isActive()
  54. obj.isEnable = this.isEnable()
  55. obj.isExpand = this.isExpand()
  56. obj.hasChildren = this.hasChildren()
  57. }
  58. if (Object.keys(obj).length > 0) {
  59. this.setState(obj)
  60. }
  61. }
  62. componentDidMount() {
  63. this.unsubscribe = newEventStores.listen(this.onEventChange)
  64. }
  65. componentWillUnmount() {
  66. this.unsubscribe()
  67. }
  68. onEventChange(status) {
  69. let obj = {}
  70. if (status.types.includes('curBlockId')) {
  71. if (this.curBlockId !== status.data.curBlockId) {
  72. this.curBlockId = status.data.curBlockId
  73. let isActive = this.isActive()
  74. if (this.state.isActive !== isActive) {
  75. obj.isActive = isActive
  76. }
  77. }
  78. }
  79. if (status.types.includes('multiBlockIds')) {
  80. this.multiBlockIds = status.data.multiBlockIds
  81. let isActive = this.isActive()
  82. if (this.state.isActive !== isActive) {
  83. obj.isActive = isActive
  84. }
  85. }
  86. if (status.types.includes('toggleBlockProp')) {
  87. if (this.props.bid === status.data.affectBlockId) {
  88. if (status.data.prop === 'skip') {
  89. let isEnable = this.isEnable()
  90. if (this.state.isEnable !== isEnable) {
  91. obj.isEnable = isEnable
  92. }
  93. } else if (status.data.prop === 'expand') {
  94. let isExpand = this.isExpand()
  95. if (this.state.isExpand !== isExpand) {
  96. obj.isExpand = isExpand
  97. }
  98. }
  99. }
  100. }
  101. if (
  102. (status.types.includes('changeBlockChildren') &&
  103. status.data &&
  104. status.data.parentBid === this.props.bid) ||
  105. (status.types.includes('reloadBlock') &&
  106. this.props.bid === status.data.affectBlockId)
  107. ) {
  108. let hasChildren = this.hasChildren()
  109. if (hasChildren !== this.state.hasChildren) {
  110. obj.hasChildren = hasChildren
  111. }
  112. }
  113. if (Object.keys(obj).length > 0) {
  114. this.setState(obj)
  115. }
  116. }
  117. isActive(bid) {
  118. let _bid = bid || this.props.bid
  119. return this.curBlockId === _bid || this.multiBlockIds.indexOf(_bid) >= 0
  120. }
  121. isEnable(bid) {
  122. let _bid = bid || this.props.bid
  123. let nodeId = this.props.eventNodeId
  124. let result = true
  125. let block = getEventBlockByBid(_bid, nodeId)
  126. if (block) {
  127. result = !block.skip
  128. }
  129. return result
  130. }
  131. isExpand(bid) {
  132. let _bid = bid || this.props.bid
  133. let nodeId = this.props.eventNodeId
  134. let result = true
  135. let block = getEventBlockByBid(_bid, nodeId)
  136. if (block) {
  137. result = block.expand !== false
  138. }
  139. return result
  140. }
  141. hasChildren(bid) {
  142. let _bid = bid || this.props.bid
  143. let nodeId = this.props.eventNodeId
  144. let result = false
  145. let block = getEventBlockByBid(_bid, nodeId)
  146. if (block) {
  147. if (block.eventId) {
  148. result = block.ast && block.ast.args && block.ast.args.length > 0
  149. } else {
  150. switch (block.op) {
  151. case BLOCK_OP_TYPE.IF:
  152. case BLOCK_OP_TYPE.ELSE:
  153. case BLOCK_OP_TYPE.FOR:
  154. case BLOCK_OP_TYPE.WHILE:
  155. result =
  156. block.args &&
  157. block.args[1] &&
  158. block.args[1].args &&
  159. block.args[1].args.length > 0
  160. break
  161. case BLOCK_OP_TYPE.BLOCK:
  162. result = block.args && block.args.length > 0
  163. break
  164. }
  165. }
  166. }
  167. return result
  168. }
  169. onSelectBlock(e, allowMulti = false) {
  170. if (
  171. this.props.eventNodeId &&
  172. this.props.eventNodeId !== newEventStores.curEventNodeId
  173. ) {
  174. newEventActions.selectEvent({ eventNodeId: this.props.eventNodeId })
  175. } else {
  176. // 多选
  177. // let isCtrl = e && (isMac ? e.metaKey : e.ctrlKey)
  178. // if (isCtrl) {
  179. // newEventActions.selectMultiBlocks({ blockId: this.props.bid })
  180. // return
  181. // }
  182. // let isShift = e && e.shiftKey
  183. // if (isShift) {
  184. // newEventActions.shiftSelectBlocks({ blockId: this.props.bid })
  185. // return
  186. // }
  187. }
  188. if (
  189. !this.state.isActive ||
  190. (this.multiBlockIds.length > 0 &&
  191. (!allowMulti ||
  192. (allowMulti && !this.multiBlockIds?.includes(this.props.bid))))
  193. ) {
  194. newEventActions.selectBlock({ blockId: this.props.bid })
  195. }
  196. }
  197. onToggleBlockExpand() {
  198. newEventActions.toggleBlockProp({
  199. nodeId: this.props.eventNodeId,
  200. blockId: this.props.bid,
  201. prop: 'expand'
  202. })
  203. }
  204. getLineColor() {
  205. let group = ['red', 'green', 'purple', 'ching', 'orange', 'blue']
  206. return group[this.props.layer % 6]
  207. }
  208. getNextLineColor() {
  209. let group = ['red', 'green', 'purple', 'ching', 'orange', 'blue']
  210. return group[(this.props.layer + 1) % 6]
  211. }
  212. getGapLeft() {
  213. return 8 + this.props.layer * 20 + (this.props.layer + 1)
  214. }
  215. getGroupColor() {
  216. const group = ['color1', 'color2', 'color3', 'color4', 'color5']
  217. // const group = ['#7F438F', '#E5C88E', '#EEB2B2', '#B5D8F1', '#CBFF97']
  218. return group[this.props.groupLayer % 5]
  219. }
  220. blockMainStyle() {
  221. if (this.props.groupLayer >= 1) {
  222. return {
  223. paddingLeft: `${(this.props.nestGroupLayer - 1) * 2}px`,
  224. marginRight: `${this.props.groupLayer * 2}px`
  225. }
  226. }
  227. return null
  228. }
  229. getLoops(bid) {
  230. let _bid = bid || this.props.bid
  231. let nodeId = this.props.eventNodeId
  232. let block = getEventBlockByBid(_bid, nodeId)
  233. if (
  234. block &&
  235. (block.op === BLOCK_OP_TYPE.FOR || block.op === BLOCK_OP_TYPE.WHILE)
  236. ) {
  237. if (this.props.loops) {
  238. return JSON.stringify(
  239. [].concat(JSON.parse(this.props.loops), [block.ln])
  240. )
  241. } else {
  242. return JSON.stringify([block.ln])
  243. }
  244. } else {
  245. return this.props.loops
  246. }
  247. }
  248. indexLeftStyle() {
  249. if (
  250. this.props.offspringInNestGroupLayer >= 2 &&
  251. this.props.nestGroupLayer === 0
  252. ) {
  253. return {
  254. marginLeft: 20 + (this.props.offspringInNestGroupLayer - 1) * 2 + 'px'
  255. }
  256. }
  257. return undefined
  258. }
  259. extraGapGroupLayer() {
  260. if (
  261. this.props.offspringInNestGroupLayer >= 2 &&
  262. this.props.nestGroupLayer === 0
  263. ) {
  264. return this.props.offspringInNestGroupLayer
  265. } else {
  266. return this.props.extraGapGroupLayer || 0
  267. }
  268. }
  269. //拖拽
  270. allowDrag() {
  271. let allow = true
  272. if (
  273. document.activeElement.tagName === 'INPUT' ||
  274. document.activeElement.tagName === 'TEXTAREA'
  275. ) {
  276. allow = false // block dragging
  277. }
  278. if (!this.isActive(this.props.bid)) {
  279. this.onSelectBlock()
  280. }
  281. return allow
  282. }
  283. allowDrop(bid, position, multiInfo) {
  284. let result = false
  285. let nodeId = this.props.eventNodeId
  286. let dragBlock = getEventBlockByBid(bid, nodeId)
  287. let targetBlock = getEventBlockByBid(this.props.bid)
  288. let dropBlock = undefined
  289. switch (position) {
  290. case dragPosition.mid:
  291. // 加到targetBlock里面
  292. dropBlock = targetBlock
  293. break
  294. case dragPosition.bot:
  295. case dragPosition.top:
  296. // 实际是放在targetBlock的父级
  297. if (targetBlock.parentBid) {
  298. let targetParentBlock = getEventBlockByBid(
  299. targetBlock.parentBid,
  300. nodeId
  301. )
  302. if (targetParentBlock) {
  303. dropBlock = targetParentBlock
  304. } else {
  305. dropBlock = undefined
  306. }
  307. }
  308. break
  309. }
  310. if (multiInfo && multiInfo.length > 0) {
  311. // 多个移动
  312. let blockTypes = []
  313. multiInfo = filterBlocks(multiInfo)
  314. multiInfo.forEach(bid => {
  315. let block = getEventBlockByBid(bid, nodeId)
  316. if (block && block.type) {
  317. let type = block.type
  318. if (blockTypes.indexOf(type) === -1) {
  319. blockTypes.push(type)
  320. }
  321. }
  322. })
  323. if (
  324. targetBlock &&
  325. targetBlock.type === EVENT_BLOCK_TYPE.ROOT &&
  326. blockTypes.length === 1 &&
  327. blockTypes[0] === EVENT_BLOCK_TYPE.ROOT &&
  328. position !== dragPosition.mid
  329. ) {
  330. // 拖的和drop的都是Root只可放上和下
  331. result = true
  332. } else if (dropBlock) {
  333. // 提炼出可drop的情况
  334. if (dropBlock.type === EVENT_BLOCK_TYPE.ACTION) {
  335. if (
  336. blockTypes.length === 1 &&
  337. blockTypes[0] === EVENT_BLOCK_TYPE.STATUS &&
  338. dropBlock.action &&
  339. dropBlock.action.callback === true
  340. ) {
  341. result = true
  342. }
  343. } else if (dropBlock.type === EVENT_BLOCK_TYPE.COMMENT) {
  344. result = false
  345. } else {
  346. if (
  347. blockTypes.indexOf(EVENT_BLOCK_TYPE.ROOT) === -1 &&
  348. blockTypes.indexOf(EVENT_BLOCK_TYPE.STATUS) === -1
  349. ) {
  350. result = true
  351. }
  352. }
  353. }
  354. } else {
  355. // 单个移动
  356. if (
  357. dragBlock &&
  358. targetBlock &&
  359. dragBlock.type === EVENT_BLOCK_TYPE.ROOT &&
  360. targetBlock.type === EVENT_BLOCK_TYPE.ROOT &&
  361. position !== dragPosition.mid
  362. ) {
  363. // 拖的和drop的都是Root只可放上和下
  364. result = true
  365. } else if (dragBlock && dropBlock) {
  366. // 提炼出可drop的情况
  367. if (dropBlock.type === EVENT_BLOCK_TYPE.ACTION) {
  368. if (
  369. dragBlock.type === EVENT_BLOCK_TYPE.STATUS &&
  370. dropBlock.action &&
  371. dropBlock.action.callback === true
  372. ) {
  373. result = true
  374. }
  375. } else if (dropBlock.type === EVENT_BLOCK_TYPE.COMMENT) {
  376. result = false
  377. } else {
  378. if (
  379. dragBlock.type !== EVENT_BLOCK_TYPE.ROOT &&
  380. dragBlock.type !== EVENT_BLOCK_TYPE.STATUS
  381. ) {
  382. result = true
  383. }
  384. }
  385. }
  386. }
  387. return result
  388. }
  389. getPosition(dragBlockId, deltaTop, maxHeight, multiInfo) {
  390. let position = dragPosition.mid
  391. let mid1 = maxHeight / 3
  392. let mid2 = (maxHeight * 2) / 3
  393. if (deltaTop >= 0 && deltaTop <= mid1) {
  394. position = dragPosition.top
  395. } else if (deltaTop >= mid2 && deltaTop <= maxHeight) {
  396. position = dragPosition.bot
  397. }
  398. let nodeId = this.props.eventNodeId
  399. let dropBlock = getEventBlockByBid(this.props.bid, nodeId)
  400. if (multiInfo && multiInfo.length > 0) {
  401. // 多个移动
  402. let blockTypes = []
  403. multiInfo = filterBlocks(multiInfo)
  404. multiInfo.forEach(bid => {
  405. let block = getEventBlockByBid(bid, nodeId)
  406. if (block && block.type) {
  407. blockTypes.push(block.type)
  408. }
  409. })
  410. if (
  411. dropBlock &&
  412. dropBlock.type === EVENT_BLOCK_TYPE.ROOT &&
  413. blockTypes.indexOf(EVENT_BLOCK_TYPE.ROOT) === -1
  414. ) {
  415. // drop为root且drap均非root的情况
  416. position = dragPosition.mid
  417. }
  418. } else {
  419. // 单个移动
  420. let dragBlock = getEventBlockByBid(dragBlockId, nodeId)
  421. if (
  422. dropBlock &&
  423. dropBlock.type === EVENT_BLOCK_TYPE.ROOT &&
  424. !(dragBlock && dragBlock.type === EVENT_BLOCK_TYPE.ROOT)
  425. ) {
  426. // 不同为root的情况且drop的root的情况
  427. position = dragPosition.mid
  428. }
  429. }
  430. return position
  431. }
  432. onDragStart(e) {
  433. dragUtils.isDragging = true
  434. e.stopPropagation()
  435. if (!this.allowDrag()) {
  436. e.preventDefault()
  437. return
  438. }
  439. e.dataTransfer.effectAllowed = 'move'
  440. dragUtils.setDraggingItemInfo(this.props.bid, this.state.isExpand)
  441. dragUtils.setDraggingMultiItemsInfo(this.multiBlockIds)
  442. // 对block的处理
  443. if (this.multiBlockIds.length === 0) {
  444. if (this.state.isExpand && this.state.hasChildren) {
  445. // 关闭起来
  446. newEventActions.toggleBlockProp({
  447. nodeId: this.props.eventNodeId,
  448. blockId: this.props.bid,
  449. prop: 'expand',
  450. value: false,
  451. addRecord: false
  452. })
  453. }
  454. }
  455. }
  456. onDragEnd(e) {
  457. // drop先执行
  458. e.stopPropagation()
  459. if (dragUtils.isDragging) {
  460. dragUtils.isDragging = false
  461. let dragInfo = dragUtils.getDraggingItemInfo()
  462. // 对block的处理
  463. if (dragInfo) {
  464. newEventActions.toggleBlockProp({
  465. nodeId: this.props.eventNodeId,
  466. blockId: dragInfo.bid,
  467. prop: 'expand',
  468. value: dragInfo.expand,
  469. addRecord: false
  470. })
  471. }
  472. dragUtils.resetDraggingItem()
  473. }
  474. }
  475. onDragOver(e) {
  476. e.stopPropagation()
  477. if (!dragUtils.isDragging) {
  478. return
  479. }
  480. e.preventDefault()
  481. let allowDrop = true
  482. let deltaTop =
  483. e.clientY -
  484. getY(e.currentTarget) +
  485. document.querySelector('.jenga-panel').scrollTop
  486. let maxHeight = e.currentTarget.clientHeight
  487. let position = dragPosition.mid
  488. let info = dragUtils.getDraggingItemInfo()
  489. let multiInfo = dragUtils.getDraggingMultiItemsInfo()
  490. if (multiInfo && multiInfo.length > 0) {
  491. // 多个移动
  492. // console.log('多个移动。。。')
  493. multiInfo = filterBlocks(multiInfo)
  494. let bidList = []
  495. multiInfo.forEach(bid => {
  496. getBidList({ blockId: bid, list: bidList })
  497. })
  498. if (bidList.indexOf(this.props.bid) > -1) {
  499. // dragOver为自己或childern时不允许
  500. allowDrop = false
  501. // console.log('包含自己。。。')
  502. } else {
  503. let blockTypes = []
  504. let nodeId = this.props.eventNodeId
  505. multiInfo.forEach(bid => {
  506. let block = getEventBlockByBid(bid, nodeId)
  507. if (block && block.type) {
  508. let type = block.type
  509. if (blockTypes.indexOf(type) === -1) {
  510. blockTypes.push(type)
  511. }
  512. }
  513. })
  514. if (
  515. multiInfo.length > 1 &&
  516. blockTypes.indexOf(EVENT_BLOCK_TYPE.ROOT) > -1 &&
  517. blockTypes.length > 1
  518. ) {
  519. // 多个待移动block中同时有root和非root时不允许
  520. allowDrop = false
  521. // console.log('同时有root和非root。。。')
  522. } else {
  523. // 对drag的处理
  524. // console.log('ok。。。')
  525. position = this.getPosition(info.bid, deltaTop, maxHeight, multiInfo)
  526. allowDrop = this.allowDrop(info.bid, position, multiInfo)
  527. }
  528. }
  529. } else {
  530. // 单个移动
  531. // console.log('单个移动。。。')
  532. // 相同bid不允许
  533. if (this.props.bid !== info.bid) {
  534. // 对drag的处理
  535. position = this.getPosition(info.bid, deltaTop, maxHeight, multiInfo)
  536. allowDrop = this.allowDrop(info.bid, position, multiInfo)
  537. } else {
  538. allowDrop = false
  539. }
  540. }
  541. dragUtils.setOverItemInfo(position, this.props.bid, allowDrop)
  542. }
  543. onDrop(e) {
  544. e.stopPropagation()
  545. e.preventDefault()
  546. dragUtils.isDragging = false
  547. let dragInfo = dragUtils.getDraggingItemInfo()
  548. let multiInfo = dragUtils.getDraggingMultiItemsInfo()
  549. multiInfo = filterBlocks(multiInfo)
  550. let overInfo = dragUtils.getOverItemInfo()
  551. // 对block的处理
  552. if (multiInfo.length === 0) {
  553. if (dragInfo) {
  554. newEventActions.toggleBlockProp({
  555. nodeId: this.props.eventNodeId,
  556. blockId: dragInfo.bid,
  557. prop: 'expand',
  558. value: dragInfo.expand,
  559. addRecord: false
  560. })
  561. }
  562. }
  563. // 移动block
  564. if (overInfo && overInfo.isAllow) {
  565. newEventActions.moveBlock({
  566. srcBlockId: dragInfo.bid,
  567. targetBlockId: overInfo.bid,
  568. position: overInfo.overPosition,
  569. multiSrcBlockIds: multiInfo
  570. })
  571. }
  572. dragUtils.resetDraggingItem()
  573. }
  574. onContextMenu(e) {
  575. e.preventDefault()
  576. e.stopPropagation()
  577. this.onSelectBlock(e, true)
  578. let info = {
  579. x: e.pageX,
  580. y: e.pageY,
  581. type: CONTEXT_MENU_TYPE.EVENT
  582. }
  583. uiActions.contextMenu(info)
  584. }
  585. render() {
  586. return null
  587. }
  588. }