diff --git a/packages/editor/src/components/provider/index.js b/packages/editor/src/components/provider/index.js index db74d142899212..46b8cf2c6ca3f8 100644 --- a/packages/editor/src/components/provider/index.js +++ b/packages/editor/src/components/provider/index.js @@ -36,6 +36,7 @@ import NavigationBlockEditingMode from './navigation-block-editing-mode'; import { useHideBlocksFromInserter } from './use-hide-blocks-from-inserter'; import useCommands from '../commands'; import useUploadSaveLock from './use-upload-save-lock'; +import useInvalidateMediaAfterUpload from './use-invalidate-media-after-upload'; import BlockRemovalWarnings from '../block-removal-warnings'; import StartPageOptions from '../start-page-options'; import KeyboardShortcutHelpModal from '../keyboard-shortcut-help-modal'; @@ -383,6 +384,9 @@ export const ExperimentalEditorProvider = withRegistryProvider( // Lock post saving when media uploads are in progress (experimental feature). useUploadSaveLock(); + // Invalidate attachment entities after uploads complete so blocks see updated data. + useInvalidateMediaAfterUpload(); + if ( ! isReady || ! mode ) { return null; } diff --git a/packages/editor/src/components/provider/use-invalidate-media-after-upload.js b/packages/editor/src/components/provider/use-invalidate-media-after-upload.js new file mode 100644 index 00000000000000..b81114492c5196 --- /dev/null +++ b/packages/editor/src/components/provider/use-invalidate-media-after-upload.js @@ -0,0 +1,71 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useEffect } from '@wordpress/element'; +import { usePrevious } from '@wordpress/compose'; +import { store as uploadStore } from '@wordpress/upload-media'; +import { store as coreDataStore } from '@wordpress/core-data'; + +const EMPTY_ARRAY = []; + +function getAttachmentIds( items ) { + const ids = new Set(); + for ( const item of items ) { + if ( item.attachment?.id ) { + ids.add( item.attachment.id ); + } + } + return ids; +} + +/** + * After client-side media processing completes, the entity store has stale + * attachment data (empty `media_details.sizes`) because the initial upload + * uses `generate_sub_sizes: false`. This hook watches the upload queue and + * invalidates each attachment's entity record once its item (including any + * sideloads) is removed from the queue, so blocks re-fetch updated data. + * + * When client-side media processing is not enabled, invalidation is handled + * by `receiveEntityRecords` in the `onFileChange` callback of + * `packages/editor/src/utils/media-upload/index.js`. + */ +export default function useInvalidateMediaAfterUpload() { + const items = useSelect( ( select ) => { + if ( ! window.__clientSideMediaProcessing ) { + return EMPTY_ARRAY; + } + return select( uploadStore ).getItems(); + }, [] ); + + const previousItems = usePrevious( items ); + const { invalidateResolution } = useDispatch( coreDataStore ); + + useEffect( () => { + if ( ! window.__clientSideMediaProcessing || ! previousItems ) { + return; + } + + const currentIds = getAttachmentIds( items ); + const previousIds = getAttachmentIds( previousItems ); + + for ( const id of previousIds ) { + if ( ! currentIds.has( id ) ) { + // Invalidate with and without the query argument, since + // resolution keys must exactly match the args used by + // each consumer's getEntityRecord() call. + invalidateResolution( 'getEntityRecord', [ + 'postType', + 'attachment', + id, + { context: 'view' }, // Used by the image block. + ] ); + invalidateResolution( 'getEntityRecord', [ + 'postType', + 'attachment', + id, // Used by the editor's mediaUpload onFileChange. + ] ); + } + } + }, [ items, previousItems, invalidateResolution ] ); +}