341 lines
9.3 KiB
JavaScript

/**
* 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 (
<>
<div {...blockProps}>
<figure className="iup-video-embed-wrapper">
<iframe
src={`https://iframe.mediadelivery.net/embed/${IUP_VIDEO.libraryId}/${attributes.video_id}?autoplay=${attributes.autoplay}&preload=${attributes.preload}&loop=${attributes.loop}&muted=${attributes.muted}`}
loading="lazy"
className="iup-video-embed"
sandbox="allow-scripts allow-same-origin allow-presentation"
allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"
allowFullScreen={true}
></iframe>
</figure>
{showOverlay && (
<button
className="iup-video-overlay"
onClick={() => setShowOverlay(false)}
/>
)}
</div>
<BlockControls group="other">
<ToolbarButton
onClick={() => setAttributes({video_id: null})}
icon={replace}
label={__('Replace Video', 'infinite-uploads')}
/>
</BlockControls>
<InspectorControls>
<PanelBody title={__('Settings')}>
<VideoCommonSettings
setAttributes={setAttributes}
attributes={attributes}
/>
</PanelBody>
</InspectorControls>
</>
);
} 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 (
<>
<div {...blockProps}>
<div className="ratio-16-9-outer">
<div className="ratio-16-9-inner" style={style}>
<div className="ratio-16-9-content">
<Spinner
style={{
height: '0.9em',
width: '0.9em',
}}
/>{' '}
{label}
</div>
</div>
</div>
</div>
<InspectorControls>
<PanelBody title={__('Settings')}>
<VideoCommonSettings
setAttributes={setAttributes}
attributes={attributes}
/>
</PanelBody>
</InspectorControls>
</>
);
}
} else {
return (
<div {...blockProps}>
<Placeholder
icon={InfiniteUploadsIcon}
instructions={__(
'Upload a new video direct to the cloud or select a video from your cloud library.',
'infinite-uploads'
)}
label={__('Infinite Uploads Video', 'infinite-uploads')}
>
<div className="placeholder-wrapper">
<div className="uppy-wrapper">
{!isUploading ? (
<DragDrop
width="100%"
height="100%"
// assuming `props.uppy` contains an Uppy instance:
uppy={uppy}
locale={{
strings: {
// Text to show on the droppable area.
// `%{browse}` is replaced with a link that opens the system file selection dialog.
dropHereOr: __(
'Drop video file here or %{browse}.',
'infinite-uploads'
),
// Used as the label for the link that opens the system file selection dialog.
browse: __(
'browse files',
'infinite-uploads'
),
},
}}
/>
) : (
''
)}
<StatusBar
// assuming `props.uppy` contains an Uppy instance:
uppy={uppy}
hideUploadButton={false}
hideAfterFinish={true}
showProgressDetails
/>
</div>
{!isUploading && (
<LibraryModal selectVideo={selectVideo}/>
)}
</div>
</Placeholder>
</div>
);
}
}