/** * WordPress components that create the necessary UI elements for the block * * @see https://developer.wordpress.org/block-editor/packages/packages-components/ * * See https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/video/ */ /** * React hook that is used to mark the block wrapper element. * It provides all the necessary props like the class name. * * @see https://developer.wordpress.org/block-editor/packages/packages-block-editor/#useBlockProps */ import {useSelect} from '@wordpress/data'; import { PanelBody, Placeholder, Spinner, ToolbarButton, } from '@wordpress/components'; import {useBlockProps, InspectorControls, BlockControls} from '@wordpress/block-editor'; import {replace} from '@wordpress/icons'; import {InfiniteUploadsIcon} from './components/Images'; import {__, sprintf} from '@wordpress/i18n'; import {useRef, useEffect, useState} from '@wordpress/element'; import Uppy from '@uppy/core'; import Tus from '@uppy/tus'; import {DragDrop, StatusBar, useUppy} from '@uppy/react'; import UppyCreateVid from './edit-uppy-plugin'; import VideoCommonSettings from './edit-common-settings'; import LibraryModal from './components/LibraryModal'; import '../../assets/css/admin.css'; //pulled from wp_localize_script later /** * The edit function describes the structure of your block in the context of the * editor. This represents what the editor will render when the block is used. * * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#edit * * @param {Object} props Properties passed to the function. * @param {Object} props.attributes Available block attributes. * @param {Function} props.setAttributes Function that updates individual attributes. * * @return {WPElement} Element to render. */ export default function Edit({clientId, attributes, setAttributes}) { const blockProps = useBlockProps(); const isSelected = useSelect((select) => select('core/block-editor').isBlockSelected(clientId, true) ); /* Possible video statuses Created = 0 Uploaded = 1 Processing = 2 Transcoding = 3 Finished = 4 Error = 5 UploadFailed = 6 */ const [video, setVideo] = useState(null); const [isUploading, setUploading] = useState(false); const [showOverlay, setShowOverlay] = useState(true); const uploadAuth = useRef(null); //reenable the click overlay whenever the block is unselected so we can click back on it useEffect(() => { if (!isSelected) { setShowOverlay(true); } }, [isSelected]); useEffect(() => { if (attributes.video_id) { getVideo(); } }, []); //poll the video status every 5 seconds while status is 2-3 useEffect(() => { if (video && (video.status === 2 || video.status === 3)) { const interval = setInterval(() => { getVideo(); }, 5000); return () => clearInterval(interval); } }, [video]); const uppy = useUppy(() => { return new Uppy({ debug: true, restrictions: { maxNumberOfFiles: 1, allowedFileTypes: ['video/*'], }, autoProceed: true, allowMultipleUploadBatches: false, onBeforeUpload: (files) => { //TODO trigger error if video_id is null }, }) .use(Tus, { endpoint: 'https://video.bunnycdn.com/tusupload', retryDelays: [0, 1000, 3000, 5000, 10000], onBeforeRequest: (req, file) => { //console.log('Video Auth:', uploadAuth.current[file.id]); if (uploadAuth.current[file.id]) { setAttributes({ video_id: uploadAuth.current[file.id].VideoId, }); attributes.video_id = uploadAuth.current[file.id].VideoId; //I don't know why this is needed } else { throw new Error('Error fetching auth.'); return false; } req.setHeader( 'AuthorizationSignature', uploadAuth.current[file.id].AuthorizationSignature ); req.setHeader( 'AuthorizationExpire', uploadAuth.current[file.id].AuthorizationExpire ); req.setHeader('VideoId', uploadAuth.current[file.id].VideoId); req.setHeader('LibraryId', IUP_VIDEO.libraryId); }, }) .use(UppyCreateVid, {uploadAuth}); //our custom plugin }); let uploadSuccess = useRef(false); uppy.on('upload', (data) => { // data object consists of `id` with upload ID and `fileIDs` array // with file IDs in current upload // data: { id, fileIDs } setUploading(true); uploadSuccess.current = false; }); uppy.on('cancel-all', () => { setUploading(false); }); uppy.on('error', (error) => { console.error(error.stack); setUploading(false); }); uppy.on('upload-error', (file, error, response) => { console.log('error with file:', file.id); console.log('error message:', error); setUploading(false); }); uppy.on('upload-success', (file, response) => { if (!uploadSuccess.current) { uploadSuccess.current = true; getVideo(); } setUploading(false); }); function getVideo() { if (!attributes.video_id) { return false; } const options = { method: 'GET', headers: { Accept: 'application/json', AccessKey: IUP_VIDEO.apiKey, }, }; fetch( `https://video.bunnycdn.com/library/${IUP_VIDEO.libraryId}/videos/${attributes.video_id}`, options ) .then((response) => response.json()) .then((data) => { console.log('Video:', data); setVideo(data); }) .catch((error) => { console.error(error); }); } const selectVideo = (video) => { setAttributes({video_id: video.guid}); setVideo(video); setUploading(false); }; if ( !isUploading && attributes.video_id && video && [1, 2, 3, 4].includes(video.status) ) { if (video.status === 4) { return ( <>
{showOverlay && (
setAttributes({video_id: null})} icon={replace} label={__('Replace Video', 'infinite-uploads')} /> ); } else { let label = ''; let style = {}; if (video.status === 3) { label = sprintf( __('Video %d%% encoded...', 'infinite-uploads'), video.encodeProgress ); style = { backgroundImage: `url("${IUP_VIDEO.cdnUrl}/${attributes.video_id}/${video.thumbnailFileName}")`, }; } else if (video.status <= 1) { label = __('Awaiting Upload...', 'infinite-uploads'); } else if (video.status > 4) { label = __('Video Error. Upload again.', 'infinite-uploads'); } else { label = sprintf( __('Video %d%% processed...', 'infinite-uploads'), video.encodeProgress ); } return ( <>
{' '} {label}
); } } else { return (
{!isUploading ? ( ) : ( '' )}
{!isUploading && ( )}
); } }