Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show the actual filesize of the selected image size slug, if known #5

Open
wants to merge 28 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e95ef25
Streamline generation of mediaBlocks
kadamwhite Jan 24, 2025
e99dac0
Store associated block clientId as a reusable value when looping thro…
kadamwhite Jan 24, 2025
505deef
Expose filterable content weight parameter
kadamwhite Jan 24, 2025
9cb1264
Attempt to read remote images and actually probably DDOS local Tachyon
kadamwhite Jan 24, 2025
e8f5922
Fix infinite request loop by serializing array to process as JSON string
kadamwhite Jan 24, 2025
bdbaa98
Set debugging attachment JSON display to "display: none"
kadamwhite Jan 24, 2025
e861ce1
Show Total Items panel by default, it is not very busy or distracting…
kadamwhite Jan 24, 2025
043eefd
Featured image WIP
kadamwhite Jan 24, 2025
fc37c6d
Merge branch 'main' into intelligent-size-selection
kadamwhite Jan 24, 2025
c289d7d
Temporarily disable PHP sniff, we do not have PHPCS installed
kadamwhite Jan 24, 2025
6cf2bc9
Customize ESLint rules
kadamwhite Jan 24, 2025
587d861
Disable "extraneous dependencies" warning, we use React through exter…
kadamwhite Jan 24, 2025
db061bc
Address ESLint issues flagged in plugin code
kadamwhite Jan 24, 2025
493c38b
ESLint: Alter how we try to disable no-shadow in reducers
kadamwhite Jan 24, 2025
a545eb5
Always cachebust in development, and order SCRIPT_DEBUG checks more e…
kadamwhite Jan 27, 2025
d3bb600
Move plugin PHP logic into namespace file
kadamwhite Jan 27, 2025
59481f9
Fix directory paths in namespace.php following code relocation
kadamwhite Jan 27, 2025
5a928ac
Move asset-related logic into Assets namespace
kadamwhite Jan 28, 2025
f075879
Reinstate main namespace.php file, we will need it later
kadamwhite Jan 28, 2025
f929746
Enable plugin to load runtime while running HMR DevServer
kadamwhite Jan 28, 2025
08e5789
Update block editor sidebar to use "Scales" icon
kadamwhite Jan 28, 2025
6b8e609
Remove unused edit.js file
kadamwhite Jan 28, 2025
897ee97
Implement logic to store post intermediate sizes after creation
kadamwhite Jan 28, 2025
ef3e176
Remove JS-side size determination in favor of using post meta via API
kadamwhite Jan 29, 2025
d8f2475
Correctly report expected requested size for featured image
kadamwhite Jan 29, 2025
359e56e
Correct calculation of Megabyte sizing
kadamwhite Jan 29, 2025
10f9779
Remove unused JS imports
kadamwhite Jan 29, 2025
612bd71
Move size report into first panel row to consolidate summary info
kadamwhite Jan 29, 2025
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
13 changes: 13 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": [ "plugin:@wordpress/eslint-plugin/recommended" ],
"env": {
"browser": true
},
"rules": {
"import/no-extraneous-dependencies": [ "off" ],
"jsdoc/no-undefined-types": [ "off" ],
"jsdoc/check-line-alignment": [ "warn" ],
"jsdoc/check-tag-names": [ "warn" ],
"prettier/prettier": [ "off" ]
}
}
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
21 changes: 20 additions & 1 deletion altis-media-weight.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,26 @@ 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
),
/**
* 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
),
]
);
}
Expand Down
132 changes: 118 additions & 14 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;

kadamwhite marked this conversation as resolved.
Show resolved Hide resolved
const getMediaBlocks = ( blocks ) => blocks.reduce(
( mediaBlocks, block ) => {
Expand All @@ -27,14 +29,14 @@ 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' ) );
/* eslint-disable no-shadow */
const { imageIds, videoIds, blocksByAttributeId } = useMemo( () => {
const mediaBlocks = getMediaBlocks( blocks );
const imageIds = [];
const videoIds = [];
const blocksByAttributeId = {};
for ( let block of mediaBlocks ) {
for ( const block of mediaBlocks ) {
if ( ! block.attributes?.id ) {
continue;
}
Expand All @@ -49,7 +51,8 @@ const useMediaBlocks = () => {
imageIds.push( featuredImageId );
}
return { imageIds, videoIds, blocksByAttributeId };
}, [ blocks, featuredImageId ] );
}, [ mediaBlocks, featuredImageId ] );
/* eslint-enable no-shadow */
const imageRecords = useEntityRecords( 'postType', 'attachment', {
per_page: imageIds.length,
include: imageIds,
Expand All @@ -62,25 +65,108 @@ const useMediaBlocks = () => {
attachments: imageRecords.concat( videoRecords ),
featuredImageId,
blocksByAttributeId,
mediaBlocks,
imageCount: imageIds.length,
videoCount: videoIds.length,
};
};

const HMMediaWeightSidebar = ( ...args ) => {
/**
* 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<number>} 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<number>} 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,
featuredImageId,
blocksByAttributeId,
mediaBlocks,
imageCount,
videoCount
} = useMediaBlocks();
const { selectBlock } = useDispatch( blockEditorStore );
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 ).toFixed( 2 );
const total = ( ( imagesSize + videosSize ) / MB_IN_B ).toFixed( 2 );
let sizeColor;

if ( total >= 0 && total <= ( mediaThreshold / 2 ) ) {
Expand All @@ -94,6 +180,7 @@ const HMMediaWeightSidebar = ( ...args ) => {
const warningMsg = total >= mediaThreshold ? (
<p className="description">
{ 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
) }
Expand All @@ -102,8 +189,8 @@ const HMMediaWeightSidebar = ( ...args ) => {

return (
<>
<p>{ __( 'Images total', 'hm-media-weight' ) }: { imagesSize.toFixed( 2 ) }mb</p>
<p>{ __( 'Videos total', 'hm-media-weight' ) }: { videosSize.toFixed( 2 ) }mb</p>
<p>{ __( 'Images total', 'hm-media-weight' ) }: { ( imagesSize / MB_IN_B ).toFixed( 2 ) }mb</p>
<p>{ __( 'Videos total', 'hm-media-weight' ) }: { ( videosSize / MB_IN_B ).toFixed( 2 ) }mb</p>
<p>
<strong>
{ __( 'Total media size', 'hm-media-weight' ) }: { ' ' }
Expand Down Expand Up @@ -131,7 +218,7 @@ const HMMediaWeightSidebar = ( ...args ) => {
</PluginSidebarMoreMenuItem>
<PluginSidebar className={ SIDEBAR_NAME } name={ SIDEBAR_NAME } title={ __( 'Media Weight', 'hm-media-weight' ) }>
<PanelBody
initialOpen={ false }
initialOpen={ true }
title={ __( 'Total Media Items', 'hm-media-weight' ) }
>
<p>Images: { imageCount }</p>
Expand All @@ -155,27 +242,44 @@ 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 ];
mediaSize = remoteImageSize || mediaSize;
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 (
<PanelRow key={ `media-details-${ attachment.id }` }>
<div>
{ thumbnail ? (
<img
src={ thumbnail }
alt=""
style={ { maxWidth: '100%' } }
/>
) : null }
<p>
<strong>
{ type }: { mediaSize.toFixed( 2 ) }mb
{ type }: {
( mediaSize < MB_IN_B )
? `${ ( mediaSize / KB_IN_B ).toFixed( 2 ) }kb`
: `${ ( mediaSize / MB_IN_B ).toFixed( 2 ) }mb`
}
</strong>
</p>
<p>
Attachment ID: { attachment.id }<br />
<small><a href={ attachment.link }>Go to the attachment post &rsaquo;</a></small>
</p>
<details style={ { margin: '0.5rem 0 1rem' } }>
<details style={ { display: 'none', margin: '0.5rem 0 1rem' } }>
<summary>{ __( 'View entity record JSON', 'hm-media-weight' ) }</summary>
<small>
<pre>
Expand Down
Loading