Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion packages/block-library/src/image/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,3 @@ figure.wp-block-image:not(.wp-block) {
// Corresponds to the size of the textarea in the block inspector.
width: 250px;
}

2 changes: 1 addition & 1 deletion packages/block-library/src/image/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ export default function Image( {
'postType',
'attachment',
id,
{ context: 'view' }
{ context: 'edit' }
)
: null;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/**
* WordPress dependencies
*/
import { PanelBody, Button, Spinner } from '@wordpress/components';
import { useSelect, useDispatch, useRegistry } from '@wordpress/data';
import { __, sprintf } from '@wordpress/i18n';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { store as coreStore } from '@wordpress/core-data';
import { store as uploadStore } from '@wordpress/upload-media';
import { useState, useRef, useEffect } from '@wordpress/element';

/**
* Internal dependencies
*/
import { unlock } from '../../lock-unlock';
import { getImageAttachmentIds } from '../provider/use-missing-sizes-check';

export default function MaybeMissingSizesPanel() {
const [ isGenerating, setIsGenerating ] = useState( false );
const [ progress, setProgress ] = useState( { current: 0, total: 0 } );
const checkIntervalRef = useRef( null );
const registry = useRegistry();
const { invalidateResolution } = useDispatch( coreStore );

const isEnabled = !! window.__clientSideMediaProcessing;

// Clean up polling interval on unmount.
useEffect( () => {
return () => {
if ( checkIntervalRef.current ) {
clearInterval( checkIntervalRef.current );
}
};
}, [] );

const blocks = useSelect(
( select ) => {
if ( ! isEnabled ) {
return [];
}
return select( blockEditorStore ).getBlocks();
},
[ isEnabled ]
);

const attachmentsWithMissingSizes = useSelect(
( select ) => {
if ( ! isEnabled || ! blocks.length ) {
return [];
}
const ids = getImageAttachmentIds( blocks );
const results = [];
for ( const id of ids ) {
const attachment = select( coreStore ).getEntityRecord(
'postType',
'attachment',
id,
{ context: 'edit' }
);
if ( attachment?.missing_image_sizes?.length ) {
results.push( attachment );
}
}
return results;
},
[ isEnabled, blocks ]
);

if ( ! isGenerating && ! attachmentsWithMissingSizes.length ) {
return null;
}

async function generateAllMissingSizes() {
const total = attachmentsWithMissingSizes.length;
setIsGenerating( true );
setProgress( { current: 0, total } );

for ( const attachment of attachmentsWithMissingSizes ) {
unlock(
registry.dispatch( uploadStore )
).queueMissingSizeGeneration( {
attachmentId: attachment.id,
sourceUrl: attachment.source_url,
missingSizes: attachment.missing_image_sizes,
} );
}

// Poll for completion by checking if any attachments still have missing sizes.
checkIntervalRef.current = setInterval( async () => {
let remaining = 0;
for ( const attachment of attachmentsWithMissingSizes ) {
await invalidateResolution( 'getEntityRecord', [
'postType',
'attachment',
attachment.id,
{ context: 'edit' },
] );
const updated = registry
.select( coreStore )
.getEntityRecord( 'postType', 'attachment', attachment.id, {
context: 'edit',
} );
if ( updated?.missing_image_sizes?.length ) {
remaining++;
}
}
const completed = total - remaining;
setProgress( { current: completed, total } );
if ( remaining === 0 ) {
clearInterval( checkIntervalRef.current );
checkIntervalRef.current = null;
setIsGenerating( false );
}
}, 3000 );
}

const panelBodyTitle = [
__( 'Suggestion:' ),
<span className="editor-post-publish-panel__link" key="label">
{ __( 'Missing image sizes' ) }
</span>,
];

return (
<PanelBody initialOpen title={ panelBodyTitle }>
<p>
{ sprintf(
/* translators: %d: number of images with missing sub-sizes */
__( '%d image(s) are missing sub-sizes.' ),
attachmentsWithMissingSizes.length
) }
</p>
{ isGenerating ? (
<>
<Spinner />
<span style={ { marginLeft: '8px' } }>
{ sprintf(
/* translators: 1: current image number, 2: total images */
__(
'Generating missing sizes for image %1$d of %2$d'
),
progress.current + 1,
progress.total
) }
</span>
</>
) : (
<Button
__next40pxDefaultSize
variant="secondary"
onClick={ generateAllMissingSizes }
>
{ __( 'Generate missing sizes' ) }
</Button>
) }
</PanelBody>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import MaybePostFormatPanel from './maybe-post-format-panel';
import { store as editorStore } from '../../store';
import MaybeCategoryPanel from './maybe-category-panel';
import MaybeUploadMedia from './maybe-upload-media';
import MaybeMissingSizes from './maybe-missing-sizes';

function PostPublishPanelPrepublish( { children } ) {
const {
Expand Down Expand Up @@ -105,6 +106,7 @@ function PostPublishPanelPrepublish( { children } ) {
</div>
</div>
<MaybeUploadMedia />
<MaybeMissingSizes />
{ hasPublishAction && (
<>
<PanelBody
Expand Down
4 changes: 4 additions & 0 deletions packages/editor/src/components/provider/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { useHideBlocksFromInserter } from './use-hide-blocks-from-inserter';
import { useRevisionBlocks } from './use-revision-blocks';
import useCommands from '../commands';
import useUploadSaveLock from './use-upload-save-lock';
import useNetworkReconnect from './use-network-reconnect';
import BlockRemovalWarnings from '../block-removal-warnings';
import StartPageOptions from '../start-page-options';
import KeyboardShortcutHelpModal from '../keyboard-shortcut-help-modal';
Expand Down Expand Up @@ -396,6 +397,9 @@ export const ExperimentalEditorProvider = withRegistryProvider(
// Lock post saving when media uploads are in progress (experimental feature).
useUploadSaveLock();

// Pause/resume media upload queue on network disconnect/reconnect.
useNetworkReconnect();

if ( ! isReady || ! mode ) {
return null;
}
Expand Down
26 changes: 26 additions & 0 deletions packages/editor/src/components/provider/use-missing-sizes-check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Recursively extracts image attachment IDs from blocks.
*
* @param {Array} blocks List of blocks to search.
* @return {Set<number>} Set of attachment IDs found.
*/
export function getImageAttachmentIds( blocks ) {
const ids = new Set();
for ( const block of blocks ) {
if ( block.name === 'core/image' && block.attributes.id ) {
ids.add( block.attributes.id );
}
if ( block.name === 'core/media-text' && block.attributes.mediaId ) {
ids.add( block.attributes.mediaId );
}
if ( block.name === 'core/cover' && block.attributes.id ) {
ids.add( block.attributes.id );
}
if ( block.innerBlocks?.length ) {
for ( const id of getImageAttachmentIds( block.innerBlocks ) ) {
ids.add( id );
}
}
}
return ids;
}
44 changes: 44 additions & 0 deletions packages/editor/src/components/provider/use-network-reconnect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* WordPress dependencies
*/
import { useEffect } from '@wordpress/element';
import { useRegistry } from '@wordpress/data';
import { store as uploadStore } from '@wordpress/upload-media';

/**
* Internal dependencies
*/
import { unlock } from '../../lock-unlock';

/**
* A hook that pauses the media upload queue when the browser goes offline
* and resumes it when connectivity is restored.
*
* Only active when client-side media processing is enabled.
*/
export default function useNetworkReconnect() {
const isEnabled = window.__clientSideMediaProcessing;
const registry = useRegistry();

useEffect( () => {
if ( ! isEnabled ) {
return;
}

const handleOffline = () => {
unlock( registry.dispatch( uploadStore ) ).pauseQueue();
};

const handleOnline = () => {
unlock( registry.dispatch( uploadStore ) ).resumeQueue();
};

window.addEventListener( 'offline', handleOffline );
window.addEventListener( 'online', handleOnline );

return () => {
window.removeEventListener( 'offline', handleOffline );
window.removeEventListener( 'online', handleOnline );
};
}, [ isEnabled, registry ] );
}
23 changes: 23 additions & 0 deletions packages/upload-media/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,24 @@ _Parameters_

Cancels an item in the queue based on an error.

If the error is retryable and the item hasn't exceeded the maximum retry attempts, it will be scheduled for automatic retry instead of being cancelled.

_Parameters_

- _id_ `QueueItemId`: Item ID.
- _error_ `Error`: Error instance.
- _silent_ Whether to cancel the item silently, without invoking its `onError` callback.

#### executeRetry

Executes a scheduled retry for an item.

This is called by the timer set in scheduleRetry. It verifies the item is still in PendingRetry status before proceeding with the retry.

_Parameters_

- _id_ `QueueItemId`: Item ID.

#### retryItem

Retries a failed item in the queue.
Expand All @@ -75,6 +87,17 @@ _Parameters_

- _id_ `QueueItemId`: Item ID.

#### scheduleRetry

Schedules an automatic retry for a failed item.

Uses exponential backoff with jitter to determine the retry delay. The item will be placed in PendingRetry status and automatically retried after the calculated delay.

_Parameters_

- _id_ `QueueItemId`: Item ID.
- _error_ `Error`: The error that caused the failure.

<!-- END TOKEN(Autogenerated actions|src/store/actions.ts) -->

### Selectors
Expand Down
Loading
Loading