Item.tsx 4.7 KB

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