List.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. /*
  2. * <<
  3. * Davinci
  4. * ==
  5. * Copyright (C) 2016 - 2017 EDP
  6. * ==
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. * >>
  19. */
  20. import React, { useCallback, useEffect, useState } from 'react'
  21. import { DndProvider, useDrop } from 'react-dnd'
  22. import HTML5Backend from 'react-dnd-html5-backend'
  23. import classnames from 'classnames'
  24. import { ISlideFormed } from '../types'
  25. import { Modal } from 'antd'
  26. import Item, { SlideDndItemType } from './Item'
  27. import styles from './SlideThumbnail.less'
  28. interface ISlideThumbnailListProps {
  29. slides: ISlideFormed[]
  30. className?: string
  31. currentSlideId: number
  32. selectedSlideIds: number[]
  33. onChange: (newSlides: ISlideFormed[]) => void
  34. onSelect: (slideId: number, append: boolean) => void
  35. onDelete: (targetSlideId?: number) => void
  36. onChangeDisplayAvatar: (avatar: string) => void
  37. }
  38. const SlideThumbnailList: React.FC<ISlideThumbnailListProps> = (props) => {
  39. const {
  40. slides,
  41. className,
  42. currentSlideId,
  43. selectedSlideIds,
  44. onSelect,
  45. onChange,
  46. onChangeDisplayAvatar,
  47. onDelete
  48. } = props
  49. const [localSlides, setLocalSlides] = useState(slides)
  50. const [movingRange, setMovingRange] = useState<[number, number]>([
  51. slides.length - 1,
  52. 0
  53. ])
  54. useEffect(() => {
  55. setLocalSlides(slides)
  56. setMovingRange([slides.length - 1, 0])
  57. }, [slides])
  58. useEffect(() => {
  59. const deleteSlides = (e: KeyboardEvent) => {
  60. if (e.keyCode !== 8 && e.keyCode !== 46) {
  61. return
  62. }
  63. if (!selectedSlideIds.length) {
  64. return
  65. }
  66. window.removeEventListener('keydown', deleteSlides, false)
  67. Modal.confirm({
  68. title:
  69. selectedSlideIds.length > 1
  70. ? '确认删除所有选中的大屏页?'
  71. : '确认删除此大屏页?',
  72. onOk: () => {
  73. onDelete()
  74. }
  75. })
  76. }
  77. window.addEventListener('keydown', deleteSlides, false)
  78. return () => {
  79. window.removeEventListener('keydown', deleteSlides, false)
  80. }
  81. }, [selectedSlideIds, onDelete])
  82. const cls = classnames({
  83. [styles.thumbnails]: true,
  84. [className]: !!className
  85. })
  86. const moveSlide = useCallback(
  87. (slideId: number, targetIdx: number, done: boolean) => {
  88. const prevIdx = localSlides.findIndex(({ id }) => id === slideId)
  89. const updatedSlides = [...localSlides]
  90. updatedSlides.splice(prevIdx, 1)
  91. updatedSlides.splice(targetIdx, 0, localSlides[prevIdx])
  92. setLocalSlides(updatedSlides)
  93. setMovingRange([
  94. Math.min(movingRange[0], prevIdx, targetIdx),
  95. Math.max(movingRange[1], prevIdx, targetIdx)
  96. ])
  97. if (done) {
  98. const partialSlides = updatedSlides
  99. .slice(movingRange[0], movingRange[1] + 1)
  100. .map((slide, partialIdx) => ({
  101. ...slide,
  102. index: slides[partialIdx + movingRange[0]].index
  103. }))
  104. onChange(partialSlides)
  105. }
  106. },
  107. [movingRange, slides, localSlides, onChange]
  108. )
  109. const [, drop] = useDrop({ accept: SlideDndItemType })
  110. return (
  111. <ul ref={drop} className={cls}>
  112. {localSlides.map((slide, idx) => (
  113. <Item
  114. key={slide.id}
  115. slide={slide}
  116. index={idx}
  117. current={currentSlideId === slide.id}
  118. selected={selectedSlideIds.includes(slide.id)}
  119. onSelect={onSelect}
  120. onDelete={onDelete}
  121. onMove={moveSlide}
  122. onChangeDisplayAvatar={onChangeDisplayAvatar}
  123. />
  124. ))}
  125. </ul>
  126. )
  127. }
  128. const withDnd: React.FC<ISlideThumbnailListProps> = (props) => {
  129. return (
  130. <DndProvider backend={HTML5Backend}>
  131. <SlideThumbnailList {...props} />
  132. </DndProvider>
  133. )
  134. }
  135. export default withDnd