From e95ef25ebeff14d55fc1008b66e8166d22693d8a Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Thu, 23 Jan 2025 23:03:22 -0500 Subject: [PATCH 01/27] Streamline generation of mediaBlocks --- src/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 116649b..9514ac0 100644 --- a/src/index.js +++ b/src/index.js @@ -27,10 +27,9 @@ const getMediaBlocks = ( blocks ) => blocks.reduce( ); const useMediaBlocks = () => { - const blocks = useSelect( ( select ) => select( blockEditorStore ).getBlocks() ); + const mediaBlocks = useSelect( ( select ) => getMediaBlocks( select( blockEditorStore ).getBlocks() ) ); const featuredImageId = useSelect( ( select ) => select( 'core/editor' ).getEditedPostAttribute( 'featured_media' ) ); const { imageIds, videoIds, blocksByAttributeId } = useMemo( () => { - const mediaBlocks = getMediaBlocks( blocks ); const imageIds = []; const videoIds = []; const blocksByAttributeId = {}; @@ -49,7 +48,7 @@ const useMediaBlocks = () => { imageIds.push( featuredImageId ); } return { imageIds, videoIds, blocksByAttributeId }; - }, [ blocks, featuredImageId ] ); + }, [ mediaBlocks, featuredImageId ] ); const imageRecords = useEntityRecords( 'postType', 'attachment', { per_page: imageIds.length, include: imageIds, @@ -62,6 +61,7 @@ const useMediaBlocks = () => { attachments: imageRecords.concat( videoRecords ), featuredImageId, blocksByAttributeId, + mediaBlocks, imageCount: imageIds.length, videoCount: videoIds.length, }; From e99dac02c45fcd27aa2661bc05aa6e658cc42217 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Thu, 23 Jan 2025 23:03:53 -0500 Subject: [PATCH 02/27] Store associated block clientId as a reusable value when looping through images --- src/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 9514ac0..ba5774d 100644 --- a/src/index.js +++ b/src/index.js @@ -143,10 +143,11 @@ const HMMediaWeightSidebar = ( ...args ) => { title={ __( 'Individual Media Items', 'hm-media-weight' ) } > { attachments.map( ( attachment ) => { + const associatedBlockClientId = blocksByAttributeId[ attachment.id ]; const blockButton = attachment.id !== featuredImageId ? ( ) : ''; From 505deef24537931bc2f8f6e606092183d3589bbc Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 24 Jan 2025 00:19:21 -0500 Subject: [PATCH 03/27] Expose filterable content weight parameter --- altis-media-weight.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/altis-media-weight.php b/altis-media-weight.php index 4994e11..bd09d5c 100644 --- a/altis-media-weight.php +++ b/altis-media-weight.php @@ -47,7 +47,19 @@ function register_block_plugin_editor_scripts() { 'hm-media-weight', 'mediaWeightData', [ - 'mediaThreshold' => apply_filters( 'altis_media_weight_threshold', 2.50 ), + /** + * Filter the threshold at which a post is deemed "too heavy" due to media weight. + * + * @param float $threshold Maxmimum number of megabytes of media permitted per post. + */ + 'mediaThreshold' => apply_filters( 'hm_media_weight_threshold', 2.50 ), + /** + * Filter the expected maximum width (in pixels) for media displayed in post content. + */ + 'contentColumnWidth' => apply_filters( + 'hm_media_weight_content_column_width', + $GLOBALS['content_width'] ?? 1024 + ), ] ); } From 9cb1264d5109ee9ae194f9aa9370da573ac934b9 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 24 Jan 2025 00:19:39 -0500 Subject: [PATCH 04/27] Attempt to read remote images and actually probably DDOS local Tachyon --- src/index.js | 107 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 98 insertions(+), 9 deletions(-) diff --git a/src/index.js b/src/index.js index ba5774d..ec723e3 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -import { useMemo } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { __, sprintf } from '@wordpress/i18n'; import { media } from '@wordpress/icons'; import { PluginSidebar, PluginSidebarMoreMenuItem } from '@wordpress/editor'; @@ -12,6 +12,8 @@ const { mediaThreshold } = window.mediaWeightData; const PLUGIN_NAME = 'hm-media-weight'; const SIDEBAR_NAME = PLUGIN_NAME; +const MB_IN_B = 1000000; +const KB_IN_B = 1000; const getMediaBlocks = ( blocks ) => blocks.reduce( ( mediaBlocks, block ) => { @@ -67,11 +69,54 @@ const useMediaBlocks = () => { }; }; +/** + * Use a HEAD request to measure the size of a remote file (in bytes). + * + * This is necessary because WordPress doesn't store the filesizes of + * dynamic (Tachyon/Photon) images in the database. + * + * @async + * @param {string} imageUri URL for a remote image. + * @returns {Promise} The size of the remote image, in bytes. + */ +async function getFileSize( imageUri ) { + const response = await fetch( imageUri, { method: 'HEAD' } ); + + if ( ! response.ok ) { + throw new Error( `Failed to fetch: ${ response.status }` ); + } + + const contentLength = response.headers.get( 'Content-Length' ); + if ( ! contentLength ) { + throw new Error( 'Content-Length header not found.' ); + } + + return parseInt( contentLength, 10 ); +} + +const imageSizesByUri = {}; + +/** + * Cached wrapper for getFileSize, to avoid repeatedly checking the same image. + * + * @async + * @param {string} imageUri URL for a remote image. + * @returns {number|Promise} The size of the remote image, in bytes. + */ +async function checkImageSize( imageUri ) { + if ( ! imageSizesByUri[ imageUri ] ) { + imageSizesByUri[ imageUri ] = getFileSize( imageUri ); + } + + return imageSizesByUri[ imageUri ]; +} + const HMMediaWeightSidebar = ( ...args ) => { const { attachments, featuredImageId, blocksByAttributeId, + mediaBlocks, imageCount, videoCount } = useMediaBlocks(); @@ -79,8 +124,36 @@ const HMMediaWeightSidebar = ( ...args ) => { let imagesSize = 0; let videosSize = 0; + const [ resolvedRemoteImageSizes, setResolvedImageSizes ] = useState( {} ); + useEffect( () => { + const imageSizeRequests = attachments + .map( ( attachment ) => { + if ( attachment.media_type !== 'image' ) { + return null; + } + const associatedBlockClientId = blocksByAttributeId[ attachment.id ]; + const associatedBlock = mediaBlocks.find( ( block ) => block.clientId === associatedBlockClientId ); + const imageUri = attachment?.media_details?.sizes?.[ associatedBlock?.attributes?.sizeSlug ]?.source_url || null; + if ( ! imageUri ) { + return null; + } + return [ attachment.id, imageUri ]; + } ) + .filter( Boolean ) + .map( ( [ id, uri ] ) => { + return checkImageSize( uri ).then( ( size ) => [ id, size ] ); + } ); + Promise.all( imageSizeRequests ).then( ( sizes ) => { + const resolvedSizes = sizes.reduce( ( memo, [ id, size ] ) => { + memo[ id ] = size; + return memo; + }, {} ); + setResolvedImageSizes( resolvedSizes ); + } ); + }, [ attachments, mediaBlocks ] ); + const DisplayTotal = ( { imagesSize, videosSize } ) => { - const total = ( imagesSize + videosSize ).toFixed( 2 ); + const total = ( ( imagesSize + videosSize ) / MB_IN_B ).toFixed( 2 ); let sizeColor; if ( total >= 0 && total <= ( mediaThreshold / 2 ) ) { @@ -102,8 +175,8 @@ const HMMediaWeightSidebar = ( ...args ) => { return ( <> -

{ __( 'Images total', 'hm-media-weight' ) }: { imagesSize.toFixed( 2 ) }mb

-

{ __( 'Videos total', 'hm-media-weight' ) }: { videosSize.toFixed( 2 ) }mb

+

{ __( 'Images total', 'hm-media-weight' ) }: { ( imagesSize / MB_IN_B ).toFixed( 2 ) }mb

+

{ __( 'Videos total', 'hm-media-weight' ) }: { ( videosSize / MB_IN_B ).toFixed( 2 ) }mb

{ __( 'Total media size', 'hm-media-weight' ) }: { ' ' } @@ -143,11 +216,10 @@ const HMMediaWeightSidebar = ( ...args ) => { title={ __( 'Individual Media Items', 'hm-media-weight' ) } > { attachments.map( ( attachment ) => { - const associatedBlockClientId = blocksByAttributeId[ attachment.id ]; const blockButton = attachment.id !== featuredImageId ? ( ) : ''; @@ -156,20 +228,37 @@ const HMMediaWeightSidebar = ( ...args ) => { if ( attachment.id === featuredImageId ) { type = __( 'Featured image', 'hm-media-weight' ); } - const mediaSize = attachment.media_details.filesize / 1000000; + let mediaSize = attachment.media_details.filesize; if ( attachment.media_type === 'image' ) { - imagesSize = imagesSize + mediaSize; + const remoteImageSize = resolvedRemoteImageSizes[ attachment.id ]; + console.log( { id: attachment.id, remoteImageSize } ); + imagesSize = imagesSize + ( remoteImageSize || mediaSize ); } else { videosSize = videosSize + mediaSize; } + const thumbnail = attachment.media_type === 'image' + ? ( attachment?.media_details?.sizes?.thumbnail?.source_url || attachment.source_url ) + : null; + return (

+ { thumbnail ? ( + + ) : null }

- { type }: { mediaSize.toFixed( 2 ) }mb + { type }: { + ( mediaSize < MB_IN_B ) + ? `${ ( mediaSize / KB_IN_B ).toFixed( 2 ) }kb` + : `${ ( mediaSize / MB_IN_B ).toFixed( 2 ) }mb` + }

From e8f5922293f3f9f4298c04fdb61cc3dbf8c8b325 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 24 Jan 2025 00:44:43 -0500 Subject: [PATCH 05/27] Fix infinite request loop by serializing array to process as JSON string This creates a stable reference for useEffect to trigger smartly --- src/index.js | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/src/index.js b/src/index.js index ec723e3..7682f58 100644 --- a/src/index.js +++ b/src/index.js @@ -125,21 +125,31 @@ const HMMediaWeightSidebar = ( ...args ) => { let videosSize = 0; const [ resolvedRemoteImageSizes, setResolvedImageSizes ] = useState( {} ); + let targetImageURIs = attachments + .map( ( attachment ) => { + if ( attachment.media_type !== 'image' ) { + return null; + } + if ( attachment.id === featuredImageId ) { + // TODO: Understand via filters the expected size of a featured image, + // and report the URI of the expected target file for measurement. + return null; + } + const associatedBlockClientId = blocksByAttributeId[ attachment.id ]; + const associatedBlock = mediaBlocks.find( ( block ) => block.clientId === associatedBlockClientId ); + const imageUri = attachment?.media_details?.sizes?.[ associatedBlock?.attributes?.sizeSlug ]?.source_url || null; + if ( ! imageUri ) { + return null; + } + return [ attachment.id, imageUri ]; + } ) + .filter( Boolean ); + + // Create a stable reference for triggering useEffect. + targetImageURIs = JSON.stringify( targetImageURIs ); + useEffect( () => { - const imageSizeRequests = attachments - .map( ( attachment ) => { - if ( attachment.media_type !== 'image' ) { - return null; - } - const associatedBlockClientId = blocksByAttributeId[ attachment.id ]; - const associatedBlock = mediaBlocks.find( ( block ) => block.clientId === associatedBlockClientId ); - const imageUri = attachment?.media_details?.sizes?.[ associatedBlock?.attributes?.sizeSlug ]?.source_url || null; - if ( ! imageUri ) { - return null; - } - return [ attachment.id, imageUri ]; - } ) - .filter( Boolean ) + const imageSizeRequests = JSON.parse( targetImageURIs ) .map( ( [ id, uri ] ) => { return checkImageSize( uri ).then( ( size ) => [ id, size ] ); } ); @@ -150,7 +160,7 @@ const HMMediaWeightSidebar = ( ...args ) => { }, {} ); setResolvedImageSizes( resolvedSizes ); } ); - }, [ attachments, mediaBlocks ] ); + }, [ targetImageURIs ] ); const DisplayTotal = ( { imagesSize, videosSize } ) => { const total = ( ( imagesSize + videosSize ) / MB_IN_B ).toFixed( 2 ); @@ -232,7 +242,7 @@ const HMMediaWeightSidebar = ( ...args ) => { if ( attachment.media_type === 'image' ) { const remoteImageSize = resolvedRemoteImageSizes[ attachment.id ]; - console.log( { id: attachment.id, remoteImageSize } ); + mediaSize = remoteImageSize || mediaSize; imagesSize = imagesSize + ( remoteImageSize || mediaSize ); } else { videosSize = videosSize + mediaSize; From bdbaa98e6ef98959e972e5983cbf6ba92148289a Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 24 Jan 2025 00:49:19 -0500 Subject: [PATCH 06/27] Set debugging attachment JSON display to "display: none" --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 7682f58..be05bb3 100644 --- a/src/index.js +++ b/src/index.js @@ -275,7 +275,7 @@ const HMMediaWeightSidebar = ( ...args ) => { Attachment ID: { attachment.id }
Go to the attachment post ›

-
+
{ __( 'View entity record JSON', 'hm-media-weight' ) }

From e861ce13e9d56fc03cb30280b06a5524b4ad7c94 Mon Sep 17 00:00:00 2001
From: "K. Adam White" 
Date: Fri, 24 Jan 2025 00:50:51 -0500
Subject: [PATCH 07/27] Show Total Items panel by default, it is not very busy
 or distracting and can be useful info

---
 src/index.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/index.js b/src/index.js
index be05bb3..44ef652 100644
--- a/src/index.js
+++ b/src/index.js
@@ -214,7 +214,7 @@ const HMMediaWeightSidebar = ( ...args ) => {
 			
 			
 				
 					

Images: { imageCount }

From 043eefd58437a9b86c497c5eef99d1b56ff888f4 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 24 Jan 2025 09:48:06 -0500 Subject: [PATCH 08/27] Featured image WIP --- altis-media-weight.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/altis-media-weight.php b/altis-media-weight.php index bd09d5c..c5a5dc5 100644 --- a/altis-media-weight.php +++ b/altis-media-weight.php @@ -60,6 +60,13 @@ function register_block_plugin_editor_scripts() { 'hm_media_weight_content_column_width', $GLOBALS['content_width'] ?? 1024 ), + /** + * Filter the expected maximum width (in pixels) for a desktop featured image. + */ + 'featuredImageWidth' => apply_filters( + 'hm_media_weight_featured_image_width', + $GLOBALS['content_width'] ?? 1024 + ), ] ); } From c289d7df1c6c6b08b62a2a7d2e8c753859915f1f Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 24 Jan 2025 09:52:48 -0500 Subject: [PATCH 09/27] Temporarily disable PHP sniff, we do not have PHPCS installed --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c4e984..46882a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,6 @@ jobs: uses: ./.github/workflows/node.yml secrets: inherit - php: - uses: ./.github/workflows/php.yml - secrets: inherit + # php: + # uses: ./.github/workflows/php.yml + # secrets: inherit From 6cf2bc9377aa7d6ca4e949458b34e28289d6b781 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 24 Jan 2025 09:55:05 -0500 Subject: [PATCH 10/27] Customize ESLint rules --- .eslintrc | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .eslintrc diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..26617e3 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,12 @@ +{ + "extends": [ "plugin:@wordpress/eslint-plugin/recommended" ], + "env": { + "browser": true + }, + "rules": { + "jsdoc/no-undefined-types": [ "off" ], + "jsdoc/check-line-alignment": [ "warn" ], + "jsdoc/check-tag-names": [ "warn" ], + "prettier/prettier": [ "off" ] + } +} From 587d8615d4d54afb26c20145d7db0e3338d9f58d Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 24 Jan 2025 09:56:34 -0500 Subject: [PATCH 11/27] Disable "extraneous dependencies" warning, we use React through externals --- .eslintrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc b/.eslintrc index 26617e3..7d08e22 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,6 +4,7 @@ "browser": true }, "rules": { + "import/no-extraneous-dependencies": [ "off" ], "jsdoc/no-undefined-types": [ "off" ], "jsdoc/check-line-alignment": [ "warn" ], "jsdoc/check-tag-names": [ "warn" ], From db061bc32a10537093d76d7d12f2f19042ce119c Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 24 Jan 2025 09:59:29 -0500 Subject: [PATCH 12/27] Address ESLint issues flagged in plugin code --- src/index.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/index.js b/src/index.js index 44ef652..35197ee 100644 --- a/src/index.js +++ b/src/index.js @@ -32,10 +32,12 @@ const useMediaBlocks = () => { const mediaBlocks = useSelect( ( select ) => getMediaBlocks( select( blockEditorStore ).getBlocks() ) ); const featuredImageId = useSelect( ( select ) => select( 'core/editor' ).getEditedPostAttribute( 'featured_media' ) ); const { imageIds, videoIds, blocksByAttributeId } = useMemo( () => { + // eslint-disable no-shadow const imageIds = []; const videoIds = []; const blocksByAttributeId = {}; - for ( let block of mediaBlocks ) { + // eslint-enable + for ( const block of mediaBlocks ) { if ( ! block.attributes?.id ) { continue; } @@ -77,7 +79,7 @@ const useMediaBlocks = () => { * * @async * @param {string} imageUri URL for a remote image. - * @returns {Promise} The size of the remote image, in bytes. + * @return {Promise} The size of the remote image, in bytes. */ async function getFileSize( imageUri ) { const response = await fetch( imageUri, { method: 'HEAD' } ); @@ -101,7 +103,7 @@ const imageSizesByUri = {}; * * @async * @param {string} imageUri URL for a remote image. - * @returns {number|Promise} The size of the remote image, in bytes. + * @return {number|Promise} The size of the remote image, in bytes. */ async function checkImageSize( imageUri ) { if ( ! imageSizesByUri[ imageUri ] ) { @@ -111,7 +113,7 @@ async function checkImageSize( imageUri ) { return imageSizesByUri[ imageUri ]; } -const HMMediaWeightSidebar = ( ...args ) => { +const HMMediaWeightSidebar = () => { const { attachments, featuredImageId, @@ -162,6 +164,7 @@ const HMMediaWeightSidebar = ( ...args ) => { } ); }, [ targetImageURIs ] ); + // eslint-disable-next-line no-shadow const DisplayTotal = ( { imagesSize, videosSize } ) => { const total = ( ( imagesSize + videosSize ) / MB_IN_B ).toFixed( 2 ); let sizeColor; @@ -177,6 +180,7 @@ const HMMediaWeightSidebar = ( ...args ) => { const warningMsg = total >= mediaThreshold ? (

{ sprintf( + /* translators: %f: Maximum allowed size (in megabytes) for all media on page. */ __( 'Warning! The media in this page exceeds the recommended threshold of %fmb', 'hm-media-weight' ), mediaThreshold ) } From 493c38bf1127e7d87cb4b36c61a1d574a117e900 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Fri, 24 Jan 2025 10:05:42 -0500 Subject: [PATCH 13/27] ESLint: Alter how we try to disable no-shadow in reducers --- src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 35197ee..d29f21f 100644 --- a/src/index.js +++ b/src/index.js @@ -31,12 +31,11 @@ const getMediaBlocks = ( blocks ) => blocks.reduce( const useMediaBlocks = () => { const mediaBlocks = useSelect( ( select ) => getMediaBlocks( select( blockEditorStore ).getBlocks() ) ); const featuredImageId = useSelect( ( select ) => select( 'core/editor' ).getEditedPostAttribute( 'featured_media' ) ); + /* eslint-disable no-shadow */ const { imageIds, videoIds, blocksByAttributeId } = useMemo( () => { - // eslint-disable no-shadow const imageIds = []; const videoIds = []; const blocksByAttributeId = {}; - // eslint-enable for ( const block of mediaBlocks ) { if ( ! block.attributes?.id ) { continue; @@ -53,6 +52,7 @@ const useMediaBlocks = () => { } return { imageIds, videoIds, blocksByAttributeId }; }, [ mediaBlocks, featuredImageId ] ); + /* eslint-enable no-shadow */ const imageRecords = useEntityRecords( 'postType', 'attachment', { per_page: imageIds.length, include: imageIds, From d3bb600de5a89d85a2fd9869e04c8454b979df4e Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Mon, 27 Jan 2025 10:04:01 -0400 Subject: [PATCH 14/27] Move plugin PHP logic into namespace file --- altis-media-weight.php | 91 +--------------------------------------- inc/namespace.php | 95 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 89 deletions(-) create mode 100644 inc/namespace.php diff --git a/altis-media-weight.php b/altis-media-weight.php index c5a5dc5..bb664ce 100644 --- a/altis-media-weight.php +++ b/altis-media-weight.php @@ -16,93 +16,6 @@ exit; // Exit if accessed directly. } -/** - * Connect namespace functions to actions and hooks. - */ -function bootstrap() : void { - add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\\register_block_plugin_editor_scripts' ); - add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\\maybe_warn_on_script_debug_mode' ); -} -// Initialize plugin. -bootstrap(); - -/** - * Registers the block plugin script bundle. - */ -function register_block_plugin_editor_scripts() { - $asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php'); - - if ( $asset_file === false ) { - return; - } +require_once __DIR__ . '/inc/namespace.php'; - wp_enqueue_script( - 'hm-media-weight', - plugins_url( 'build/index.js', __FILE__ ), - $asset_file['dependencies'], - $asset_file['version'] - ); - - wp_localize_script( - 'hm-media-weight', - 'mediaWeightData', - [ - /** - * Filter the threshold at which a post is deemed "too heavy" due to media weight. - * - * @param float $threshold Maxmimum number of megabytes of media permitted per post. - */ - 'mediaThreshold' => apply_filters( 'hm_media_weight_threshold', 2.50 ), - /** - * Filter the expected maximum width (in pixels) for media displayed in post content. - */ - 'contentColumnWidth' => apply_filters( - 'hm_media_weight_content_column_width', - $GLOBALS['content_width'] ?? 1024 - ), - /** - * Filter the expected maximum width (in pixels) for a desktop featured image. - */ - 'featuredImageWidth' => apply_filters( - 'hm_media_weight_featured_image_width', - $GLOBALS['content_width'] ?? 1024 - ), - ] - ); -} - -/** - * Show a warning if SCRIPT_DEBUG is off while we're running the dev server. - */ -function maybe_warn_on_script_debug_mode() { - // Only render this notice in the post editor. - if ( ( get_current_screen()->base ?? '' ) !== 'post' ) { - return; - } - - $asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php'); - - if ( ! in_array( 'wp-react-refresh-runtime', $asset_file['dependencies'] ?? [], true ) ) { - // Either not in hot-reload mode, or plugin isn't currently built. - return; - } - - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { - // SCRIPT_DEBUG configured correctly. - return; - } - - ob_start(); - ?> - wp.domReady( () => { - wp.data.dispatch( 'core/notices' ).createNotice( - 'warning', - "", - { - isDismissible: false, - } - ); - } ); - apply_filters( 'hm_media_weight_threshold', 2.50 ), + /** + * Filter the expected maximum width (in pixels) for media displayed in post content. + */ + 'contentColumnWidth' => apply_filters( + 'hm_media_weight_content_column_width', + $GLOBALS['content_width'] ?? 1024 + ), + /** + * Filter the expected maximum width (in pixels) for a desktop featured image. + */ + 'featuredImageWidth' => apply_filters( + 'hm_media_weight_featured_image_width', + $GLOBALS['content_width'] ?? 1024 + ), + ] + ); +} + +/** + * Show a warning if SCRIPT_DEBUG is off while we're running the dev server. + */ +function maybe_warn_on_script_debug_mode() { + // Only render this notice in the post editor. + if ( ( get_current_screen()->base ?? '' ) !== 'post' ) { + return; + } + + $asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php'); + + if ( ! in_array( 'wp-react-refresh-runtime', $asset_file['dependencies'] ?? [], true ) ) { + // Either not in hot-reload mode, or plugin isn't currently built. + return; + } + + if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { + // SCRIPT_DEBUG configured correctly. + return; + } + + ob_start(); + ?> + wp.domReady( () => { + wp.data.dispatch( 'core/notices' ).createNotice( + 'warning', + "", + { + isDismissible: false, + } + ); + } ); + Date: Mon, 27 Jan 2025 12:05:19 -0400 Subject: [PATCH 15/27] Fix directory paths in namespace.php following code relocation --- inc/namespace.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/inc/namespace.php b/inc/namespace.php index 6fef491..0d424f8 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -11,13 +11,12 @@ function bootstrap() : void { add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\\register_block_plugin_editor_scripts' ); add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\\maybe_warn_on_script_debug_mode' ); -} /** * Registers the block plugin script bundle. */ function register_block_plugin_editor_scripts() { - $asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php'); + $asset_file = include( plugin_dir_path( __DIR__ ) . 'build/index.asset.php' ); if ( $asset_file === false ) { return; @@ -25,7 +24,7 @@ function register_block_plugin_editor_scripts() { wp_enqueue_script( 'hm-media-weight', - plugins_url( 'build/index.js', __FILE__ ), + plugins_url( 'build/index.js', __DIR__ ), $asset_file['dependencies'], $asset_file['version'] ); @@ -67,7 +66,7 @@ function maybe_warn_on_script_debug_mode() { return; } - $asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php'); + $asset_file = include( plugin_dir_path( __DIR__ ) . 'build/index.asset.php'); if ( ! in_array( 'wp-react-refresh-runtime', $asset_file['dependencies'] ?? [], true ) ) { // Either not in hot-reload mode, or plugin isn't currently built. From a545eb50fce5840f1b2662f15536ef2573f0ed23 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Mon, 27 Jan 2025 12:23:47 -0400 Subject: [PATCH 16/27] Always cachebust in development, and order SCRIPT_DEBUG checks more efficiently --- inc/namespace.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/inc/namespace.php b/inc/namespace.php index 0d424f8..d883da6 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -11,6 +11,7 @@ function bootstrap() : void { add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\\register_block_plugin_editor_scripts' ); add_action( 'enqueue_block_editor_assets', __NAMESPACE__ . '\\maybe_warn_on_script_debug_mode' ); +} /** * Registers the block plugin script bundle. @@ -26,7 +27,7 @@ function register_block_plugin_editor_scripts() { 'hm-media-weight', plugins_url( 'build/index.js', __DIR__ ), $asset_file['dependencies'], - $asset_file['version'] + ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? time() : $asset_file['version'] ); wp_localize_script( @@ -61,6 +62,15 @@ function register_block_plugin_editor_scripts() { * Show a warning if SCRIPT_DEBUG is off while we're running the dev server. */ function maybe_warn_on_script_debug_mode() { + if ( wp_get_environment_type() !== 'local' ) { + return; + } + + if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { + // SCRIPT_DEBUG configured correctly. + return; + } + // Only render this notice in the post editor. if ( ( get_current_screen()->base ?? '' ) !== 'post' ) { return; @@ -73,11 +83,6 @@ function maybe_warn_on_script_debug_mode() { return; } - if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) { - // SCRIPT_DEBUG configured correctly. - return; - } - ob_start(); ?> wp.domReady( () => { From 5a928ace3c4dde334de68d1c10e40213b174f2bb Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 28 Jan 2025 09:52:38 -0400 Subject: [PATCH 17/27] Move asset-related logic into Assets namespace --- altis-media-weight.php | 4 ++-- inc/{namespace.php => assets.php} | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) rename inc/{namespace.php => assets.php} (90%) diff --git a/altis-media-weight.php b/altis-media-weight.php index bb664ce..d4dc120 100644 --- a/altis-media-weight.php +++ b/altis-media-weight.php @@ -16,6 +16,6 @@ exit; // Exit if accessed directly. } -require_once __DIR__ . '/inc/namespace.php'; +require_once __DIR__ . '/inc/assets.php'; -bootstrap(); +Assets\bootstrap(); diff --git a/inc/namespace.php b/inc/assets.php similarity index 90% rename from inc/namespace.php rename to inc/assets.php index d883da6..28b9ac8 100644 --- a/inc/namespace.php +++ b/inc/assets.php @@ -1,9 +1,9 @@ Date: Tue, 28 Jan 2025 10:57:00 -0400 Subject: [PATCH 18/27] Reinstate main namespace.php file, we will need it later --- altis-media-weight.php | 2 ++ inc/namespace.php | 12 ++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 inc/namespace.php diff --git a/altis-media-weight.php b/altis-media-weight.php index d4dc120..69d9321 100644 --- a/altis-media-weight.php +++ b/altis-media-weight.php @@ -16,6 +16,8 @@ exit; // Exit if accessed directly. } +require_once __DIR__ . '/inc/namespace.php'; require_once __DIR__ . '/inc/assets.php'; +bootstrap(); Assets\bootstrap(); diff --git a/inc/namespace.php b/inc/namespace.php new file mode 100644 index 0000000..b1c190c --- /dev/null +++ b/inc/namespace.php @@ -0,0 +1,12 @@ + Date: Tue, 28 Jan 2025 17:34:00 -0500 Subject: [PATCH 19/27] Enable plugin to load runtime while running HMR DevServer --- inc/assets.php | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/inc/assets.php b/inc/assets.php index 28b9ac8..3b33952 100644 --- a/inc/assets.php +++ b/inc/assets.php @@ -3,6 +3,8 @@ * Asset-related functionality. */ +declare( strict_types=1 ); + namespace HM_Media_Weight\Assets; /** @@ -26,6 +28,15 @@ function register_block_plugin_editor_scripts() { $asset_uri = plugins_url( 'build/index.js', __DIR__ ); + // Check whether a runtime chunk exists, and inject it as a dependency if it does. + if ( includes_hmr_dependency( $asset_file['dependencies'] ) ) { + // Try to infer and depend upon our custom runtime chunk (see webpack.config.js). + $runtime_handle = detect_and_register_runtime_chunk( $asset_file_path, $asset_uri ); + if ( ! empty( $runtime_handle ) ) { + $asset_file['dependencies'][] = $runtime_handle; + } + } + wp_enqueue_script( 'hm-media-weight', $asset_uri, @@ -100,3 +111,91 @@ function maybe_warn_on_script_debug_mode() { Date: Tue, 28 Jan 2025 17:38:07 -0500 Subject: [PATCH 20/27] Update block editor sidebar to use "Scales" icon --- src/assets/scale-icon.svg | 1 + src/index.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 src/assets/scale-icon.svg diff --git a/src/assets/scale-icon.svg b/src/assets/scale-icon.svg new file mode 100644 index 0000000..b0055e4 --- /dev/null +++ b/src/assets/scale-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/index.js b/src/index.js index d29f21f..2950865 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,5 @@ import { useEffect, useMemo, useState } from 'react'; import { __, sprintf } from '@wordpress/i18n'; -import { media } from '@wordpress/icons'; import { PluginSidebar, PluginSidebarMoreMenuItem } from '@wordpress/editor'; import { PanelRow, PanelBody, Button } from '@wordpress/components'; import { registerPlugin, unregisterPlugin } from '@wordpress/plugins'; @@ -8,6 +7,8 @@ import { useDispatch, useSelect } from '@wordpress/data'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { useEntityRecords } from '@wordpress/core-data'; +import { ReactComponent as ScalesIcon } from './assets/scale-icon.svg'; + const { mediaThreshold } = window.mediaWeightData; const PLUGIN_NAME = 'hm-media-weight'; @@ -311,7 +312,7 @@ const HMMediaWeightSidebar = () => { }; registerPlugin( PLUGIN_NAME, { - icon: media, + icon: ScalesIcon, render: HMMediaWeightSidebar, } ); From 6b8e6090f03e06b12b953454a64f26397fdbcc9b Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 28 Jan 2025 17:47:00 -0500 Subject: [PATCH 21/27] Remove unused edit.js file --- src/edit.js | 41 ----------------------------------------- 1 file changed, 41 deletions(-) delete mode 100644 src/edit.js diff --git a/src/edit.js b/src/edit.js deleted file mode 100644 index a127175..0000000 --- a/src/edit.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Retrieves the translation of text. - * - * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/ - */ -import { __ } from '@wordpress/i18n'; - -/** - * 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/reference-guides/packages/packages-block-editor/#useblockprops - */ -import { useBlockProps } from '@wordpress/block-editor'; - -/** - * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files. - * Those files can contain any CSS code that gets applied to the editor. - * - * @see https://www.npmjs.com/package/@wordpress/scripts#using-css - */ -import './editor.scss'; - -/** - * 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/reference-guides/block-api/block-edit-save/#edit - * - * @return {Element} Element to render. - */ -export default function Edit() { - return ( -

- { __( - 'HM Media Weight – hello from the editor!', - 'hm-media-weight' - ) } -

- ); -} From 897ee97877d580bbff8641f847cccffb5a44852b Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 28 Jan 2025 18:43:32 -0500 Subject: [PATCH 22/27] Implement logic to store post intermediate sizes after creation --- inc/namespace.php | 125 ++++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 1 + 2 files changed, 126 insertions(+) diff --git a/inc/namespace.php b/inc/namespace.php index b1c190c..3917add 100644 --- a/inc/namespace.php +++ b/inc/namespace.php @@ -5,8 +5,133 @@ namespace HM_Media_Weight; +const ATTACHMENT_SIZE_CRON_ID = 'hm-media-weight-attachment-check'; +const ATTACHMENT_SIZE_META_KEY = 'intermediate_image_filesizes'; + /** * Connect namespace functions to actions and hooks. */ function bootstrap() : void { + add_action( 'init', __NAMESPACE__ . '\register_attachment_meta' ); + add_action( 'add_attachment', __NAMESPACE__ . '\\schedule_file_size_check' ); + add_action( ATTACHMENT_SIZE_CRON_ID, __NAMESPACE__ . '\\store_intermediate_file_sizes' ); + add_action( 'rest_prepare_attachment', __NAMESPACE__ . '\\lookup_file_sizes_as_needed_during_rest_response', 10, 3 ); +} + +/** + * Registers the _image_file_sizes meta field for the attachment post type. + */ +function register_attachment_meta() { + register_post_meta( 'attachment', ATTACHMENT_SIZE_META_KEY, [ + 'type' => 'object', + 'description' => 'File sizes for all intermediate image sizes of an attachment.', + 'single' => true, + 'show_in_rest' => [ + 'schema' => [ + 'type' => 'object', + 'additionalProperties' => [ + 'type' => 'integer', + ], + ], + ], + 'auth_callback' => function() { + return current_user_can( 'edit_posts' ); + }, + ] ); +} + +/** + * Schedules a cron job to check file sizes for the uploaded attachment. + * + * @param int $attachment_id The ID of the uploaded attachment. + */ +function schedule_file_size_check( $attachment_id ) { + if ( ! in_array( get_post_mime_type( $attachment_id ), [ 'image/jpeg', 'image/png', 'image/gif' ], true ) ) { + return; + } + + if ( ! wp_next_scheduled( ATTACHMENT_SIZE_CRON_ID, [ $attachment_id ] ) ) { + // Ensure the cron job is scheduled for later as fallback. + wp_schedule_single_event( time(), ATTACHMENT_SIZE_CRON_ID, [ $attachment_id ] ); + } +} + +/** + * Logs the file sizes for each image size of the uploaded attachment. + * + * @param int $attachment_id The ID of the uploaded attachment. + */ +function store_intermediate_file_sizes( $attachment_id ) { + $image_sizes = get_intermediate_image_sizes(); + $file_sizes = []; + + foreach ( $image_sizes as $size ) { + $image_url = wp_get_attachment_image_url( $attachment_id, $size ); + + if ( ! $image_url ) { + continue; + } + + $response = wp_remote_head( $image_url ); + + if ( is_wp_error( $response ) ) { + continue; + } + + $headers = wp_remote_retrieve_headers( $response ); + if ( isset( $headers['content-length'] ) ) { + $file_sizes[ $size ] = (int) $headers['content-length']; + } + } + + // Save the file sizes to the attachment meta. + update_post_meta( $attachment_id, ATTACHMENT_SIZE_META_KEY, $file_sizes ); +} + +/** + * Retrieves the file sizes for an attachment. + * + * @param int $attachment_id The ID of the attachment. + * @return array|null The array of file sizes keyed by URL, or null if not available. + */ +function get_file_sizes( $attachment_id ) { + return get_post_meta( $attachment_id, ATTACHMENT_SIZE_META_KEY, true ) ?: []; +} + +/** + * Conditionally request and fill in missing file size meta before fulfilling + * an edit-context REST request for an image. + * + * @param WP_REST_Response $response The response object. + * @param WP_Post $post The original attachment post. + * @param WP_REST_Request $request Request used to generate the response. + * @return WP_REST_Response The filtered response. + */ +function lookup_file_sizes_as_needed_during_rest_response( $response, $post, $request ) { + if ( $request->get_param( 'context' ) !== 'edit' ) { + return $response; + } + + if ( ! empty( $response->data['meta'][ ATTACHMENT_SIZE_META_KEY ] ?? null ) ) { + return $response; + } + + $meta_field_included = rest_is_field_included( + 'meta.' . ATTACHMENT_SIZE_META_KEY, + get_post_type_object( 'attachment' )->get_rest_controller()->get_fields_for_response( $request ) ?? [] + ); + if ( ! $meta_field_included ) { + return $response; + } + + // Update post meta and then append the stored values to the response. + store_intermediate_file_sizes( $post->ID ); + $response->data['meta'][ ATTACHMENT_SIZE_META_KEY ] = get_file_sizes( $post->ID ); + + // Ensure we consistently return object-shaped JSON, not `[]` for empty. + if ( empty( $response->data['meta'][ ATTACHMENT_SIZE_META_KEY ] ) && isset( $response->data['meta'][ ATTACHMENT_SIZE_META_KEY ] ) ) { + $response->data['meta'][ ATTACHMENT_SIZE_META_KEY ] = (object) []; + } + + return $response; } diff --git a/src/index.js b/src/index.js index 2950865..060264f 100644 --- a/src/index.js +++ b/src/index.js @@ -58,6 +58,7 @@ const useMediaBlocks = () => { per_page: imageIds.length, include: imageIds, } )?.records || []; + console.log( imageRecords.reduce( ( memo, { id, meta } ) => ( { ...memo, [ id ]: meta } ), {} ) ); const videoRecords = useEntityRecords( 'postType', 'attachment', { per_page: videoIds.length, include: videoIds, From ef3e176ebf6dafe0fc193f31261c1faef1c43f1c Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 28 Jan 2025 19:01:27 -0500 Subject: [PATCH 23/27] Remove JS-side size determination in favor of using post meta via API --- src/index.js | 93 +++++----------------------------------------------- 1 file changed, 8 insertions(+), 85 deletions(-) diff --git a/src/index.js b/src/index.js index 060264f..6844225 100644 --- a/src/index.js +++ b/src/index.js @@ -32,6 +32,7 @@ const getMediaBlocks = ( blocks ) => blocks.reduce( const useMediaBlocks = () => { const mediaBlocks = useSelect( ( select ) => getMediaBlocks( select( blockEditorStore ).getBlocks() ) ); const featuredImageId = useSelect( ( select ) => select( 'core/editor' ).getEditedPostAttribute( 'featured_media' ) ); + /* eslint-disable no-shadow */ const { imageIds, videoIds, blocksByAttributeId } = useMemo( () => { const imageIds = []; @@ -58,11 +59,11 @@ const useMediaBlocks = () => { per_page: imageIds.length, include: imageIds, } )?.records || []; - console.log( imageRecords.reduce( ( memo, { id, meta } ) => ( { ...memo, [ id ]: meta } ), {} ) ); const videoRecords = useEntityRecords( 'postType', 'attachment', { per_page: videoIds.length, include: videoIds, } )?.records || []; + return { attachments: imageRecords.concat( videoRecords ), featuredImageId, @@ -73,48 +74,6 @@ const useMediaBlocks = () => { }; }; -/** - * Use a HEAD request to measure the size of a remote file (in bytes). - * - * This is necessary because WordPress doesn't store the filesizes of - * dynamic (Tachyon/Photon) images in the database. - * - * @async - * @param {string} imageUri URL for a remote image. - * @return {Promise} The size of the remote image, in bytes. - */ -async function getFileSize( imageUri ) { - const response = await fetch( imageUri, { method: 'HEAD' } ); - - if ( ! response.ok ) { - throw new Error( `Failed to fetch: ${ response.status }` ); - } - - const contentLength = response.headers.get( 'Content-Length' ); - if ( ! contentLength ) { - throw new Error( 'Content-Length header not found.' ); - } - - return parseInt( contentLength, 10 ); -} - -const imageSizesByUri = {}; - -/** - * Cached wrapper for getFileSize, to avoid repeatedly checking the same image. - * - * @async - * @param {string} imageUri URL for a remote image. - * @return {number|Promise} The size of the remote image, in bytes. - */ -async function checkImageSize( imageUri ) { - if ( ! imageSizesByUri[ imageUri ] ) { - imageSizesByUri[ imageUri ] = getFileSize( imageUri ); - } - - return imageSizesByUri[ imageUri ]; -} - const HMMediaWeightSidebar = () => { const { attachments, @@ -128,44 +87,6 @@ const HMMediaWeightSidebar = () => { let imagesSize = 0; let videosSize = 0; - const [ resolvedRemoteImageSizes, setResolvedImageSizes ] = useState( {} ); - let targetImageURIs = attachments - .map( ( attachment ) => { - if ( attachment.media_type !== 'image' ) { - return null; - } - if ( attachment.id === featuredImageId ) { - // TODO: Understand via filters the expected size of a featured image, - // and report the URI of the expected target file for measurement. - return null; - } - const associatedBlockClientId = blocksByAttributeId[ attachment.id ]; - const associatedBlock = mediaBlocks.find( ( block ) => block.clientId === associatedBlockClientId ); - const imageUri = attachment?.media_details?.sizes?.[ associatedBlock?.attributes?.sizeSlug ]?.source_url || null; - if ( ! imageUri ) { - return null; - } - return [ attachment.id, imageUri ]; - } ) - .filter( Boolean ); - - // Create a stable reference for triggering useEffect. - targetImageURIs = JSON.stringify( targetImageURIs ); - - useEffect( () => { - const imageSizeRequests = JSON.parse( targetImageURIs ) - .map( ( [ id, uri ] ) => { - return checkImageSize( uri ).then( ( size ) => [ id, size ] ); - } ); - Promise.all( imageSizeRequests ).then( ( sizes ) => { - const resolvedSizes = sizes.reduce( ( memo, [ id, size ] ) => { - memo[ id ] = size; - return memo; - }, {} ); - setResolvedImageSizes( resolvedSizes ); - } ); - }, [ targetImageURIs ] ); - // eslint-disable-next-line no-shadow const DisplayTotal = ( { imagesSize, videosSize } ) => { const total = ( ( imagesSize + videosSize ) / MB_IN_B ).toFixed( 2 ); @@ -232,10 +153,11 @@ const HMMediaWeightSidebar = () => { title={ __( 'Individual Media Items', 'hm-media-weight' ) } > { attachments.map( ( attachment ) => { + const associatedBlockClientId = blocksByAttributeId[ attachment.id ]; const blockButton = attachment.id !== featuredImageId ? ( ) : ''; @@ -247,9 +169,10 @@ const HMMediaWeightSidebar = () => { let mediaSize = attachment.media_details.filesize; if ( attachment.media_type === 'image' ) { - const remoteImageSize = resolvedRemoteImageSizes[ attachment.id ]; - mediaSize = remoteImageSize || mediaSize; - imagesSize = imagesSize + ( remoteImageSize || mediaSize ); + const requestedSize = mediaBlocks.find( ( block ) => block.clientId === associatedBlockClientId )?.attributes?.sizeSlug; + // Swap in the actual measured size of the target image, if available. + mediaSize = attachment.meta?.intermediate_image_filesizes?.[ requestedSize ] || mediaSize; + imagesSize = imagesSize + mediaSize; } else { videosSize = videosSize + mediaSize; } From d8f24755f1fbdf66de0f917a580769293a470933 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 28 Jan 2025 19:15:18 -0500 Subject: [PATCH 24/27] Correctly report expected requested size for featured image Consuming application may filter to set the expected slug / crop --- inc/assets.php | 12 +----------- src/index.js | 6 ++++-- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/inc/assets.php b/inc/assets.php index 3b33952..3c8f9c1 100644 --- a/inc/assets.php +++ b/inc/assets.php @@ -54,20 +54,10 @@ function register_block_plugin_editor_scripts() { * @param float $threshold Maxmimum number of megabytes of media permitted per post. */ 'mediaThreshold' => apply_filters( 'hm_media_weight_threshold', 2.50 ), - /** - * Filter the expected maximum width (in pixels) for media displayed in post content. - */ - 'contentColumnWidth' => apply_filters( - 'hm_media_weight_content_column_width', - $GLOBALS['content_width'] ?? 1024 - ), /** * Filter the expected maximum width (in pixels) for a desktop featured image. */ - 'featuredImageWidth' => apply_filters( - 'hm_media_weight_featured_image_width', - $GLOBALS['content_width'] ?? 1024 - ), + 'featuredImageSize' => apply_filters( 'hm_media_weight_featured_image_size_slug', 'large' ), ] ); } diff --git a/src/index.js b/src/index.js index 6844225..d868075 100644 --- a/src/index.js +++ b/src/index.js @@ -9,7 +9,7 @@ import { useEntityRecords } from '@wordpress/core-data'; import { ReactComponent as ScalesIcon } from './assets/scale-icon.svg'; -const { mediaThreshold } = window.mediaWeightData; +const { mediaThreshold, featuredImageSize } = window.mediaWeightData; const PLUGIN_NAME = 'hm-media-weight'; const SIDEBAR_NAME = PLUGIN_NAME; @@ -169,7 +169,9 @@ const HMMediaWeightSidebar = () => { let mediaSize = attachment.media_details.filesize; if ( attachment.media_type === 'image' ) { - const requestedSize = mediaBlocks.find( ( block ) => block.clientId === associatedBlockClientId )?.attributes?.sizeSlug; + const requestedSize = attachment.id !== featuredImageId + ? mediaBlocks.find( ( block ) => block.clientId === associatedBlockClientId )?.attributes?.sizeSlug + : ( featuredImageSize || 'full' ); // Swap in the actual measured size of the target image, if available. mediaSize = attachment.meta?.intermediate_image_filesizes?.[ requestedSize ] || mediaSize; imagesSize = imagesSize + mediaSize; From 359e56e1cfbad3434013584fd6c68a28fd0870e4 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 28 Jan 2025 19:16:43 -0500 Subject: [PATCH 25/27] Correct calculation of Megabyte sizing --- src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index d868075..e7fca54 100644 --- a/src/index.js +++ b/src/index.js @@ -13,8 +13,8 @@ const { mediaThreshold, featuredImageSize } = window.mediaWeightData; const PLUGIN_NAME = 'hm-media-weight'; const SIDEBAR_NAME = PLUGIN_NAME; -const MB_IN_B = 1000000; -const KB_IN_B = 1000; +const MB_IN_B = 1024 * 1024; +const KB_IN_B = 1024; const getMediaBlocks = ( blocks ) => blocks.reduce( ( mediaBlocks, block ) => { From 10f977970f61e84fe244acdc8f918487f97bc576 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 28 Jan 2025 19:18:24 -0500 Subject: [PATCH 26/27] Remove unused JS imports --- src/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index e7fca54..e17a0a7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useMemo } from 'react'; import { __, sprintf } from '@wordpress/i18n'; import { PluginSidebar, PluginSidebarMoreMenuItem } from '@wordpress/editor'; import { PanelRow, PanelBody, Button } from '@wordpress/components'; @@ -55,6 +55,7 @@ const useMediaBlocks = () => { return { imageIds, videoIds, blocksByAttributeId }; }, [ mediaBlocks, featuredImageId ] ); /* eslint-enable no-shadow */ + const imageRecords = useEntityRecords( 'postType', 'attachment', { per_page: imageIds.length, include: imageIds, From 612bd718214db0b678659d88b712873c2f5c7a09 Mon Sep 17 00:00:00 2001 From: "K. Adam White" Date: Tue, 28 Jan 2025 19:29:27 -0500 Subject: [PATCH 27/27] Move size report into first panel row to consolidate summary info --- src/index.js | 90 ++++++++++++++++++++++++++++------------------------ 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/src/index.js b/src/index.js index e17a0a7..516c30d 100644 --- a/src/index.js +++ b/src/index.js @@ -113,8 +113,6 @@ const HMMediaWeightSidebar = () => { return ( <> -

{ __( 'Images total', 'hm-media-weight' ) }: { ( imagesSize / MB_IN_B ).toFixed( 2 ) }mb

-

{ __( 'Videos total', 'hm-media-weight' ) }: { ( videosSize / MB_IN_B ).toFixed( 2 ) }mb

{ __( 'Total media size', 'hm-media-weight' ) }: { ' ' } @@ -130,11 +128,53 @@ const HMMediaWeightSidebar = () => {

+

{ __( 'Images total', 'hm-media-weight' ) }: { ( imagesSize / MB_IN_B ).toFixed( 2 ) }mb

+

{ __( 'Videos total', 'hm-media-weight' ) }: { ( videosSize / MB_IN_B ).toFixed( 2 ) }mb

{ warningMsg } ); } + const attachmentSizeDetails = attachments.map( ( attachment ) => { + const associatedBlockClientId = blocksByAttributeId[ attachment.id ]; + const blockButton = attachment.id !== featuredImageId ? ( + ) : ''; + + let type = attachment.media_type === 'image' ? __( 'Image', 'hm-media-weight' ) : __( 'Video', 'hm-media-weight' ); + if ( attachment.id === featuredImageId ) { + type = __( 'Featured image', 'hm-media-weight' ); + } + let mediaSize = attachment.media_details.filesize; + + if ( attachment.media_type === 'image' ) { + const requestedSize = attachment.id !== featuredImageId + ? mediaBlocks.find( ( block ) => block.clientId === associatedBlockClientId )?.attributes?.sizeSlug + : ( featuredImageSize || 'full' ); + // Swap in the actual measured size of the target image, if available. + mediaSize = attachment.meta?.intermediate_image_filesizes?.[ requestedSize ] || mediaSize; + imagesSize = imagesSize + mediaSize; + } else { + videosSize = videosSize + mediaSize; + } + + const thumbnail = attachment.media_type === 'image' + ? ( attachment?.media_details?.sizes?.thumbnail?.source_url || attachment.source_url ) + : null; + + return { + attachment, + thumbnail, + type, + mediaSize, + blockButton + }; + } ); + return ( <> @@ -147,42 +187,18 @@ const HMMediaWeightSidebar = () => { >

Images: { imageCount }

Videos: { videoCount }

+ +
- { attachments.map( ( attachment ) => { - const associatedBlockClientId = blocksByAttributeId[ attachment.id ]; - const blockButton = attachment.id !== featuredImageId ? ( - ) : ''; - - let type = attachment.media_type === 'image' ? __( 'Image', 'hm-media-weight' ) : __( 'Video', 'hm-media-weight' ); - if ( attachment.id === featuredImageId ) { - type = __( 'Featured image', 'hm-media-weight' ); - } - let mediaSize = attachment.media_details.filesize; - - if ( attachment.media_type === 'image' ) { - const requestedSize = attachment.id !== featuredImageId - ? mediaBlocks.find( ( block ) => block.clientId === associatedBlockClientId )?.attributes?.sizeSlug - : ( featuredImageSize || 'full' ); - // Swap in the actual measured size of the target image, if available. - mediaSize = attachment.meta?.intermediate_image_filesizes?.[ requestedSize ] || mediaSize; - imagesSize = imagesSize + mediaSize; - } else { - videosSize = videosSize + mediaSize; - } - - const thumbnail = attachment.media_type === 'image' - ? ( attachment?.media_details?.sizes?.thumbnail?.source_url || attachment.source_url ) - : null; + { attachmentSizeDetails.map( ( { attachment, thumbnail, type, mediaSize, blockButton } ) => { return ( @@ -223,16 +239,6 @@ const HMMediaWeightSidebar = () => { ); } ) } - - - -
);