installed plugin Infinite Uploads
version 2.0.8
This commit is contained in:
@ -0,0 +1,31 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import {useEffect, useState} from '@wordpress/element';
|
||||
import Button from "react-bootstrap/Button";
|
||||
import Row from "react-bootstrap/Row";
|
||||
import Col from "react-bootstrap/Col";
|
||||
import {ChromePicker} from "react-color";
|
||||
|
||||
export default function ColorPick({settings, setSettings}) {
|
||||
const [showPicker, setShowPicker] = useState(false);
|
||||
|
||||
return (
|
||||
<Row className="justify-content-start">
|
||||
<Col xs={2}>
|
||||
<Button style={{backgroundColor: settings.PlayerKeyColor}} variant="secondary" className="rounded-pill px-4" onClick={() => {
|
||||
setShowPicker(!showPicker);
|
||||
}}>{settings.PlayerKeyColor}</Button>
|
||||
</Col>
|
||||
{showPicker && (
|
||||
<Col>
|
||||
<ChromePicker
|
||||
color={settings.PlayerKeyColor}
|
||||
onChangeComplete={(color) => {
|
||||
setSettings({...settings, PlayerKeyColor: color.hex});
|
||||
}}
|
||||
disableAlpha
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import {useState, useEffect} from '@wordpress/element';
|
||||
import Modal from 'react-bootstrap/Modal';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
|
||||
export default function DeleteModal({video, setVideos}) {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
const handleClose = () => setShow(false);
|
||||
const handleShow = () => setShow(true);
|
||||
|
||||
function deleteVideo() {
|
||||
const formData = new FormData();
|
||||
formData.append('video_id', video.guid);
|
||||
formData.append('nonce', IUP_VIDEO.nonce);
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: formData,
|
||||
};
|
||||
|
||||
fetch(`${ajaxurl}?action=infinite-uploads-video-delete`, options)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
if (data.success) {
|
||||
setVideos((videos) =>
|
||||
videos.filter((v) => v.guid !== video.guid)
|
||||
);
|
||||
handleClose();
|
||||
} else {
|
||||
console.error(data.data);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Error:', error);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="outline-danger"
|
||||
size="sm"
|
||||
onClick={handleShow}
|
||||
className="rounded-4"
|
||||
>
|
||||
{__('Delete Video', 'infinite-uploads')}
|
||||
</Button>
|
||||
|
||||
<Modal show={show} onHide={handleClose}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>
|
||||
{__('Delete Video:', 'infinite-uploads')}{' '}
|
||||
{video.title}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{__(
|
||||
'Are you sure you would like to delete this video?',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={handleClose}>
|
||||
{__('Cancel', 'infinite-uploads')}
|
||||
</Button>
|
||||
<Button variant="danger" onClick={deleteVideo}>
|
||||
{__('Delete', 'infinite-uploads')}
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import {useState} from '@wordpress/element';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import InputGroup from 'react-bootstrap/InputGroup';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import {VideoSize} from "./VideoAttributes";
|
||||
import UploadModal from "./UploadModal";
|
||||
|
||||
function Header({orderBy, setOrderBy, search, setSearch, selectVideo, getVideos}) {
|
||||
|
||||
const sizeOf = function (bytes) {
|
||||
if (bytes === 0) {
|
||||
return '0 B';
|
||||
}
|
||||
var e = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return (
|
||||
(bytes / Math.pow(1024, e)).toFixed(1) +
|
||||
' ' +
|
||||
' KMGTP'.charAt(e) +
|
||||
'B'
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Row className="align-items-center">
|
||||
<Col sm={8} md={3} className="mb-3 mb-lg-0">
|
||||
<InputGroup>
|
||||
<InputGroup.Text>
|
||||
<span className="dashicons dashicons-search"></span>
|
||||
</InputGroup.Text>
|
||||
<Form.Control
|
||||
placeholder={__('Search', 'infinite-uploads')}
|
||||
aria-label={__('Search', 'infinite-uploads')}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
<Col sm={4} md={2} className="mb-3 mb-lg-0">
|
||||
<InputGroup>
|
||||
<InputGroup.Text>
|
||||
{__('Sort', 'infinite-uploads')}
|
||||
</InputGroup.Text>
|
||||
<Form.Select
|
||||
aria-label={__(
|
||||
'Sort by select',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
value={orderBy}
|
||||
onChange={(e) => setOrderBy(e.target.value)}
|
||||
>
|
||||
>
|
||||
<option value="title">
|
||||
{__('Title', 'infinite-uploads')}
|
||||
</option>
|
||||
<option value="date">
|
||||
{__('Date', 'infinite-uploads')}
|
||||
</option>
|
||||
</Form.Select>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
<Col className="mb-3 mb-lg-0">
|
||||
<Row className="justify-content-center flex-nowrap">
|
||||
<Col className="col-auto">
|
||||
<p className="mb-0">{__("Video Count", 'infinite-uploads')}</p>
|
||||
<span className="h4 text-nowrap">{IUP_VIDEO.settings.VideoCount}</span>
|
||||
</Col>
|
||||
<Col className="col-auto">
|
||||
<p className="mb-0">{__("Library Storage", 'infinite-uploads')}</p>
|
||||
<span className="h4 text-nowrap">{sizeOf(IUP_VIDEO.settings.StorageUsage)}</span>
|
||||
</Col>
|
||||
<Col className="col-auto">
|
||||
<p className="mb-0">{__("Video Bandwidth", 'infinite-uploads')}</p>
|
||||
<span className="h4 text-nowrap">{sizeOf(IUP_VIDEO.settings.TrafficUsage)}</span>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col className="d-flex justify-content-end mb-3 mb-lg-0">
|
||||
<Button
|
||||
variant="outline-secondary"
|
||||
className="rounded-pill text-nowrap"
|
||||
href={IUP_VIDEO.settingsUrl}
|
||||
>
|
||||
<span className="dashicons dashicons-admin-generic"></span>
|
||||
{__('Settings', 'infinite-uploads')}
|
||||
</Button>
|
||||
{!selectVideo && (
|
||||
<UploadModal getVideos={getVideos}/>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default Header;
|
@ -0,0 +1,158 @@
|
||||
import {useEffect, useState} from '@wordpress/element';
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import VideoCard from './VideoCard';
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import Header from './Header';
|
||||
import Paginator from './Paginator';
|
||||
import Spinner from 'react-bootstrap/Spinner';
|
||||
|
||||
export default function Library({selectVideo}) {
|
||||
const [videos, setVideos] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [orderBy, setOrderBy] = useState('date');
|
||||
const [search, setSearch] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const [totalItems, setTotalItems] = useState(0);
|
||||
const [itemsPerPage, setItemsPerPage] = useState(40);
|
||||
const [refreshInterval, setRefreshInterval] = useState(60000);
|
||||
|
||||
//get videos on render
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
setLoading(true);
|
||||
getVideos();
|
||||
}
|
||||
}, [orderBy, page]);
|
||||
|
||||
useEffect(() => {
|
||||
if (search.length > 2 || search.length === 0) {
|
||||
setPage(1);
|
||||
setLoading(true);
|
||||
getVideos();
|
||||
}
|
||||
}, [search]);
|
||||
|
||||
useEffect(() => {
|
||||
//check the videos array if any of the video objects are currently processing or transcoding
|
||||
const processing = videos.find((video) => (video.status === 2 || video.status === 3));
|
||||
if (processing) {
|
||||
setRefreshInterval(10000);
|
||||
} else {
|
||||
setRefreshInterval(60000);
|
||||
}
|
||||
}, [videos]);
|
||||
|
||||
//fetch videos on a 30s interval
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
getVideos();
|
||||
}, refreshInterval);
|
||||
return () => clearInterval(interval);
|
||||
}, [orderBy, page, search, refreshInterval]);
|
||||
|
||||
function getVideos() {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
AccessKey: IUP_VIDEO.apiKey,
|
||||
},
|
||||
};
|
||||
|
||||
fetch(
|
||||
`https://video.bunnycdn.com/library/${IUP_VIDEO.libraryId}/videos?page=${page}&itemsPerPage=${itemsPerPage}&orderBy=${orderBy}&search=${search}`,
|
||||
options
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log('Videos:', data);
|
||||
setVideos(data.items);
|
||||
setTotalItems(data.totalItems);
|
||||
setItemsPerPage(data.itemsPerPage);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!selectVideo && (
|
||||
<h1 className="text-muted mb-3">
|
||||
<img
|
||||
src={IUP_VIDEO.assetBase + '/img/iu-logo-gray.svg'}
|
||||
alt="Infinite Uploads Logo"
|
||||
height="32"
|
||||
width="32"
|
||||
className="me-2"
|
||||
/>
|
||||
{__('Cloud Video Library', 'infinite-uploads')}
|
||||
</h1>
|
||||
)}
|
||||
<Container fluid>
|
||||
<Header
|
||||
{...{
|
||||
orderBy,
|
||||
setOrderBy,
|
||||
search,
|
||||
setSearch,
|
||||
selectVideo,
|
||||
getVideos
|
||||
}}
|
||||
/>
|
||||
|
||||
{!loading ? (
|
||||
<Container fluid>
|
||||
<Row
|
||||
xs={1}
|
||||
sm={1}
|
||||
md={2}
|
||||
lg={3}
|
||||
xl={4}
|
||||
xxl={5}
|
||||
>
|
||||
{videos.length > 0 ? (
|
||||
videos.map((video, index) => {
|
||||
return (
|
||||
<Col key={index + video.guid}>
|
||||
<VideoCard
|
||||
{...{
|
||||
video,
|
||||
setVideos,
|
||||
selectVideo,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Container className="my-5 justify-content-center align-items-center">
|
||||
<p className="text-muted text-center h5">
|
||||
{__('No videos found.', 'infinite-uploads')}
|
||||
</p>
|
||||
</Container>
|
||||
)}
|
||||
</Row>
|
||||
<Paginator
|
||||
{...{page, setPage, totalItems, itemsPerPage}}
|
||||
/>
|
||||
</Container>
|
||||
) : (
|
||||
<Container className="d-flex justify-content-center align-middle my-5">
|
||||
<Spinner
|
||||
animation="grow"
|
||||
role="status"
|
||||
className="my-5"
|
||||
>
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</Spinner>
|
||||
</Container>
|
||||
)}
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
import Pagination from 'react-bootstrap/Pagination';
|
||||
import Container from 'react-bootstrap/Container';
|
||||
|
||||
function Paginator({page, setPage, totalItems, itemsPerPage}) {
|
||||
if (totalItems <= itemsPerPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let active = page;
|
||||
const lastPage = Math.ceil(totalItems / itemsPerPage);
|
||||
let items = [];
|
||||
for (let number = 1; number <= lastPage; number++) {
|
||||
items.push(
|
||||
<Pagination.Item
|
||||
key={number}
|
||||
active={number === active}
|
||||
onClick={() => setPage(number)}
|
||||
>
|
||||
{number}
|
||||
</Pagination.Item>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Pagination className="justify-content-center mt-4">
|
||||
<Pagination.First
|
||||
onClick={() => setPage(1)}
|
||||
disabled={page === 1}
|
||||
/>
|
||||
{items}
|
||||
<Pagination.Last
|
||||
onClick={() => setPage(lastPage)}
|
||||
disabled={page === lastPage}
|
||||
/>
|
||||
</Pagination>
|
||||
);
|
||||
}
|
||||
|
||||
export default Paginator;
|
@ -0,0 +1,27 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
|
||||
export default function PlayerCheckbox({control, icon, label, settings, setSettings}) {
|
||||
|
||||
return (
|
||||
<Col>
|
||||
<Form.Check
|
||||
type="checkbox"
|
||||
id={control}
|
||||
inline
|
||||
label={
|
||||
<span className="text-nowrap"><span className={"dashicons dashicons-" + icon}></span> {label}</span>
|
||||
}
|
||||
checked={settings.Controls.includes(control)}
|
||||
onChange={e => setSettings(settings => {
|
||||
if (e.target.checked) {
|
||||
return {...settings, Controls: [...settings.Controls, control]};
|
||||
} else {
|
||||
return {...settings, Controls: settings.Controls.filter(ctrl => ctrl !== control)};
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</Col>
|
||||
);
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import Row from "react-bootstrap/Row";
|
||||
import Col from "react-bootstrap/Col";
|
||||
import {Badge} from "react-bootstrap";
|
||||
|
||||
export default function ResCheckbox({px, bitrate, settings, setSettings}) {
|
||||
const resolution = px + "p";
|
||||
//calculate width if 16:9 based on height
|
||||
const width = Math.round(px * 16 / 9);
|
||||
const label = "(" + width + "x" + px + ")";
|
||||
const bitrateLabel = bitrate ? bitrate + " kbps" : "";
|
||||
const hdLabel = px >= 2160 ? "4K UHD" : px >= 1440 ? "2K QHD" : px >= 1080 ? "Full HD" : px >= 720 ? "HD" : "";
|
||||
const disabled = px >= 1440;
|
||||
|
||||
return (
|
||||
<Badge bg="light" text="dark" className="mb-2 rounded-pill px-3 w-100">
|
||||
<Row className="d-flex justify-content-between align-items-center">
|
||||
<Col className="">
|
||||
<Form.Check
|
||||
className="d-flex align-items-center"
|
||||
type="checkbox"
|
||||
id={resolution}
|
||||
label={
|
||||
<span className="text-nowrap"><strong>{resolution}</strong> {label} {hdLabel}</span>
|
||||
}
|
||||
checked={settings.EnabledResolutions.includes(resolution)}
|
||||
onChange={e => setSettings(settings => {
|
||||
if (e.target.checked) {
|
||||
return {...settings, EnabledResolutions: [...settings.EnabledResolutions, resolution]};
|
||||
} else {
|
||||
return {...settings, EnabledResolutions: settings.EnabledResolutions.filter(res => res !== resolution)};
|
||||
}
|
||||
})}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="col-auto">
|
||||
{bitrateLabel}
|
||||
</Col>
|
||||
</Row>
|
||||
</Badge>
|
||||
);
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
import {useEffect, useState} from '@wordpress/element';
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import Card from "react-bootstrap/Card";
|
||||
import Spinner from 'react-bootstrap/Spinner';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import PlayerCheckbox from "./PlayerCheckbox";
|
||||
import ResCheckbox from "./ResCheckbox";
|
||||
import ColorPick from "./ColorPick";
|
||||
import Tabs from "react-bootstrap/Tabs";
|
||||
import Tab from "react-bootstrap/Tab";
|
||||
import Button from "react-bootstrap/Button";
|
||||
|
||||
export default function Settings() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [settings, setSettings] = useState(IUP_VIDEO.settings);
|
||||
|
||||
function updateSettings() {
|
||||
setLoading(true);
|
||||
const formData = new FormData();
|
||||
formData.append('settings', JSON.stringify(settings));
|
||||
formData.append('nonce', IUP_VIDEO.nonce);
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: formData,
|
||||
};
|
||||
|
||||
fetch(`${ajaxurl}?action=infinite-uploads-video-settings`, options)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
setSettings(data.data);
|
||||
} else {
|
||||
console.error(data.data);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Error:', error);
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
if (!settings) {
|
||||
return (
|
||||
<h2>{__('Video library not yet connected.', 'infinite-uploads')}</h2>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Container fluid>
|
||||
<Row className="justify-content-between align-items-center">
|
||||
<Col>
|
||||
<h1 className="text-muted mb-3">
|
||||
<img
|
||||
src={IUP_VIDEO.assetBase + '/img/iu-logo-gray.svg'}
|
||||
alt="Infinite Uploads Logo"
|
||||
height="32"
|
||||
width="32"
|
||||
className="me-2"
|
||||
/>
|
||||
{__('Infinite Uploads Video Settings', 'infinite-uploads')}
|
||||
</h1>
|
||||
</Col>
|
||||
<Col>
|
||||
<Button variant="primary" className="float-end" href={IUP_VIDEO.libraryUrl}>
|
||||
{__('Video Library', 'infinite-uploads')}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Card>
|
||||
<Card.Body>
|
||||
<Tabs
|
||||
defaultActiveKey="player"
|
||||
id="video-settings-tabs"
|
||||
className="mb-3"
|
||||
>
|
||||
<Tab eventKey="player" title={__('Player', 'infinite-uploads')} className="mt-4">
|
||||
<Row className="justify-content-center mb-5" xs={1} md={2}>
|
||||
<Col>
|
||||
<h5>{__('Main Player Color', 'infinite-uploads')}</h5>
|
||||
<p className="lead">{__('Select the primary color that will be displayed for the controls in the video player.', 'infinite-uploads')}</p>
|
||||
</Col>
|
||||
<Col>
|
||||
<ColorPick {...{settings, setSettings}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="justify-content-center mb-5" xs={1} md={2}>
|
||||
<Col>
|
||||
<h5>{__('Player Language', 'infinite-uploads')}</h5>
|
||||
<p className="lead">{__('Select the default language that will be displayed in the video player.', 'infinite-uploads')}</p>
|
||||
</Col>
|
||||
<Col>
|
||||
<Form.Select size="lg" value={settings.UILanguage} onChange={(e) => setSettings({...settings, UILanguage: e.target.value})}>
|
||||
<option value="en" label="English"></option>
|
||||
<option value="ar" label="Arabic"></option>
|
||||
<option value="bu" label="Bulgarian"></option>
|
||||
<option value="cn" label="Chinese"></option>
|
||||
<option value="cz" label="Czech"></option>
|
||||
<option value="dk" label="Danish"></option>
|
||||
<option value="nl" label="Dutch"></option>
|
||||
<option value="fi" label="Finnish"></option>
|
||||
<option value="fr" label="French"></option>
|
||||
<option value="de" label="German"></option>
|
||||
<option value="gr" label="Greek"></option>
|
||||
<option value="hu" label="Hungarian"></option>
|
||||
<option value="id" label="Indonesian"></option>
|
||||
<option value="it" label="Italian"></option>
|
||||
<option value="jp" label="Japanese"></option>
|
||||
<option value="kr" label="Korean"></option>
|
||||
<option value="no" label="Norwegian"></option>
|
||||
<option value="pl" label="Polish"></option>
|
||||
<option value="pt" label="Portuguese"></option>
|
||||
<option value="ro" label="Romanian"></option>
|
||||
<option value="rs" label="Serbian"></option>
|
||||
<option value="sk" label="Slovakian"></option>
|
||||
<option value="si" label="Slovenian"></option>
|
||||
<option value="es" label="Spanish"></option>
|
||||
<option value="se" label="Swedish"></option>
|
||||
<option value="ru" label="Russian"></option>
|
||||
<option value="th" label="Thai"></option>
|
||||
<option value="tr" label="Turkish"></option>
|
||||
<option value="ua" label="Ukrainian"></option>
|
||||
<option value="vn" label="Vietnamese"></option>
|
||||
</Form.Select>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="justify-content-center mb-5" xs={1} md={2}>
|
||||
<Col>
|
||||
<h5>{__('Player Controls', 'infinite-uploads')}</h5>
|
||||
<p className="lead">{__('Select the UI controls that will be displayed on the player.', 'infinite-uploads')}</p>
|
||||
</Col>
|
||||
<Col className="d-flex flex-wrap justify-content-between">
|
||||
<Row>
|
||||
<PlayerCheckbox control="play" icon="controls-play" label={__('Play / Pause', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="play-large" icon="video-alt3" label={__('Center Play Button', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="volume" icon="controls-volumeon" label={__('Volume', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="mute" icon="controls-volumeoff" label={__('Mute', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="pip" icon="external" label={__('Picture-in-Picture', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="settings" icon="admin-generic" label={__('Settings', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="captions" icon="format-status" label={__('Captions', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="current-time" icon="clock" label={__('Current Time', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="duration" icon="editor-video" label={__('Duration', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="rewind" icon="controls-skipback" label={__('10s Backward', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="fast-forward" icon="controls-skipforward" label={__('10s Forward', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="progress" icon="leftright" label={__('Progress Bar', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
<PlayerCheckbox control="fullscreen" icon="fullscreen-alt" label={__('Full Screen', 'infinite-uploads')} {...{settings, setSettings}} />
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Tab>
|
||||
<Tab eventKey="encoding" title={__('Encoding', 'infinite-uploads')} className="mt-4">
|
||||
<Row className="justify-content-center mb-5" xs={1} md={2}>
|
||||
<Col>
|
||||
<h5>{__('Enabled Resolutions', 'infinite-uploads')}</h5>
|
||||
<p className="lead">{__('Select the enabled resolutions that will be encoded on upload. More resolutions provide a more efficient streaming service to users, but require more storage space. Resolutions larger than the original video will be skipped.', 'infinite-uploads')}</p>
|
||||
</Col>
|
||||
<Col>
|
||||
<ResCheckbox {...{settings, setSettings}} px={240} bitrate={600}/>
|
||||
<ResCheckbox {...{settings, setSettings}} px={360} bitrate={800}/>
|
||||
<ResCheckbox {...{settings, setSettings}} px={480} bitrate={1400}/>
|
||||
<ResCheckbox {...{settings, setSettings}} px={720} bitrate={2800}/>
|
||||
<ResCheckbox {...{settings, setSettings}} px={1080} bitrate={5000}/>
|
||||
<ResCheckbox {...{settings, setSettings}} px={1440} bitrate={8000}/>
|
||||
<ResCheckbox {...{settings, setSettings}} px={2160} bitrate={25000}/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
<Row className="justify-content-center mb-3">
|
||||
<Col className="text-center">
|
||||
<Button variant="info" className="text-nowrap text-white px-4" onClick={updateSettings} disabled={loading}>{__('Save Settings', 'infinite-uploads')}</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
|
||||
</Container>
|
||||
);
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import {useState, useEffect, useRef} from '@wordpress/element';
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Modal from 'react-bootstrap/Modal';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import Uppy from '@uppy/core';
|
||||
import Tus from '@uppy/tus';
|
||||
import {DragDrop, StatusBar, useUppy} from '@uppy/react';
|
||||
import UppyCreateVid from '../../block/edit-uppy-plugin';
|
||||
import '@uppy/core/dist/style.css';
|
||||
import '@uppy/drag-drop/dist/style.css';
|
||||
import '@uppy/status-bar/dist/style.css';
|
||||
|
||||
export default function UploadModal({getVideos}) {
|
||||
const [show, setShow] = useState(false);
|
||||
const uploadAuth = useRef(null);
|
||||
const uploaded = useRef({});
|
||||
|
||||
const handleShow = () => {
|
||||
setShow(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setShow(false);
|
||||
};
|
||||
|
||||
const uppy = useUppy(() => {
|
||||
return new Uppy({
|
||||
debug: true,
|
||||
restrictions: {
|
||||
maxNumberOfFiles: null,
|
||||
allowedFileTypes: ['video/*'],
|
||||
},
|
||||
autoProceed: true,
|
||||
allowMultipleUploadBatches: true,
|
||||
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]) {
|
||||
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
|
||||
});
|
||||
|
||||
uppy.on('error', (error) => {
|
||||
console.error(error.stack);
|
||||
});
|
||||
uppy.on('upload-error', (file, error, response) => {
|
||||
console.log('error with file:', file.id);
|
||||
console.log('error message:', error);
|
||||
});
|
||||
uppy.on('upload-success', (file, response) => {
|
||||
if (!uploaded.current[file.id]) { //make sure it only triggers once
|
||||
getVideos();
|
||||
uploaded.current = {...uploaded.current, [file.id]: true};
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant="primary"
|
||||
className="text-nowrap text-white ms-4"
|
||||
onClick={handleShow}
|
||||
>
|
||||
<span className="dashicons dashicons-video-alt3"></span>
|
||||
{__('Upload Videos', 'infinite-uploads')}
|
||||
</Button>
|
||||
|
||||
<Modal
|
||||
show={show}
|
||||
onHide={handleClose}
|
||||
size="lg"
|
||||
aria-labelledby="contained-modal-title-vcenter"
|
||||
centered
|
||||
>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title id="contained-modal-title-vcenter">
|
||||
{__('Upload Videos', 'infinite-uploads')}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Container fluid className="p-3">
|
||||
|
||||
<div className="uppy-wrapper">
|
||||
<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 videos 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={true}
|
||||
hideAfterFinish={true}
|
||||
showProgressDetails
|
||||
/>
|
||||
</div>
|
||||
</Container>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
|
||||
export function VideoSize({video}) {
|
||||
const sizeOf = function (bytes) {
|
||||
if (bytes === 0) {
|
||||
return '0 B';
|
||||
}
|
||||
var e = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return (
|
||||
(bytes / Math.pow(1024, e)).toFixed(1) +
|
||||
' ' +
|
||||
' KMGTP'.charAt(e) +
|
||||
'B'
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
className="d-inline-flex text-nowrap"
|
||||
title={__('Storage Size', 'infinite-uploads')}
|
||||
>
|
||||
<span className="dashicons dashicons-media-video me-1"></span>
|
||||
{sizeOf(video.storageSize)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function VideoLength({video}) {
|
||||
//function to turn seconds into a human readable time
|
||||
const secondsToTime = function (seconds) {
|
||||
var h = Math.floor(seconds / 3600);
|
||||
var m = Math.floor((seconds % 3600) / 60);
|
||||
var s = Math.floor((seconds % 3600) % 60);
|
||||
return (
|
||||
(h > 0 ? h + ':' : '') +
|
||||
(m > 0 ? (h > 0 && m < 10 ? '0' : '') + m + ':' : '0:') +
|
||||
(s < 10 ? '0' : '') +
|
||||
s
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<span
|
||||
className="d-inline-flex text-nowrap"
|
||||
title={__('Video Length', 'infinite-uploads')}
|
||||
>
|
||||
<span className="dashicons dashicons-clock me-1"></span>
|
||||
{secondsToTime(video.length)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function VideoViews({video}) {
|
||||
return (
|
||||
<span
|
||||
className="d-inline-flex text-nowrap"
|
||||
title={__('View Count', 'infinite-uploads')}
|
||||
>
|
||||
<span className="dashicons dashicons-welcome-view-site me-1"></span>
|
||||
{video.views}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function VideoDate({video}) {
|
||||
//change datetime to human-readable in localstring
|
||||
const dateTime = function (date) {
|
||||
return new Date(date).toLocaleString();
|
||||
};
|
||||
|
||||
return (
|
||||
<small
|
||||
className="d-inline-flex text-nowrap"
|
||||
title={__('Upload Date', 'infinite-uploads')}
|
||||
>
|
||||
<span className="dashicons dashicons-calendar me-1"></span>
|
||||
{dateTime(video.dateUploaded)}
|
||||
</small>
|
||||
);
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import Card from 'react-bootstrap/Card';
|
||||
import {useState} from '@wordpress/element';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import {VideoLength, VideoViews, VideoSize} from './VideoAttributes';
|
||||
import VideoModal from './VideoModal';
|
||||
import ProgressBar from 'react-bootstrap/ProgressBar';
|
||||
import DeleteModal from './DeleteModal';
|
||||
|
||||
function VideoCard({video, videos, setVideos, selectVideo}) {
|
||||
const getThumbnail = (file) => {
|
||||
return IUP_VIDEO.cdnUrl + '/' + video.guid + '/' + file;
|
||||
};
|
||||
|
||||
const [src, setSrc] = useState(getThumbnail(video.thumbnailFileName));
|
||||
|
||||
const statusLabels = {
|
||||
0: __('Awaiting Upload', 'infinite-uploads'),
|
||||
1: __('Uploaded', 'infinite-uploads'),
|
||||
2: __('Processing', 'infinite-uploads'),
|
||||
3: __('Transcoding', 'infinite-uploads'),
|
||||
4: __('Finished', 'infinite-uploads'),
|
||||
5: __('Error', 'infinite-uploads'),
|
||||
6: __('Upload Failed', 'infinite-uploads'),
|
||||
};
|
||||
const status = statusLabels[video.status];
|
||||
|
||||
if ([0, 1, 5, 6].includes(video.status)) {
|
||||
return (
|
||||
<span className="m-3 w-100 p-0">
|
||||
<Card className="m-0 shadow-sm">
|
||||
<div className="ratio ratio-16x9 overflow-hidden bg-black text-white rounded-top">
|
||||
<div>
|
||||
<div className="d-flex justify-content-center align-items-center h-100 text-secondary font-weight-bold">
|
||||
{status}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Card.Body className={'p-2'}>
|
||||
<Card.Title className="h6 card-title text-truncate">
|
||||
{video.title}
|
||||
</Card.Title>
|
||||
<Row className="justify-content-end text-muted align-items-center">
|
||||
<Col className="justify-content-end d-flex">
|
||||
{!selectVideo && (
|
||||
<DeleteModal
|
||||
video={video}
|
||||
setVideos={setVideos}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</span>
|
||||
);
|
||||
} else if ([2].includes(video.status)) {
|
||||
//processing
|
||||
return (
|
||||
<span className="m-3 w-100 p-0">
|
||||
<Card className="m-0 shadow-sm">
|
||||
<div className="ratio ratio-16x9 overflow-hidden bg-black text-white rounded-top">
|
||||
<div>
|
||||
<div className="d-flex justify-content-center align-items-center h-100 text-secondary font-weight-bold">
|
||||
{status}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Card.Body className={'p-2'}>
|
||||
<Card.Title className="h6 card-title text-truncate">
|
||||
{video.title}
|
||||
</Card.Title>
|
||||
<small className="row justify-content-between text-muted align-items-center">
|
||||
<Col className="col-auto">
|
||||
{__('Processing', 'infinite-uploads')}:
|
||||
</Col>
|
||||
<Col>
|
||||
<ProgressBar
|
||||
animated
|
||||
now={video.encodeProgress}
|
||||
label={`${video.encodeProgress}%`}
|
||||
className="w-100"
|
||||
/>
|
||||
</Col>
|
||||
</small>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<VideoModal {...{video, setVideos, selectVideo}}>
|
||||
<Card className="m-0 shadow-sm">
|
||||
<div className="ratio ratio-16x9 overflow-hidden bg-black rounded-top">
|
||||
<div
|
||||
className="iup-video-thumb"
|
||||
style={{backgroundImage: `url("${src}")`}}
|
||||
onMouseOver={() =>
|
||||
setSrc(getThumbnail('preview.webp'))
|
||||
}
|
||||
onMouseOut={() =>
|
||||
setSrc(
|
||||
getThumbnail(video.thumbnailFileName)
|
||||
)
|
||||
}
|
||||
></div>
|
||||
</div>
|
||||
<Card.Body className={'p-2'}>
|
||||
<Card.Title className="h6 card-title text-truncate">
|
||||
{video.title}
|
||||
</Card.Title>
|
||||
{video.status === 3 ? (
|
||||
<small className="row justify-content-between text-muted align-items-center">
|
||||
<Col className="col-auto">
|
||||
{__('Transcoding', 'infinite-uploads')}:
|
||||
</Col>
|
||||
<Col>
|
||||
<ProgressBar
|
||||
animated
|
||||
now={video.encodeProgress}
|
||||
label={`${video.encodeProgress}%`}
|
||||
className="w-100"
|
||||
/>
|
||||
</Col>
|
||||
</small>
|
||||
) : (
|
||||
<small className="row justify-content-between text-muted align-items-center">
|
||||
<Col>
|
||||
<VideoLength video={video}/>
|
||||
</Col>
|
||||
<Col></Col>
|
||||
<Col>
|
||||
<VideoSize video={video}/>
|
||||
</Col>
|
||||
</small>
|
||||
)}
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</VideoModal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default VideoCard;
|
@ -0,0 +1,604 @@
|
||||
import {__, _x, _n, _nx} from '@wordpress/i18n';
|
||||
import Card from 'react-bootstrap/Card';
|
||||
import {useState, useEffect} from '@wordpress/element';
|
||||
import Row from 'react-bootstrap/Row';
|
||||
import Col from 'react-bootstrap/Col';
|
||||
import Container from 'react-bootstrap/Container';
|
||||
import Modal from 'react-bootstrap/Modal';
|
||||
import {
|
||||
VideoLength,
|
||||
VideoSize,
|
||||
VideoViews,
|
||||
VideoDate,
|
||||
} from './VideoAttributes';
|
||||
import Form from 'react-bootstrap/Form';
|
||||
import InputGroup from 'react-bootstrap/InputGroup';
|
||||
import Button from 'react-bootstrap/Button';
|
||||
import Tab from 'react-bootstrap/Tab';
|
||||
import Tabs from 'react-bootstrap/Tabs';
|
||||
import DeleteModal from './DeleteModal';
|
||||
import Spinner from 'react-bootstrap/Spinner';
|
||||
|
||||
export default function VideoModal({
|
||||
video,
|
||||
setVideos,
|
||||
selectVideo,
|
||||
children,
|
||||
}) {
|
||||
const [show, setShow] = useState(false);
|
||||
const [title, setTitle] = useState(video.title);
|
||||
const [autoPlay, setAutoPlay] = useState(false);
|
||||
const [loop, setLoop] = useState(false);
|
||||
const [muted, setMuted] = useState(false);
|
||||
const [preload, setPreload] = useState(true);
|
||||
const [embedParams, setEmbedParams] = useState('');
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [iframe, setIframe] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
let params = [];
|
||||
if (autoPlay) {
|
||||
params.push('autoplay="true"');
|
||||
}
|
||||
if (loop) {
|
||||
params.push('loop="true"');
|
||||
}
|
||||
if (muted) {
|
||||
params.push('muted="true"');
|
||||
}
|
||||
if (preload) {
|
||||
params.push('preload="true"');
|
||||
}
|
||||
setEmbedParams(params.join(' '));
|
||||
}, [autoPlay, loop, muted, preload]);
|
||||
|
||||
useEffect(() => {
|
||||
const component = (
|
||||
<iframe
|
||||
src={`https://iframe.mediadelivery.net/embed/${
|
||||
video.videoLibraryId
|
||||
}/${video.guid}?autoplay=false&v=${Math.random()}`}
|
||||
loading="lazy"
|
||||
allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"
|
||||
allowFullScreen={true}
|
||||
></iframe>
|
||||
);
|
||||
setIframe(component);
|
||||
}, [video]);
|
||||
|
||||
const getThumbnail = (file) => {
|
||||
return IUP_VIDEO.cdnUrl + '/' + video.guid + '/' + file;
|
||||
};
|
||||
|
||||
const handleShow = () => {
|
||||
setShow(true);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setShow(false);
|
||||
};
|
||||
|
||||
function updateVideo() {
|
||||
setLoading(true);
|
||||
const formData = new FormData();
|
||||
formData.append('title', title);
|
||||
formData.append('video_id', video.guid);
|
||||
formData.append('nonce', IUP_VIDEO.nonce);
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: formData,
|
||||
};
|
||||
|
||||
fetch(`${ajaxurl}?action=infinite-uploads-video-update`, options)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
setVideos((videos) =>
|
||||
videos.map((v) =>
|
||||
v.guid === video.guid ? {...v, title} : v
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.error(data.data);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Error:', error);
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
function setThumbnail(thumbnailFileName) {
|
||||
setLoading(true);
|
||||
const formData = new FormData();
|
||||
formData.append('thumbnail', thumbnailFileName);
|
||||
formData.append('video_id', video.guid);
|
||||
formData.append('nonce', IUP_VIDEO.nonce);
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: formData,
|
||||
};
|
||||
|
||||
fetch(`${ajaxurl}?action=infinite-uploads-video-update`, options)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
setVideos((videos) =>
|
||||
videos.map((v) =>
|
||||
v.guid === video.guid
|
||||
? {...v, thumbnailFileName}
|
||||
: v
|
||||
)
|
||||
);
|
||||
} else {
|
||||
console.error(data.data);
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Error:', error);
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
function uploadThumbnail(file) {
|
||||
setUploading(true);
|
||||
const formData = new FormData();
|
||||
formData.append('thumbnailFile', file);
|
||||
formData.append('video_id', video.guid);
|
||||
formData.append('nonce', IUP_VIDEO.nonce);
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: formData,
|
||||
};
|
||||
|
||||
fetch(`${ajaxurl}?action=infinite-uploads-video-update`, options)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.success) {
|
||||
getVideo(); // refresh video data
|
||||
setUploading(false);
|
||||
} else {
|
||||
console.error(data.data);
|
||||
setUploading(false);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Error:', error);
|
||||
setUploading(false);
|
||||
});
|
||||
}
|
||||
|
||||
function getVideo() {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
AccessKey: IUP_VIDEO.apiKey,
|
||||
},
|
||||
};
|
||||
|
||||
fetch(
|
||||
`https://video.bunnycdn.com/library/${IUP_VIDEO.libraryId}/videos/${video.guid}`,
|
||||
options
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
//replace video in videos array
|
||||
setVideos((videos) =>
|
||||
videos.map((v) =>
|
||||
v.guid === video.guid ? {...v, ...data} : v
|
||||
)
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
let thumbnails = [];
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
thumbnails.push(
|
||||
<Col key={i} className="mb-2">
|
||||
<Card
|
||||
className="bg-dark text-white h-100 p-0"
|
||||
role="button"
|
||||
disabled={loading || uploading}
|
||||
onClick={() => setThumbnail('thumbnail_' + i + '.jpg')}
|
||||
>
|
||||
<div className="ratio ratio-16x9 overflow-hidden bg-black rounded">
|
||||
<div
|
||||
className="iup-video-thumb rounded border-0"
|
||||
style={{
|
||||
backgroundImage: `url("${getThumbnail(
|
||||
'thumbnail_' + i + '.jpg'
|
||||
)}")`,
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<div className="card-img-overlay rounded border-0">
|
||||
<div className="card-title align-middle text-center text-white">
|
||||
{__('Set', 'infinite-uploads')}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
}
|
||||
thumbnails.push(
|
||||
<Col key="fileupload" className="mb-2">
|
||||
<Card
|
||||
className="h-100 p-0 border-4 border-secondary"
|
||||
style={{borderStyle: 'dashed'}}
|
||||
disabled={loading || uploading}
|
||||
role="button"
|
||||
onClick={() =>
|
||||
document.getElementById('upload-thumbnail').click()
|
||||
}
|
||||
>
|
||||
<div className="ratio ratio-16x9 overflow-hidden bg-light border-0 rounded">
|
||||
<div>
|
||||
{uploading ? (
|
||||
<div className="h-100 w-100 d-flex align-items-center justify-content-center">
|
||||
<Spinner
|
||||
animation="border"
|
||||
role="status"
|
||||
className="text-muted"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<span className="dashicons dashicons-upload h-100 w-100 d-flex align-items-center justify-content-center text-muted h3"></span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Form.Control
|
||||
type="file"
|
||||
id="upload-thumbnail"
|
||||
className="d-none"
|
||||
accept="image/png, image/jpeg"
|
||||
disabled={loading || uploading}
|
||||
onChange={() =>
|
||||
uploadThumbnail(
|
||||
document.getElementById('upload-thumbnail')
|
||||
.files[0]
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<a
|
||||
className="m-3 w-100 p-0 text-decoration-none"
|
||||
role="button"
|
||||
aria-label={__('Open video modal', 'infinite-uploads')}
|
||||
onClick={() => {
|
||||
if (selectVideo) {
|
||||
selectVideo(video);
|
||||
} else {
|
||||
handleShow();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
|
||||
<Modal
|
||||
show={show}
|
||||
onHide={handleClose}
|
||||
size="xl"
|
||||
aria-labelledby="contained-modal-title-vcenter"
|
||||
centered
|
||||
>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title id="contained-modal-title-vcenter">
|
||||
{__('Edit Video:', 'infinite-uploads')}{' '}
|
||||
{video.title}
|
||||
</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Container fluid className="pb-3">
|
||||
<Row
|
||||
className="justify-content-center mb-4 mt-3"
|
||||
xs={1}
|
||||
lg={2}
|
||||
>
|
||||
<Col>
|
||||
<Row className="mb-2">
|
||||
<Col>
|
||||
<div className="ratio ratio-16x9">
|
||||
{iframe}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="justify-content-between text-muted text-center">
|
||||
<Col>
|
||||
<VideoDate video={video}/>
|
||||
</Col>
|
||||
<Col>
|
||||
<VideoLength video={video}/>
|
||||
</Col>
|
||||
<Col>
|
||||
<VideoViews video={video}/>
|
||||
</Col>
|
||||
<Col>
|
||||
<VideoSize video={video}/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col>
|
||||
<Row className="mb-4">
|
||||
<Col>
|
||||
<label htmlFor="video-title">
|
||||
{__(
|
||||
'Video Title',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</label>
|
||||
<InputGroup>
|
||||
<Form.Control
|
||||
id="video-title"
|
||||
placeholder={__(
|
||||
'Title',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
aria-label={__(
|
||||
'Title',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
value={title}
|
||||
onChange={(e) =>
|
||||
setTitle(e.target.value)
|
||||
}
|
||||
disabled={
|
||||
loading || uploading
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
className="text-white"
|
||||
disabled={
|
||||
loading || uploading
|
||||
}
|
||||
onClick={updateVideo}
|
||||
>
|
||||
{__(
|
||||
'Update',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</Button>
|
||||
</InputGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="mb-4">
|
||||
<Col className="col-4">
|
||||
<h6>
|
||||
{__(
|
||||
'Current Thumbnail',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</h6>
|
||||
<Card className="bg-dark text-white w-100 p-0 mb-2">
|
||||
<div className="ratio ratio-16x9 overflow-hidden bg-black rounded border-0">
|
||||
<div
|
||||
className="iup-video-thumb rounded border-0"
|
||||
style={{
|
||||
backgroundImage: `url("${getThumbnail(
|
||||
video.thumbnailFileName
|
||||
)}")`,
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col className="col-8">
|
||||
<p>
|
||||
{__(
|
||||
'Choose a new thumbnail to be displayed in the video player:',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</p>
|
||||
<Row className="justify-content-start d-flex row-cols-2 row-cols-md-3">
|
||||
{thumbnails}
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row className="justify-content-end mb-3">
|
||||
<Col className="justify-content-end d-flex">
|
||||
<DeleteModal
|
||||
video={video}
|
||||
setVideos={setVideos}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Tabs defaultActiveKey="shortcode" className="mb-4">
|
||||
<Tab
|
||||
eventKey="shortcode"
|
||||
title={
|
||||
<div className="d-inline-flex align-start">
|
||||
<span className="dashicons dashicons-shortcode me-1"></span>
|
||||
{__(
|
||||
'Embed Code',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Row className="justify-content-center mt-2">
|
||||
<Col>
|
||||
<Row>
|
||||
<Col>
|
||||
<p>
|
||||
{__(
|
||||
'Copy and paste this code into your post, page, or widget to embed the video. If using Gutenberg editor use our block.',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row className="mb-1">
|
||||
<Col>
|
||||
<Form>
|
||||
<Form.Check
|
||||
inline
|
||||
label={__(
|
||||
'Autoplay',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
type="checkbox"
|
||||
checked={autoPlay}
|
||||
onChange={(e) =>
|
||||
setAutoPlay(
|
||||
e.target.checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Form.Check
|
||||
inline
|
||||
label={__(
|
||||
'Loop',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
type="checkbox"
|
||||
checked={loop}
|
||||
onChange={(e) =>
|
||||
setLoop(
|
||||
e.target.checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Form.Check
|
||||
inline
|
||||
label={__(
|
||||
'Muted',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
type="checkbox"
|
||||
checked={muted}
|
||||
onChange={(e) =>
|
||||
setMuted(
|
||||
e.target.checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Form.Check
|
||||
inline
|
||||
label={__(
|
||||
'Preload',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
type="checkbox"
|
||||
checked={preload}
|
||||
onChange={(e) =>
|
||||
setPreload(
|
||||
e.target.checked
|
||||
)
|
||||
}
|
||||
style={{ display: 'none' }} // Hides the preload checkbox
|
||||
/>
|
||||
</Form>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
<Form.Control
|
||||
type="text"
|
||||
aria-label="Embed Code"
|
||||
readOnly
|
||||
value={`[infinite-uploads-vid id="${video.guid}" ${embedParams}]`}
|
||||
onClick={(e) => {
|
||||
e.target.select();
|
||||
document.execCommand(
|
||||
'copy'
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
eventKey="stats"
|
||||
disabled
|
||||
title={
|
||||
<>
|
||||
<span className="dashicons dashicons-chart-area me-1"></span>
|
||||
{__('Stats', 'infinite-uploads')}
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Row className="justify-content-center">
|
||||
<Col>
|
||||
<Row>
|
||||
<Col>
|
||||
<h5>
|
||||
{__(
|
||||
'Statistics',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</h5>
|
||||
<p>
|
||||
{__(
|
||||
'View the statistics for this video.',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>Chart here</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
eventKey="captions"
|
||||
disabled
|
||||
title={
|
||||
<>
|
||||
<span className="dashicons dashicons-format-status me-1"></span>
|
||||
{__('Captions', 'infinite-uploads')}
|
||||
</>
|
||||
}
|
||||
></Tab>
|
||||
|
||||
<Tab
|
||||
eventKey="chapters"
|
||||
disabled
|
||||
title={
|
||||
<>
|
||||
<span className="dashicons dashicons-text me-1"></span>
|
||||
{__('Chapters', 'infinite-uploads')}
|
||||
</>
|
||||
}
|
||||
></Tab>
|
||||
</Tabs>
|
||||
</Container>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
29
wp-content/plugins/infinite-uploads/inc/video/admin/index.js
Normal file
29
wp-content/plugins/infinite-uploads/inc/video/admin/index.js
Normal file
@ -0,0 +1,29 @@
|
||||
import {render} from '@wordpress/element';
|
||||
import {Component} from '@wordpress/element';
|
||||
import Library from './components/Library';
|
||||
import Settings from "./components/Settings";
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import '../../assets/css/admin.css';
|
||||
|
||||
class InfiniteUploadsLibrary extends Component {
|
||||
render() {
|
||||
return <Library/>;
|
||||
}
|
||||
}
|
||||
|
||||
class InfiniteUploadsSettings extends Component {
|
||||
render() {
|
||||
return <Settings/>;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function (event) {
|
||||
const library = document.getElementById("iup-videos-page")
|
||||
if (library) {
|
||||
render(<InfiniteUploadsLibrary/>, library);
|
||||
}
|
||||
const settings = document.getElementById("iup-video-settings-page")
|
||||
if (settings) {
|
||||
render(<InfiniteUploadsSettings/>, settings);
|
||||
}
|
||||
});
|
@ -0,0 +1,44 @@
|
||||
{
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 2,
|
||||
"name": "infinite-uploads/video",
|
||||
"version": "0.1.0",
|
||||
"title": "Infinite Uploads Video",
|
||||
"category": "media",
|
||||
"icon": "format-video",
|
||||
"description": "Upload & Embed a video via Infinite Uploads Video Cloud.",
|
||||
"attributes": {
|
||||
"video_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"autoplay": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"loop": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"muted": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"preload": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"supports": {
|
||||
"html": false,
|
||||
"anchor": true,
|
||||
"align": true,
|
||||
"spacing": {
|
||||
"margin": true,
|
||||
"padding": true
|
||||
}
|
||||
},
|
||||
"textdomain": "infinite-uploads",
|
||||
"editorScript": "file:../../../build/block.js",
|
||||
"editorStyle": "file:../../../build/block.css",
|
||||
"style": "file:../../../build/style-block.css"
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,40 @@
|
||||
import {__, _x} from '@wordpress/i18n';
|
||||
import {Button, Modal} from '@wordpress/components';
|
||||
import {useState, useEffect} from '@wordpress/element';
|
||||
import Library from '../../../admin/components/Library';
|
||||
import {InfiniteUploadsIcon} from '../Images';
|
||||
import './styles.scss';
|
||||
|
||||
export default function LibraryModal({selectVideo, ...props}) {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const openModal = () => setOpen(true);
|
||||
const closeModal = () => setOpen(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button variant="primary" onClick={openModal}>
|
||||
{__('Select from Library', 'infinite-uploads')}
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<Modal
|
||||
{...props}
|
||||
isDismissible={true}
|
||||
onRequestClose={closeModal}
|
||||
icon={InfiniteUploadsIcon(false)}
|
||||
style={{width: '98%'}}
|
||||
title={__('Cloud Video Library', 'infinite-uploads')}
|
||||
className="iup-block-library-model"
|
||||
>
|
||||
<p>
|
||||
{__(
|
||||
'Select a video from your library to insert into the editor.',
|
||||
'infinite-uploads'
|
||||
)}
|
||||
</p>
|
||||
|
||||
<Library selectVideo={selectVideo}/>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
//scope bootstrap styles to library modal only
|
||||
.iup-block-library-model {
|
||||
@import '~bootstrap/scss/bootstrap';
|
||||
|
||||
.components-modal__header-heading {
|
||||
margin-bottom: 0;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.bg-black {
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: #6c757d !important;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.rounded-top {
|
||||
border-top-left-radius: 0.375rem !important;
|
||||
border-top-right-radius: 0.375rem !important;
|
||||
}
|
||||
|
||||
.btn .dashicons {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.rounded-pill {
|
||||
border-radius: 50rem !important;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* WordPress dependencies
|
||||
*/
|
||||
import {__, _x} from '@wordpress/i18n';
|
||||
import {ToggleControl, SelectControl} from '@wordpress/components';
|
||||
import {useMemo, useCallback, Platform} from '@wordpress/element';
|
||||
|
||||
const VideoSettings = ({setAttributes, attributes}) => {
|
||||
const {autoplay, loop, muted, preload} = attributes;
|
||||
|
||||
const autoPlayHelpText = __(
|
||||
'Autoplay may cause usability issues for some users.'
|
||||
);
|
||||
const getAutoplayHelp = Platform.select({
|
||||
web: useCallback((checked) => {
|
||||
return checked ? autoPlayHelpText : null;
|
||||
}, []),
|
||||
native: autoPlayHelpText,
|
||||
});
|
||||
|
||||
const toggleFactory = useMemo(() => {
|
||||
const toggleAttribute = (attribute) => {
|
||||
return (newValue) => {
|
||||
setAttributes({[attribute]: newValue});
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
autoplay: toggleAttribute('autoplay'),
|
||||
loop: toggleAttribute('loop'),
|
||||
muted: toggleAttribute('muted'),
|
||||
preload: toggleAttribute('preload'),
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToggleControl
|
||||
label={__('Autoplay')}
|
||||
onChange={toggleFactory.autoplay}
|
||||
checked={autoplay}
|
||||
help={getAutoplayHelp}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={__('Loop')}
|
||||
onChange={toggleFactory.loop}
|
||||
checked={loop}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={__('Muted')}
|
||||
onChange={toggleFactory.muted}
|
||||
checked={muted}
|
||||
/>
|
||||
<ToggleControl
|
||||
label={__('Preload')}
|
||||
onChange={toggleFactory.preload}
|
||||
checked={preload}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default VideoSettings;
|
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Because of the way Uppy is designed, we can't use onBeforeUpload to set the video_id as it does not support async.
|
||||
* So this custom plugin is used to set the video_id before the upload starts.
|
||||
*
|
||||
* @see https://uppy.io/docs/writing-plugins/#Example-of-a-custom-plugin
|
||||
*/
|
||||
import {UIPlugin} from '@uppy/core';
|
||||
|
||||
class UppyCreateVid extends UIPlugin {
|
||||
constructor(uppy, opts) {
|
||||
super(uppy, {...{}, ...opts});
|
||||
|
||||
this.id = this.opts.id || 'CreateVid';
|
||||
this.type = 'modifier';
|
||||
}
|
||||
|
||||
createVideo(title) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const formData = new FormData();
|
||||
formData.append('title', title);
|
||||
formData.append('nonce', IUP_VIDEO.nonce);
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: formData,
|
||||
};
|
||||
|
||||
fetch(
|
||||
`${ajaxurl}?action=infinite-uploads-video-create`,
|
||||
options
|
||||
)
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
if (data.success) {
|
||||
resolve(data.data);
|
||||
} else {
|
||||
reject(data.data);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log('Error:', error);
|
||||
return reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
prepareUpload = (fileIDs) => {
|
||||
const promises = fileIDs.map((fileID) => {
|
||||
const file = this.uppy.getFile(fileID);
|
||||
//get the title from the file name and remove the extension
|
||||
const title = file.name.replace(/\.[^/.]+$/, '');
|
||||
|
||||
return this.createVideo(title)
|
||||
.then((upload) => {
|
||||
console.log(`Video ${upload.VideoId} created`);
|
||||
this.opts.uploadAuth.current = {...this.opts.uploadAuth.current, [fileID]: upload};
|
||||
})
|
||||
.catch((err) => {
|
||||
this.uppy.log(
|
||||
`Video could not be created ${file.id}:`,
|
||||
'warning'
|
||||
);
|
||||
this.uppy.log(err, 'warning');
|
||||
});
|
||||
});
|
||||
|
||||
const emitPreprocessCompleteForAll = () => {
|
||||
fileIDs.forEach((fileID) => {
|
||||
const file = this.uppy.getFile(fileID);
|
||||
this.uppy.emit('preprocess-complete', file);
|
||||
});
|
||||
};
|
||||
|
||||
// Why emit `preprocess-complete` for all files at once, instead of
|
||||
// above when each is processed?
|
||||
// Because it leads to StatusBar showing a weird “upload 6 files” button,
|
||||
// while waiting for all the files to complete pre-processing.
|
||||
return Promise.all(promises).then(emitPreprocessCompleteForAll);
|
||||
};
|
||||
|
||||
install() {
|
||||
this.uppy.addPreProcessor(this.prepareUpload);
|
||||
}
|
||||
|
||||
uninstall() {
|
||||
this.uppy.removePreProcessor(this.prepareUpload);
|
||||
}
|
||||
}
|
||||
|
||||
export default UppyCreateVid;
|
340
wp-content/plugins/infinite-uploads/inc/video/block/edit.js
Normal file
340
wp-content/plugins/infinite-uploads/inc/video/block/edit.js
Normal file
@ -0,0 +1,340 @@
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
}
|
||||
}
|
102
wp-content/plugins/infinite-uploads/inc/video/block/editor.scss
Normal file
102
wp-content/plugins/infinite-uploads/inc/video/block/editor.scss
Normal file
@ -0,0 +1,102 @@
|
||||
/**
|
||||
* The following styles get applied inside the editor only.
|
||||
*
|
||||
* Replace them with your own styles or remove the file completely.
|
||||
*/
|
||||
.wp-block-infinite-uploads-video {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
color: #1e1e1e;
|
||||
-moz-font-smoothing: subpixel-antialiased;
|
||||
-webkit-font-smoothing: subpixel-antialiased;
|
||||
border-radius: 2px;
|
||||
background-color: #fff;
|
||||
box-shadow: inset 0 0 0 1px #1e1e1e;
|
||||
outline: 1px solid transparent;
|
||||
|
||||
@import '../../../node_modules/@uppy/core/dist/style';
|
||||
@import '../../../node_modules/@uppy/drag-drop/dist/style';
|
||||
@import '../../../node_modules/@uppy/status-bar/dist/style';
|
||||
|
||||
.placeholder-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.uppy-wrapper {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.uppy-StatusBar-progress {
|
||||
height: 1em;
|
||||
background-color: var(--wp-admin-theme-color);
|
||||
}
|
||||
|
||||
.uppy-StatusBar::before {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
.uppy-StatusBar-content {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.uppy-StatusBar-actions {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.uppy-DragDrop-inner {
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.uppy-DragDrop-arrow {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.ratio-16-9-outer {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
display: block;
|
||||
padding-top: calc(9 / 16) * 100%;
|
||||
}
|
||||
|
||||
> .ratio-16-9-inner {
|
||||
background-size: cover;
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-color: #000000;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ratio-16-9-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 1.5em;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.iup-video-overlay {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
50
wp-content/plugins/infinite-uploads/inc/video/block/index.js
Normal file
50
wp-content/plugins/infinite-uploads/inc/video/block/index.js
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Registers a new block provided a unique name and an object defining its behavior.
|
||||
*
|
||||
* @see https://developer.wordpress.org/block-editor/developers/block-api/#registering-a-block
|
||||
*/
|
||||
import {registerBlockType} from '@wordpress/blocks';
|
||||
|
||||
/**
|
||||
* Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
|
||||
* All files containing `style` keyword are bundled together. The code used
|
||||
* gets applied both to the front of your site and to the editor. All other files
|
||||
* get applied to the editor only.
|
||||
*
|
||||
* @see https://www.npmjs.com/package/@wordpress/scripts#using-css
|
||||
*/
|
||||
import './style.scss';
|
||||
import './editor.scss';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import Edit from './edit';
|
||||
import save from './save';
|
||||
import metadata from './block.json';
|
||||
import {InfiniteUploadsIcon} from './components/Images';
|
||||
|
||||
/**
|
||||
* Every block starts by registering a new block type definition.
|
||||
*
|
||||
* @see https://developer.wordpress.org/block-editor/developers/block-api/#registering-a-block
|
||||
*/
|
||||
registerBlockType(metadata.name, {
|
||||
/**
|
||||
* Used to construct a preview for the block to be shown in the block inserter.
|
||||
*/
|
||||
example: {
|
||||
attributes: {
|
||||
video_id: '',
|
||||
},
|
||||
},
|
||||
/**
|
||||
* @see ./edit.js
|
||||
*/
|
||||
edit: Edit,
|
||||
/**
|
||||
* @see ./save.js
|
||||
*/
|
||||
save,
|
||||
icon: InfiniteUploadsIcon(false),
|
||||
});
|
40
wp-content/plugins/infinite-uploads/inc/video/block/save.js
Normal file
40
wp-content/plugins/infinite-uploads/inc/video/block/save.js
Normal file
@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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 {useBlockProps} from '@wordpress/block-editor';
|
||||
|
||||
/**
|
||||
* The save function defines the way in which the different attributes should
|
||||
* be combined into the final markup, which is then serialized by the block
|
||||
* editor into `post_content`.
|
||||
*
|
||||
* @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#save
|
||||
*
|
||||
* @param {Object} props Properties passed to the function.
|
||||
* @param {Object} props.attributes Available block attributes.
|
||||
* @return {WPElement} Element to render.
|
||||
*/
|
||||
export default function save({attributes}) {
|
||||
const blockProps = useBlockProps.save();
|
||||
if (attributes.video_id) {
|
||||
return (
|
||||
<figure {...blockProps}>
|
||||
<div 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>
|
||||
</div>
|
||||
</figure>
|
||||
);
|
||||
} else {
|
||||
return <div {...blockProps}></div>;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/**
|
||||
* The following styles get applied both on the front of your site
|
||||
* and in the editor.
|
||||
*
|
||||
* Replace them with your own styles or remove the file completely.
|
||||
*/
|
||||
.wp-block-infinite-uploads-video {
|
||||
.iup-video-embed-wrapper {
|
||||
position: relative;
|
||||
padding-top: 56.25%;
|
||||
min-width: var(--wp--style--global--content-size);
|
||||
}
|
||||
|
||||
.iup-video-embed {
|
||||
border: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user