Item.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  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, useRef } from 'react'
  21. import { useDrag, useDrop } from 'react-dnd'
  22. import classnames from 'classnames'
  23. import { Menu, Dropdown, message } from 'antd'
  24. import { MenuProps } from 'antd/lib/menu'
  25. import { ISlideFormed } from '../types'
  26. import styles from './SlideThumbnail.less'
  27. interface ISlideThumbnailProps {
  28. className?: string
  29. slide: ISlideFormed
  30. index: number
  31. current: boolean
  32. selected: boolean
  33. onSelect: (slideId: number, append: boolean) => void
  34. onDelete: (targetSlideId: number) => void
  35. onChangeDisplayAvatar: (avatar: string) => void
  36. onMove: (slideId: number, newIdx: number, done?: boolean) => void
  37. }
  38. const ThumbnailRatio = 3 / 4
  39. export const SlideDndItemType = 'DisplaySlideSort'
  40. type SlideDragObject = {
  41. type: string
  42. slideId: number
  43. originalIdx: number
  44. }
  45. type SlideDragCollectedProps = {
  46. isDragging: boolean
  47. }
  48. const SlideThumbnail: React.FC<ISlideThumbnailProps> = (props) => {
  49. const {
  50. slide,
  51. index,
  52. current,
  53. selected,
  54. className,
  55. onDelete,
  56. onSelect,
  57. onChangeDisplayAvatar,
  58. onMove
  59. } = props
  60. const { id: slideId, config } = slide
  61. const { width, height, avatar, backgroundColor } = config.slideParams
  62. const ref = useRef<HTMLDivElement>(null)
  63. const originalIdx = index
  64. const [{ isDragging }, drag] = useDrag<
  65. SlideDragObject,
  66. {},
  67. SlideDragCollectedProps
  68. >({
  69. item: { type: SlideDndItemType, slideId, originalIdx },
  70. end(_, monitor) {
  71. const { slideId: droppedId, originalIdx } = monitor.getItem()
  72. const didDrop = monitor.didDrop()
  73. onMove(droppedId, didDrop ? index : originalIdx, didDrop)
  74. },
  75. collect: (monitor) => ({
  76. isDragging: monitor.isDragging()
  77. })
  78. })
  79. const [, drop] = useDrop<SlideDragObject, {}, SlideDragCollectedProps>({
  80. accept: SlideDndItemType,
  81. canDrop: () => false,
  82. hover: ({ slideId: draggedId }) => {
  83. if (draggedId !== slideId) {
  84. onMove(draggedId, index)
  85. }
  86. }
  87. })
  88. drag(drop(ref))
  89. const handleClickRightMenu: MenuProps['onClick'] = useCallback(
  90. ({ key, domEvent }) => {
  91. domEvent.stopPropagation()
  92. if (key === 'delete') {
  93. onDelete(slideId)
  94. return
  95. }
  96. if (avatar) {
  97. onChangeDisplayAvatar(avatar)
  98. } else {
  99. message.error('请先为该大屏页设置封面')
  100. }
  101. },
  102. [onDelete, onChangeDisplayAvatar, slideId, avatar]
  103. )
  104. const selectSlide = useCallback(
  105. (e: React.MouseEvent) => {
  106. const { shiftKey, metaKey, altKey } = e
  107. e.stopPropagation()
  108. onSelect(slideId, shiftKey || metaKey || altKey)
  109. },
  110. [onSelect, slideId]
  111. )
  112. const cls = classnames({
  113. [className]: !!className,
  114. [styles.current]: current
  115. })
  116. const slideStyle: React.CSSProperties = {
  117. background: avatar && `url(${avatar}) center/cover`,
  118. backgroundColor: `rgba(${backgroundColor.join()})`
  119. }
  120. const divStyle: React.CSSProperties = { opacity: isDragging ? 0.1 : 1 }
  121. if (height / width <= ThumbnailRatio) {
  122. // landscape
  123. slideStyle.top = `${
  124. ((1 - (1 / ThumbnailRatio) * (height / width)) / 2) * 100
  125. }%`
  126. slideStyle.bottom = slideStyle.top
  127. slideStyle.width = '100%'
  128. } else {
  129. // portrait
  130. slideStyle.left = `${((1 - ThumbnailRatio * (width / height)) / 2) * 100}%`
  131. slideStyle.right = slideStyle.left
  132. slideStyle.height = '100%'
  133. }
  134. const thumbnailCls = classnames({
  135. [styles.thumbnail]: true,
  136. [styles.selected]: selected
  137. })
  138. return (
  139. <Dropdown
  140. overlay={
  141. <Menu onClick={handleClickRightMenu}>
  142. <Menu.Item key="setAsCover">设置为封面</Menu.Item>
  143. <Menu.Item key="delete">删除</Menu.Item>
  144. </Menu>
  145. }
  146. trigger={['contextMenu']}
  147. >
  148. <li className={cls} onClick={selectSlide}>
  149. <div className={styles.serial}>{index + 1}</div>
  150. <div ref={ref} style={divStyle} className={styles.content}>
  151. <div className={thumbnailCls}>
  152. <div className={styles.cover} style={slideStyle} />
  153. </div>
  154. </div>
  155. </li>
  156. </Dropdown>
  157. )
  158. }
  159. export default SlideThumbnail