1 | /** |
---|
2 | * External dependencies |
---|
3 | */ |
---|
4 | import { Text, Button, useBreakpointMatch } from '@automattic/jetpack-components'; |
---|
5 | import { Dropdown } from '@wordpress/components'; |
---|
6 | import { gmdateI18n } from '@wordpress/date'; |
---|
7 | import { __, sprintf } from '@wordpress/i18n'; |
---|
8 | import { Icon, edit, cloud, image, media, video } from '@wordpress/icons'; |
---|
9 | import classnames from 'classnames'; |
---|
10 | import { forwardRef } from 'react'; |
---|
11 | /** |
---|
12 | * Internal dependencies |
---|
13 | */ |
---|
14 | import Placeholder from '../placeholder'; |
---|
15 | import ProgressBar from '../progress-bar'; |
---|
16 | import styles from './style.module.scss'; |
---|
17 | /** |
---|
18 | * Types |
---|
19 | */ |
---|
20 | import { VideoThumbnailDropdownProps, VideoThumbnailProps } from './types'; |
---|
21 | import type React from 'react'; |
---|
22 | |
---|
23 | export const VideoThumbnailDropdownButtons = ( { |
---|
24 | onUseDefaultThumbnail, |
---|
25 | onSelectFromVideo, |
---|
26 | onUploadImage, |
---|
27 | onClose, |
---|
28 | isUpdatingPoster = false, |
---|
29 | } ) => { |
---|
30 | return ( |
---|
31 | <> |
---|
32 | { /* TODO: Implement use default and remove disabled class */ } |
---|
33 | <Button |
---|
34 | className={ styles.disabled } |
---|
35 | weight="regular" |
---|
36 | fullWidth |
---|
37 | variant="tertiary" |
---|
38 | icon={ image } |
---|
39 | onClick={ () => { |
---|
40 | onClose(); |
---|
41 | onUseDefaultThumbnail?.(); |
---|
42 | } } |
---|
43 | > |
---|
44 | { __( 'Use default thumbnail', 'jetpack-videopress-pkg' ) } |
---|
45 | </Button> |
---|
46 | <Button |
---|
47 | weight="regular" |
---|
48 | fullWidth |
---|
49 | variant="tertiary" |
---|
50 | icon={ media } |
---|
51 | onClick={ () => { |
---|
52 | onClose(); |
---|
53 | onSelectFromVideo?.(); |
---|
54 | } } |
---|
55 | > |
---|
56 | { __( 'Select from video', 'jetpack-videopress-pkg' ) } |
---|
57 | </Button> |
---|
58 | <Button |
---|
59 | weight="regular" |
---|
60 | fullWidth |
---|
61 | variant="tertiary" |
---|
62 | icon={ cloud } |
---|
63 | disabled={ isUpdatingPoster } |
---|
64 | onClick={ () => { |
---|
65 | onClose(); |
---|
66 | onUploadImage?.(); |
---|
67 | } } |
---|
68 | > |
---|
69 | { __( 'Upload image', 'jetpack-videopress-pkg' ) } |
---|
70 | </Button> |
---|
71 | </> |
---|
72 | ); |
---|
73 | }; |
---|
74 | |
---|
75 | export const VideoThumbnailDropdown = ( { |
---|
76 | onUseDefaultThumbnail, |
---|
77 | onSelectFromVideo, |
---|
78 | onUploadImage, |
---|
79 | }: VideoThumbnailDropdownProps ) => { |
---|
80 | return ( |
---|
81 | <div className={ styles[ 'video-thumbnail-edit' ] }> |
---|
82 | <Dropdown |
---|
83 | position="bottom left" |
---|
84 | renderToggle={ ( { isOpen, onToggle } ) => ( |
---|
85 | <Button |
---|
86 | variant="secondary" |
---|
87 | className={ styles[ 'thumbnail__edit-button' ] } |
---|
88 | icon={ edit } |
---|
89 | onClick={ onToggle } |
---|
90 | aria-expanded={ isOpen } |
---|
91 | /> |
---|
92 | ) } |
---|
93 | renderContent={ ( { onClose } ) => ( |
---|
94 | <VideoThumbnailDropdownButtons |
---|
95 | onClose={ onClose } |
---|
96 | onUseDefaultThumbnail={ onUseDefaultThumbnail } |
---|
97 | onSelectFromVideo={ onSelectFromVideo } |
---|
98 | onUploadImage={ onUploadImage } |
---|
99 | /> |
---|
100 | ) } |
---|
101 | /> |
---|
102 | </div> |
---|
103 | ); |
---|
104 | }; |
---|
105 | |
---|
106 | const UploadingThumbnail = ( { |
---|
107 | uploadProgress = 0, |
---|
108 | isRow = false, |
---|
109 | }: { |
---|
110 | uploadProgress: number; |
---|
111 | isRow?: boolean; |
---|
112 | } ) => { |
---|
113 | const completingTextFull = __( 'Completing upload', 'jetpack-videopress-pkg' ); |
---|
114 | const completingTextCompact = __( 'Completing', 'jetpack-videopress-pkg' ); |
---|
115 | const completingText = isRow ? completingTextCompact : completingTextFull; |
---|
116 | |
---|
117 | const uploadPercentage = `${ Math.floor( uploadProgress * 100 ) }%`; |
---|
118 | const uploadingText = sprintf( |
---|
119 | /* translators: placeholder is the upload percentage */ |
---|
120 | __( 'Uploading %s', 'jetpack-videopress-pkg' ), |
---|
121 | uploadPercentage |
---|
122 | ); |
---|
123 | const infoText = uploadProgress === 1 ? completingText : uploadingText; |
---|
124 | |
---|
125 | return ( |
---|
126 | <div |
---|
127 | className={ classnames( styles[ 'custom-thumbnail' ], { [ styles[ 'is-row' ] ]: isRow } ) } |
---|
128 | > |
---|
129 | <ProgressBar |
---|
130 | className={ styles[ 'progress-bar' ] } |
---|
131 | size="small" |
---|
132 | progress={ uploadProgress } |
---|
133 | /> |
---|
134 | <Text variant={ isRow ? 'body-extra-small' : 'body' } className={ styles[ 'upload-text' ] }> |
---|
135 | { infoText } |
---|
136 | </Text> |
---|
137 | </div> |
---|
138 | ); |
---|
139 | }; |
---|
140 | |
---|
141 | const ProcessingThumbnail = ( { isRow = false }: { isRow?: boolean } ) => ( |
---|
142 | <div className={ styles[ 'custom-thumbnail' ] }> |
---|
143 | <Text variant={ isRow ? 'body-extra-small' : 'body' } className={ styles.pulse }> |
---|
144 | { __( 'Processing', 'jetpack-videopress-pkg' ) } |
---|
145 | </Text> |
---|
146 | </div> |
---|
147 | ); |
---|
148 | |
---|
149 | /** |
---|
150 | * React component to display video thumbnail. |
---|
151 | * |
---|
152 | * @param {VideoThumbnailProps} props - Component props. |
---|
153 | * @returns {React.ReactNode} - VideoThumbnail react component. |
---|
154 | */ |
---|
155 | const VideoThumbnail = forwardRef< HTMLDivElement, VideoThumbnailProps >( |
---|
156 | ( |
---|
157 | { |
---|
158 | className, |
---|
159 | thumbnail: defaultThumbnail, |
---|
160 | duration, |
---|
161 | editable, |
---|
162 | blankIconSize = 96, |
---|
163 | loading = false, |
---|
164 | uploading = false, |
---|
165 | processing = false, |
---|
166 | onUseDefaultThumbnail, |
---|
167 | onSelectFromVideo, |
---|
168 | onUploadImage, |
---|
169 | uploadProgress, |
---|
170 | isRow = false, |
---|
171 | }, |
---|
172 | ref |
---|
173 | ) => { |
---|
174 | const [ isSmall ] = useBreakpointMatch( 'sm' ); |
---|
175 | |
---|
176 | // Mapping thumbnail (Ordered by priority) |
---|
177 | let thumbnail = defaultThumbnail; |
---|
178 | thumbnail = loading ? <Placeholder /> : thumbnail; |
---|
179 | thumbnail = uploading ? ( |
---|
180 | <UploadingThumbnail isRow={ isRow } uploadProgress={ uploadProgress } /> |
---|
181 | ) : ( |
---|
182 | thumbnail |
---|
183 | ); |
---|
184 | thumbnail = processing ? <ProcessingThumbnail isRow={ isRow } /> : thumbnail; |
---|
185 | |
---|
186 | thumbnail = |
---|
187 | typeof thumbnail === 'string' && thumbnail !== '' ? ( |
---|
188 | <img src={ thumbnail } alt={ __( 'Video thumbnail', 'jetpack-videopress-pkg' ) } /> |
---|
189 | ) : ( |
---|
190 | thumbnail |
---|
191 | ); |
---|
192 | |
---|
193 | /** If the thumbnail is not set, use the placeholder with an icon */ |
---|
194 | thumbnail = thumbnail ? ( |
---|
195 | thumbnail |
---|
196 | ) : ( |
---|
197 | <div className={ styles[ 'thumbnail-blank' ] }> |
---|
198 | <Icon icon={ video } size={ blankIconSize } /> |
---|
199 | </div> |
---|
200 | ); |
---|
201 | |
---|
202 | return ( |
---|
203 | <div |
---|
204 | className={ classnames( className, styles.thumbnail, { |
---|
205 | [ styles[ 'is-small' ] ]: isSmall, |
---|
206 | } ) } |
---|
207 | ref={ ref } |
---|
208 | > |
---|
209 | { Boolean( thumbnail ) && editable && ( |
---|
210 | <VideoThumbnailDropdown |
---|
211 | onUseDefaultThumbnail={ onUseDefaultThumbnail } |
---|
212 | onSelectFromVideo={ onSelectFromVideo } |
---|
213 | onUploadImage={ onUploadImage } |
---|
214 | /> |
---|
215 | ) } |
---|
216 | { Number.isFinite( duration ) && ( |
---|
217 | <div className={ styles[ 'video-thumbnail-duration' ] }> |
---|
218 | <Text variant="body-small" component="div"> |
---|
219 | { duration >= 3600 * 1000 |
---|
220 | ? gmdateI18n( 'H:i:s', new Date( duration ) ) |
---|
221 | : gmdateI18n( 'i:s', new Date( duration ) ) } |
---|
222 | </Text> |
---|
223 | </div> |
---|
224 | ) } |
---|
225 | |
---|
226 | <div className={ styles[ 'thumbnail-placeholder' ] }>{ thumbnail }</div> |
---|
227 | </div> |
---|
228 | ); |
---|
229 | } |
---|
230 | ); |
---|
231 | |
---|
232 | export default VideoThumbnail; |
---|