From da7f365e0c1faaf6714efdd7c5ee369dafea1377 Mon Sep 17 00:00:00 2001 From: Christian Gastrell Date: Fri, 9 Aug 2024 13:58:36 -0300 Subject: [PATCH] temp --- pnpm-lock.yaml | 53 +++++- projects/packages/videopress/package.json | 2 +- .../packages/videopress/src/class-xmlrpc.php | 2 +- .../admin/components/admin-page/index.tsx | 20 ++- .../admin/components/admin-page/libraries.tsx | 6 +- .../admin/components/video-card/error.tsx | 161 ++++++++++++++++++ .../admin/components/video-grid/index.tsx | 33 +++- .../components/video-thumbnail/index.tsx | 12 +- .../video-thumbnail/style.module.scss | 4 + .../admin/components/video-thumbnail/types.ts | 5 + .../client/admin/hooks/use-videos/index.js | 2 + .../src/client/admin/types/index.ts | 6 + .../lib/resumable-file-uploader/index.ts | 26 ++- .../videopress/src/client/state/actions.js | 22 ++- .../videopress/src/client/state/constants.js | 1 + .../videopress/src/client/state/reducers.js | 22 +++ .../videopress/src/client/state/selectors.js | 8 + 17 files changed, 351 insertions(+), 34 deletions(-) create mode 100644 projects/packages/videopress/src/client/admin/components/video-card/error.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 178cf5324f225..d95ce8fc082c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2876,8 +2876,8 @@ importers: specifier: ^5.3.4 version: 5.3.4(react@18.3.1) tus-js-client: - specifier: 2.3.0 - version: 2.3.0 + specifier: 4.1.0 + version: 4.1.0 devDependencies: '@automattic/calypso-color-schemes': specifier: 3.1.3 @@ -11202,6 +11202,9 @@ packages: js-base64@2.6.4: resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==} + js-base64@3.7.7: + resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -12650,6 +12653,11 @@ packages: engines: {node: '>=14'} hasBin: true + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + pretty-error@4.0.0: resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==} @@ -12698,6 +12706,9 @@ packages: resolution: {integrity: sha512-rjaeGbsmhNDcDInmwi4MuI6mRwJu6zq8GjYCLuSuE7GF+4UjgzkL69sVKKJ2T2xH61kK7rXvGYpvaTu909oXaQ==} engines: {node: '>=4.0.0'} + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + properties@1.2.1: resolution: {integrity: sha512-qYNxyMj1JeW54i/EWEFsM1cVwxJbtgPp8+0Wg9XjNaK6VE/c4oRi6PNu5p7w1mNXEIQIjV5Wwn8v8Gz82/QzdQ==} engines: {node: '>=0.10'} @@ -13213,6 +13224,10 @@ packages: retry@0.10.1: resolution: {integrity: sha512-ZXUSQYTHdl3uS7IuCehYfMzKyIDBNoAuUblvy5oGO5UJSUTmStUUVPXbA9Qxd173Bgre53yCQczQuHgRWAdvJQ==} + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -14070,6 +14085,10 @@ packages: tus-js-client@2.3.0: resolution: {integrity: sha512-I4cSwm6N5qxqCmBqenvutwSHe9ntf81lLrtf6BmLpG2v4wTl89atCQKqGgqvkodE6Lx+iKIjMbaXmfvStTg01g==} + tus-js-client@4.1.0: + resolution: {integrity: sha512-e/nC/kJahvNYBcnwcqzuhFIvVELMMpbVXIoOOKdUn74SdQCvJd2JjqV2jZLv2EFOVbV4qLiO0lV7BxBXF21b6Q==} + engines: {node: '>=18'} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -17680,7 +17699,7 @@ snapshots: node-fetch: 2.6.7 picomatch: 2.3.1 pkg-dir: 5.0.0 - prettier-fallback: prettier@3.3.2 + prettier-fallback: prettier@3.3.3 pretty-hrtime: 1.0.3 resolve-from: 5.0.0 semver: 7.5.2 @@ -17715,7 +17734,7 @@ snapshots: node-fetch: 2.6.7 picomatch: 2.3.1 pkg-dir: 5.0.0 - prettier-fallback: prettier@3.3.2 + prettier-fallback: prettier@3.3.3 pretty-hrtime: 1.0.3 resolve-from: 5.0.0 semver: 7.5.2 @@ -17750,7 +17769,7 @@ snapshots: node-fetch: 2.6.7 picomatch: 2.3.1 pkg-dir: 5.0.0 - prettier-fallback: prettier@3.3.2 + prettier-fallback: prettier@3.3.3 pretty-hrtime: 1.0.3 resolve-from: 5.0.0 semver: 7.5.2 @@ -17787,7 +17806,7 @@ snapshots: node-fetch: 2.6.7 picomatch: 2.3.1 pkg-dir: 5.0.0 - prettier-fallback: prettier@3.3.2 + prettier-fallback: prettier@3.3.3 pretty-hrtime: 1.0.3 resolve-from: 5.0.0 semver: 7.5.2 @@ -24241,6 +24260,8 @@ snapshots: js-base64@2.6.4: {} + js-base64@3.7.7: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -25857,6 +25878,8 @@ snapshots: prettier@3.3.2: {} + prettier@3.3.3: {} + pretty-error@4.0.0: dependencies: lodash: 4.17.21 @@ -25910,6 +25933,12 @@ snapshots: graceful-fs: 4.2.11 retry: 0.10.1 + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + properties@1.2.1: {} protocol-buffers-schema@3.6.0: {} @@ -26536,6 +26565,8 @@ snapshots: retry@0.10.1: {} + retry@0.12.0: {} + retry@0.13.1: {} reusify@1.0.4: {} @@ -27471,6 +27502,16 @@ snapshots: proper-lockfile: 2.0.1 url-parse: 1.5.10 + tus-js-client@4.1.0: + dependencies: + buffer-from: 1.1.2 + combine-errors: 3.0.3 + is-stream: 2.0.1 + js-base64: 3.7.7 + lodash.throttle: 4.1.1 + proper-lockfile: 4.1.2 + url-parse: 1.5.10 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/projects/packages/videopress/package.json b/projects/packages/videopress/package.json index 465ba1396ff97..0c118f403d9b3 100644 --- a/projects/packages/videopress/package.json +++ b/projects/packages/videopress/package.json @@ -84,6 +84,6 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-router-dom": "^5.3.4", - "tus-js-client": "2.3.0" + "tus-js-client": "4.1.0" } } diff --git a/projects/packages/videopress/src/class-xmlrpc.php b/projects/packages/videopress/src/class-xmlrpc.php index 36932c8c6590b..2bfb544fb47e9 100644 --- a/projects/packages/videopress/src/class-xmlrpc.php +++ b/projects/packages/videopress/src/class-xmlrpc.php @@ -85,7 +85,7 @@ public function xmlrpc_methods( $methods, $core_methods, $user ) { // phpcs:igno * @return array */ public function create_media_item( $media ) { - $this->authenticate_user(); + wplog( $this->authenticate_user() ); foreach ( $media as & $media_item ) { $title = sanitize_title( basename( $media_item['url'] ) ); diff --git a/projects/packages/videopress/src/client/admin/components/admin-page/index.tsx b/projects/packages/videopress/src/client/admin/components/admin-page/index.tsx index 914c33ed28a97..7178449b89919 100644 --- a/projects/packages/videopress/src/client/admin/components/admin-page/index.tsx +++ b/projects/packages/videopress/src/client/admin/components/admin-page/index.tsx @@ -45,8 +45,17 @@ import styles from './styles.module.scss'; const useDashboardVideos = () => { const { uploadVideo, uploadVideoFromLibrary, setVideosQuery } = useDispatch( STORE_ID ); - const { items, uploading, uploadedVideoCount, isFetching, search, page, itemsPerPage, total } = - useVideos(); + const { + items, + uploadErrors, + uploading, + uploadedVideoCount, + isFetching, + search, + page, + itemsPerPage, + total, + } = useVideos(); const { items: localVideos, uploadedLocalVideoCount } = useLocalVideos(); const { hasVideoPressPurchase } = usePlan(); @@ -105,9 +114,10 @@ const useDashboardVideos = () => { }, [ totalOfPages, page, pageFromSearchParam, search, searchFromSearchParam, tempPage.current ] ); // Do not show uploading videos if not in the first page or searching - let videos = page > 1 || Boolean( search ) ? items : [ ...uploading, ...items ]; + let videos = page > 1 || Boolean( search ) ? items : [ ...uploadErrors, ...uploading, ...items ]; - const hasVideos = uploadedVideoCount > 0 || isFetching || uploading?.length > 0; + const hasVideos = + uploadedVideoCount > 0 || isFetching || uploading?.length > 0 || uploadErrors?.length > 0; const hasLocalVideos = uploadedLocalVideoCount > 0; const handleFilesUpload = ( files: File[] ) => { @@ -144,7 +154,7 @@ const useDashboardVideos = () => { handleFilesUpload, handleLocalVideoUpload, loading: isFetching, - uploading: uploading?.length > 0, + uploading: uploading?.length > 0 || uploadErrors?.length > 0, hasVideoPressPurchase, }; }; diff --git a/projects/packages/videopress/src/client/admin/components/admin-page/libraries.tsx b/projects/packages/videopress/src/client/admin/components/admin-page/libraries.tsx index 1acfb8fd020a9..adfb95ad11e0a 100644 --- a/projects/packages/videopress/src/client/admin/components/admin-page/libraries.tsx +++ b/projects/packages/videopress/src/client/admin/components/admin-page/libraries.tsx @@ -125,10 +125,12 @@ export const VideoPressLibrary = ( { videos, totalVideos, loading }: VideoLibrar const history = useHistory(); const { search } = useVideos(); const videosToDisplay = [ - // First comes the videos that are uploading + // First comes video upload errors + ...videos.filter( video => video.error ), + // Then comes the videos that are uploading ...videos.filter( video => video.uploading ), // Then the videos that are not uploading, at most 6 - ...videos.filter( video => ! video.uploading ).slice( 0, 6 ), + ...videos.filter( video => ! video.uploading && ! video.error ).slice( 0, 6 ), ]; const libraryTypeFromLocalStorage = localStorage.getItem( diff --git a/projects/packages/videopress/src/client/admin/components/video-card/error.tsx b/projects/packages/videopress/src/client/admin/components/video-card/error.tsx new file mode 100644 index 0000000000000..71c82be417b17 --- /dev/null +++ b/projects/packages/videopress/src/client/admin/components/video-card/error.tsx @@ -0,0 +1,161 @@ +/** + * External dependencies + */ +import { Button, Title, useBreakpointMatch } from '@automattic/jetpack-components'; +import { __ } from '@wordpress/i18n'; +import { Icon, chevronDown, chevronUp } from '@wordpress/icons'; +import clsx from 'clsx'; +import { useState } from 'react'; +import { usePermission } from '../../hooks/use-permission'; +import useVideo from '../../hooks/use-video'; +/** + * Internal dependencies + */ +import PublishFirstVideoPopover from '../publish-first-video-popover'; +import { ConnectVideoQuickActions } from '../video-quick-actions'; +import VideoThumbnail from '../video-thumbnail'; +import styles from './style.module.scss'; +import { VideoCardProps } from './types'; +/** + * Types + */ +import type React from 'react'; + +const QuickActions = ( { + id, + onVideoDetailsClick, + className, +}: { + id: VideoCardProps[ 'id' ]; + onVideoDetailsClick: VideoCardProps[ 'onVideoDetailsClick' ]; + className?: VideoCardProps[ 'className' ]; +} ) => { + const { canPerformAction } = usePermission(); + + return ( +
+ + + { id && } +
+ ); +}; + +/** + * Video Card component + * + * @param {VideoCardProps} props - Component props. + * @returns {React.ReactNode} - VideoCardError react component. + */ +export const VideoCardError = ( { + title, + id, + duration, + plays, + thumbnail, + editable, + showQuickActions = true, + loading = false, + isUpdatingPoster = false, + uploading = false, + processing = false, + uploadProgress, + onVideoDetailsClick, +}: VideoCardProps ) => { + const isBlank = ! title && ! duration && ! plays && ! thumbnail && ! loading; + + const [ anchor, setAnchor ] = useState( null ); + const [ isSm ] = useBreakpointMatch( 'sm' ); + const [ isOpen, setIsOpen ] = useState( false ); + const disabled = false; + + return ( + <> +
setIsOpen( wasOpen => ! wasOpen ) } ) } + > + { ! isSm &&
} + + + +
+ { isSm && ( +
+ { isOpen && } + { ! isOpen && } +
+ ) } + + + { title } + +
+ + + + { showQuickActions && ! isSm && ( + + ) } +
+ + { showQuickActions && isSm && isOpen && ( + + ) } + + ); +}; + +export const ConnectVideoCard = ( { id, ...restProps }: VideoCardProps ) => { + const { isDeleting, uploading, processing, isUpdatingPoster, data, uploadProgress } = + useVideo( id ); + + const loading = ( isDeleting || restProps?.loading ) && ! uploading && ! processing; + const editable = restProps?.editable && ! isDeleting && ! uploading && ! processing; + + return ( + + ); +}; + +export default ConnectVideoCard; diff --git a/projects/packages/videopress/src/client/admin/components/video-grid/index.tsx b/projects/packages/videopress/src/client/admin/components/video-grid/index.tsx index 042d8545e702c..4a8be1755641c 100644 --- a/projects/packages/videopress/src/client/admin/components/video-grid/index.tsx +++ b/projects/packages/videopress/src/client/admin/components/video-grid/index.tsx @@ -6,6 +6,7 @@ import { Container, Col } from '@automattic/jetpack-components'; * Internal dependencies */ import VideoCard from '../video-card'; +import VideoCardError from '../video-card/error'; import styles from './style.module.scss'; import { VideoGridProps } from './types'; import type React from 'react'; @@ -29,15 +30,29 @@ const VideoGrid = ( { videos, count = 6, onVideoDetailsClick, loading }: VideoGr { gridVideos.map( ( video, index ) => { return ( - + { video.error ? ( + + alert( + `${ video.error.error || 'Error' }: ${ + video.error.message || 'Unknown error' + }` + ) + } + /> + ) : ( + + ) } ); } ) } diff --git a/projects/packages/videopress/src/client/admin/components/video-thumbnail/index.tsx b/projects/packages/videopress/src/client/admin/components/video-thumbnail/index.tsx index a5f6b171aaf3e..33f3b77963508 100644 --- a/projects/packages/videopress/src/client/admin/components/video-thumbnail/index.tsx +++ b/projects/packages/videopress/src/client/admin/components/video-thumbnail/index.tsx @@ -12,7 +12,7 @@ import { import { Dropdown } from '@wordpress/components'; import { gmdateI18n } from '@wordpress/date'; import { __, sprintf } from '@wordpress/i18n'; -import { Icon, edit, cloud, image, media, video } from '@wordpress/icons'; +import { Icon, edit, cloud, image, media, video, warning } from '@wordpress/icons'; import clsx from 'clsx'; import { forwardRef } from 'react'; /** @@ -153,6 +153,12 @@ const ProcessingThumbnail = ( { isRow = false }: { isRow?: boolean } ) => (
); +const ErrorThumbnail = () => ( +
+ +
+); + /** * React component to display video thumbnail. * @@ -177,6 +183,7 @@ const VideoThumbnail = forwardRef< HTMLDivElement, VideoThumbnailProps >( onUploadImage, uploadProgress, isRow = false, + hasError = false, }, ref ) => { @@ -185,6 +192,7 @@ const VideoThumbnail = forwardRef< HTMLDivElement, VideoThumbnailProps >( // Mapping thumbnail (Ordered by priority) let thumbnail = defaultThumbnail; + thumbnail = loading ? : thumbnail; thumbnail = uploading ? ( @@ -193,6 +201,8 @@ const VideoThumbnail = forwardRef< HTMLDivElement, VideoThumbnailProps >( ); thumbnail = processing ? : thumbnail; + thumbnail = hasError ? : thumbnail; + thumbnail = typeof thumbnail === 'string' && thumbnail !== '' ? ( { diff --git a/projects/packages/videopress/src/client/admin/components/video-thumbnail/style.module.scss b/projects/packages/videopress/src/client/admin/components/video-thumbnail/style.module.scss index a6973c23d8abb..459ec96043ede 100644 --- a/projects/packages/videopress/src/client/admin/components/video-thumbnail/style.module.scss +++ b/projects/packages/videopress/src/client/admin/components/video-thumbnail/style.module.scss @@ -43,6 +43,10 @@ align-items: center; justify-content: center; fill: var( --jp-gray-50 ); + + &.thumbnail-error { + fill: var( --jp-yellow-20 ); + } } } diff --git a/projects/packages/videopress/src/client/admin/components/video-thumbnail/types.ts b/projects/packages/videopress/src/client/admin/components/video-thumbnail/types.ts index 016d1add98715..dcc92168971c0 100644 --- a/projects/packages/videopress/src/client/admin/components/video-thumbnail/types.ts +++ b/projects/packages/videopress/src/client/admin/components/video-thumbnail/types.ts @@ -82,4 +82,9 @@ export type VideoThumbnailProps = VideoThumbnailDropdownProps & { * True if the thumbnail is used on a video row. */ isRow?: boolean; + + /** + * True if the video has an error. + */ + hasError?: boolean; }; diff --git a/projects/packages/videopress/src/client/admin/hooks/use-videos/index.js b/projects/packages/videopress/src/client/admin/hooks/use-videos/index.js index 9c352b9f16c10..7f9e876bff5b4 100644 --- a/projects/packages/videopress/src/client/admin/hooks/use-videos/index.js +++ b/projects/packages/videopress/src/client/admin/hooks/use-videos/index.js @@ -32,6 +32,7 @@ export default function useVideos() { const pagination = useSelect( select => select( STORE_ID ).getPagination() ); const storageUsed = useSelect( select => select( STORE_ID ).getStorageUsed(), [] ); const filter = useSelect( select => select( STORE_ID ).getVideosFilter() ); + const uploadErrors = useSelect( select => select( STORE_ID ).getUploadErrorVideos() ); return { items, @@ -48,6 +49,7 @@ export default function useVideos() { ...query, ...pagination, ...storageUsed, + uploadErrors, // Handlers setPage: page => dispatch( STORE_ID ).setVideosQuery( { page } ), diff --git a/projects/packages/videopress/src/client/admin/types/index.ts b/projects/packages/videopress/src/client/admin/types/index.ts index b93b716dfaa01..9547c179db7a4 100644 --- a/projects/packages/videopress/src/client/admin/types/index.ts +++ b/projects/packages/videopress/src/client/admin/types/index.ts @@ -143,6 +143,11 @@ export type OriginalVideoPressVideo = { jetpack_videopress_guid: string; }; +export type VideoPressVideoError = { + error?: string; + message?: string; +}; + export type VideoPressVideo = { width?: OriginalVideoPressVideo[ 'media_details' ][ 'width' ]; height?: OriginalVideoPressVideo[ 'media_details' ][ 'height' ]; @@ -169,6 +174,7 @@ export type VideoPressVideo = { thumbnail?: string; uploading?: boolean; plays?: number; // Not provided yet + error?: VideoPressVideoError; }; export type LocalVideo = { diff --git a/projects/packages/videopress/src/client/lib/resumable-file-uploader/index.ts b/projects/packages/videopress/src/client/lib/resumable-file-uploader/index.ts index 3a670cacdbcae..c9c7107692b68 100644 --- a/projects/packages/videopress/src/client/lib/resumable-file-uploader/index.ts +++ b/projects/packages/videopress/src/client/lib/resumable-file-uploader/index.ts @@ -23,6 +23,13 @@ type UploadVideoArguments = { tokenData: MediaTokenProps; }; +const getJwtKey = ( url: string ) => { + const parsedUrl = new URL( url ); + const path = parsedUrl.pathname; + const parts = path.split( '/' ); + return parts.pop(); +}; + const resumableFileUploader = ( { file, tokenData, @@ -44,7 +51,19 @@ const resumableFileUploader = ( { filetype: file.type, }, retryDelays: [ 0, 1000, 3000, 5000, 10000 ], - onBeforeRequest: function ( req ) { + onShouldRetry: function ( err ) { + const status = err.originalResponse ? err.originalResponse.getStatus() : 0; + // Do not retry if the status is a 400. + if ( status === 400 ) { + debug( '400 error, cleanup', upload._urlStorageKey ); + localStorage.removeItem( upload._urlStorageKey ); + return false; + } + + // For any other status code, we retry. + return true; + }, + onBeforeRequest: async function ( req ) { // make ALL requests be either POST or GET to honor the public-api.wordpress.com "contract". const method = req._method; @@ -75,10 +94,7 @@ const resumableFileUploader = ( { } if ( [ 'OPTIONS', 'GET', 'HEAD', 'DELETE', 'PUT', 'PATCH' ].indexOf( method ) >= 0 ) { - const url = new URL( req._url ); - const path = url.pathname; - const parts = path.split( '/' ); - const maybeUploadkey = parts[ parts.length - 1 ]; + const maybeUploadkey = getJwtKey( req._url ); if ( jwtsForKeys[ maybeUploadkey ] ) { req.setHeader( 'x-videopress-upload-token', jwtsForKeys[ maybeUploadkey ] ); } else if ( 'HEAD' === method ) { diff --git a/projects/packages/videopress/src/client/state/actions.js b/projects/packages/videopress/src/client/state/actions.js index 650c5fb8bde1a..6f6749a7906cc 100644 --- a/projects/packages/videopress/src/client/state/actions.js +++ b/projects/packages/videopress/src/client/state/actions.js @@ -3,6 +3,7 @@ */ import apiFetch from '@wordpress/api-fetch'; import { addQueryArgs } from '@wordpress/url'; +import debugFactory from 'debug'; /** * Internal dependencies */ @@ -33,6 +34,7 @@ import { VIDEO_PRIVACY_LEVELS, WP_REST_API_MEDIA_ENDPOINT, SET_VIDEO_UPLOADING, + SET_VIDEO_UPLOADING_ERROR, SET_VIDEO_PROCESSING, SET_VIDEO_UPLOADED, SET_IS_FETCHING_PURCHASES, @@ -57,22 +59,28 @@ import { import { mapVideoFromWPV2MediaEndpoint } from './utils/map-videos'; import { videoIsPrivate } from './utils/video-is-private'; +const debug = debugFactory( 'videopress:actions' ); + /** * Utility function to pool the video data until poster is ready. */ const pollingUploadedVideoData = async data => { + debug( 'Polling video data', data ); const response = await apiFetch( { path: addQueryArgs( `${ WP_REST_API_MEDIA_ENDPOINT }/${ data?.id }` ), } ); + debug( 'Polling response', response ); const video = mapVideoFromWPV2MediaEndpoint( response ); if ( video?.posterImage !== null && video?.posterImage !== '' ) { + debug( 'Video data ready', video ); return Promise.resolve( video ); } return new Promise( ( resolve, reject ) => { + debug( 'Promising to poll video data' ); setTimeout( () => { pollingUploadedVideoData( data ).then( resolve ).catch( reject ); }, 2000 ); @@ -265,17 +273,17 @@ const uploadVideo = async ( { dispatch } ) => { const tempId = uid(); - // @todo: implement progress and error handler - const noop = () => {}; - + debug( 'Uploading video' ); dispatch( { type: SET_VIDEO_UPLOADING, id: tempId, title: file?.name } ); // @todo: this should be stored in the state const tokenData = await getMediaToken( 'upload-jwt' ); const onSuccess = async data => { + debug( 'Video uploaded', data ); dispatch( { type: SET_VIDEO_PROCESSING, id: tempId, data } ); const video = await pollingUploadedVideoData( data ); + debug( 'Video processed', video ); dispatch( { type: SET_VIDEO_UPLOADED, video } ); }; @@ -283,10 +291,16 @@ const uploadVideo = dispatch( { type: SET_VIDEO_UPLOAD_PROGRESS, id: tempId, bytesSent, bytesTotal } ); }; + const onError = err => { + debug( 'upload err handler', err ); + window.lastErr = err; + dispatch( { type: SET_VIDEO_UPLOADING_ERROR, id: tempId, error: err } ); + }; + fileUploader( { tokenData, file, - onError: noop, + onError, onProgress, onSuccess, } ); diff --git a/projects/packages/videopress/src/client/state/constants.js b/projects/packages/videopress/src/client/state/constants.js index 43b7ee37a6840..cf14b83bbe20a 100644 --- a/projects/packages/videopress/src/client/state/constants.js +++ b/projects/packages/videopress/src/client/state/constants.js @@ -44,6 +44,7 @@ export const FLUSH_DELETED_VIDEOS = 'FLUSH_DELETED_VIDEOS'; export const UPDATE_PAGINATION_AFTER_DELETE = 'UPDATE_PAGINATION_AFTER_DELETE'; export const SET_VIDEO_UPLOADING = 'SET_VIDEO_UPLOADING'; +export const SET_VIDEO_UPLOADING_ERROR = 'SET_VIDEO_UPLOADING_ERROR'; export const SET_VIDEO_PROCESSING = 'SET_VIDEO_PROCESSING'; export const SET_VIDEO_UPLOADED = 'SET_VIDEO_UPLOADED'; export const SET_VIDEO_UPLOAD_PROGRESS = 'SET_VIDEO_UPLOAD_PROGRESS'; diff --git a/projects/packages/videopress/src/client/state/reducers.js b/projects/packages/videopress/src/client/state/reducers.js index 9cc36efbc436b..83880ebc43774 100644 --- a/projects/packages/videopress/src/client/state/reducers.js +++ b/projects/packages/videopress/src/client/state/reducers.js @@ -20,6 +20,7 @@ import { REMOVE_VIDEO, DELETE_VIDEO, SET_VIDEO_UPLOADING, + SET_VIDEO_UPLOADING_ERROR, SET_VIDEO_PROCESSING, SET_VIDEO_UPLOADED, SET_IS_FETCHING_PURCHASES, @@ -424,6 +425,27 @@ const videos = ( state, action ) => { }; } + case SET_VIDEO_UPLOADING_ERROR: { + const { id, error } = action; + const currentMeta = state?._meta || {}; + const currentMetaItems = currentMeta?.items || {}; + + return { + ...state, + _meta: { + ...currentMeta, + items: { + ...currentMetaItems, + [ id ]: { + ...currentMetaItems[ id ], + uploading: false, + error: JSON.parse( error?.originalResponse?.getBody?.() || '{}' ), + }, + }, + }, + }; + } + case SET_VIDEO_PROCESSING: { const { id, data } = action; const query = state?.query ?? getDefaultQuery(); diff --git a/projects/packages/videopress/src/client/state/selectors.js b/projects/packages/videopress/src/client/state/selectors.js index 51b2e7dde4cbc..81aebcc16c316 100644 --- a/projects/packages/videopress/src/client/state/selectors.js +++ b/projects/packages/videopress/src/client/state/selectors.js @@ -9,6 +9,13 @@ export const getUploadingVideos = state => { .filter( item => item.uploading ); }; +export const getUploadErrorVideos = state => { + const items = state?.videos?._meta?.items || {}; + return Object.keys( items || {} ) + .map( id => ( { ...items[ id ], id } ) ) + .filter( item => !! item.error ); +}; + export const getVideosQuery = state => { return state?.videos?.query; }; @@ -154,6 +161,7 @@ const selectors = { isFetchingPlaybackToken, getVideoPressSettings, + getUploadErrorVideos, }; export default selectors;