Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ build
AGENTS.local.md

*.tsbuildinfo

.env
.env.*
7 changes: 7 additions & 0 deletions asset-optimization/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,10 @@ If you encounter any issues or have questions about the plugin, please [open an
## License

MIT

## Changelog

- 0.7.9 - Hide the **Media Area** button on optimized rows during preview runs. In dry-run mode the asset hasn't been written back to the media library, so jumping into it would just show the original and reinforce the misconception that the optimization already ran. The button still appears for skipped/failed entries and for real (non-preview) optimization runs.
- 0.7.8 - Two preview-mode fixes:
- Decimal megabyte thresholds (e.g. `0.1` MB) no longer break the asset listing. The plugin converts MB to bytes (multiplying by `1024 * 1024`) and the result was being passed to the CMA `size` filter as a float, which the API rejected with `INVALID_FILTER_FIELDS_PARAM` ("Could not coerce value `0.10485760` to IntType"). The threshold is now floored to a whole number of bytes before the request goes out.
- The "View Asset" button in the **Optimized Assets** panel after a preview run was opening the unmodified original (because preview runs never replace anything), making editors think the optimization didn't apply. The dry-run pipeline now keeps hold of the Imgix URL it generated for the preview, and the button switches to "View optimized preview" + opens that URL when the panel is showing dry-run results.
11 changes: 9 additions & 2 deletions asset-optimization/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion asset-optimization/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "datocms-plugin-asset-optimization",
"homepage": "https://github.com/datocms/plugins/tree/master/asset-optimization#readme",
"version": "0.7.7",
"version": "0.7.9",
"author": "DatoCMS <support@datocms.com>",
"description": "A plugin that allows you to mass apply optimizations to your DatoCMS assets.",
"keywords": [
Expand Down
75 changes: 59 additions & 16 deletions asset-optimization/src/components/asset-optimization/AssetList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ import type {
interface AssetListProps {
assets: Asset[] | OptimizedAssetType[] | ProcessedAsset[];
category: 'optimized' | 'skipped' | 'failed';
/**
* True when the parent page is showing results from a "Preview Optimization"
* (dry-run) pass — no assets were modified and the original asset URL still
* points at the unmodified bytes. The "View Asset" button switches to the
* Imgix preview URL in this mode so editors can actually compare the
* predicted optimized output instead of being silently routed back to the
* original.
*/
isPreview?: boolean;
onClose?: () => void;
ctx: RenderPageCtx;
}
Expand All @@ -20,6 +29,7 @@ interface AssetListProps {
interface DisplayAsset extends Asset {
originalSize?: number;
optimizedSize?: number;
optimizedUrl?: string;
}

const getCategoryBadgeClass = (
Expand All @@ -44,6 +54,8 @@ const normalizeToDisplayAsset = (
'originalSize' in asset ? (asset.originalSize as number) : undefined;
const optimizedSize =
'optimizedSize' in asset ? (asset.optimizedSize as number) : undefined;
const optimizedUrl =
'optimizedUrl' in asset ? (asset.optimizedUrl as string | undefined) : undefined;
const size = 'size' in asset ? asset.size : (originalSize ?? 0);
const basename = 'basename' in asset ? asset.basename : '';

Expand All @@ -56,6 +68,7 @@ const normalizeToDisplayAsset = (
basename,
originalSize,
optimizedSize,
optimizedUrl,
};
};

Expand All @@ -72,12 +85,14 @@ const computeSavingsPercentage = (
type AssetListItemProps = {
asset: Asset | OptimizedAssetType | ProcessedAsset;
category: 'optimized' | 'skipped' | 'failed';
isPreview: boolean;
ctx: RenderPageCtx;
};

const AssetListItem = ({
asset,
category,
isPreview,
ctx,
}: AssetListItemProps): ReactElement => {
const displayAsset = normalizeToDisplayAsset(asset);
Expand All @@ -91,6 +106,20 @@ const AssetListItem = ({
Boolean(displayAsset.originalSize) &&
Boolean(displayAsset.optimizedSize);

// In preview mode the asset URL is still the unmodified original — opening
// it from the "View Asset" button just shows the pre-optimization image and
// confused editors into thinking the optimization didn't apply. Prefer the
// dry-run Imgix URL when we have one so the button actually surfaces the
// predicted output.
const showOptimizedPreview =
isPreview && category === 'optimized' && Boolean(displayAsset.optimizedUrl);
const viewAssetLabel = showOptimizedPreview
? 'View optimized preview'
: 'View Asset';
const viewAssetUrl = showOptimizedPreview
? (displayAsset.optimizedUrl as string)
: displayAsset.url;

return (
<li key={displayAsset.id} className={s.assetListItem}>
<div className={s.assetItemContent}>
Expand Down Expand Up @@ -128,23 +157,33 @@ const AssetListItem = ({
<Button
buttonSize="xxs"
buttonType="primary"
onClick={() => window.open(displayAsset.url, '_blank')}
>
View Asset
</Button>
<Button
buttonSize="xxs"
buttonType="muted"
style={{ marginLeft: '8px' }}
onClick={() =>
window.open(
`https://${ctx.site.attributes.internal_domain}/environments/${ctx.environment}/media/assets/${displayAsset.id}`,
'_blank',
)
}
onClick={() => window.open(viewAssetUrl, '_blank')}
>
Media Area
{viewAssetLabel}
</Button>
{/*
* The Media Area link is hidden in preview mode for optimized
* entries: nothing has been written back to the media library yet,
* so jumping into it would just show the original asset and
* reinforce the misconception that the optimization already ran.
* It still appears for skipped/failed entries (where the media
* library still has useful context) and for non-preview runs.
*/}
{!showOptimizedPreview && (
<Button
buttonSize="xxs"
buttonType="muted"
style={{ marginLeft: '8px' }}
onClick={() =>
window.open(
`https://${ctx.site.attributes.internal_domain}/environments/${ctx.environment}/media/assets/${displayAsset.id}`,
'_blank',
)
}
>
Media Area
</Button>
)}
</div>
</div>
</li>
Expand All @@ -155,11 +194,14 @@ const AssetListItem = ({
* AssetList component displays categorized lists of assets (optimized, skipped, or failed)
*
* This component displays a list of assets based on the selected category with action buttons:
* - "View Asset" button - Opens the asset in a new tab
* - "View Asset" / "View optimized preview" button - opens the asset (or, in
* preview mode for optimized entries, the dry-run Imgix URL) in a new tab
* - "Media Area" button - jumps to the asset in the DatoCMS media library
*/
const AssetList = ({
assets,
category,
isPreview = false,
onClose,
ctx,
}: AssetListProps): ReactElement => {
Expand Down Expand Up @@ -193,6 +235,7 @@ const AssetList = ({
key={asset.id}
asset={asset}
category={category}
isPreview={isPreview}
ctx={ctx}
/>
))}
Expand Down
25 changes: 22 additions & 3 deletions asset-optimization/src/entrypoints/OptimizeAssetsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,13 @@ function assetToOptimizedAsset(
asset: Asset,
originalSize: number,
optimizedSize: number,
optimizedUrl?: string,
): OptimizedAsset {
return {
id: asset.id,
path: asset.path,
url: asset.url,
optimizedUrl,
originalSize,
optimizedSize,
};
Expand Down Expand Up @@ -112,6 +114,7 @@ async function processAsset(
status: 'optimized' | 'skipped' | 'failed';
asset: Asset;
optimizedSize?: number;
optimizedUrl?: string;
error?: string;
}> {
try {
Expand Down Expand Up @@ -152,13 +155,17 @@ async function processAsset(
return { status: 'skipped', asset };
}

// If this is just a preview, don't actually replace the asset
// If this is just a preview, don't actually replace the asset. Return the
// Imgix URL so the UI can let the user inspect the predicted optimized
// version directly — without a real preview URL the "View Asset" button
// would only ever open the still-unmodified original.
if (isPreview) {
addSizeComparisonLog(asset.path, asset.size, optimizedImageBlob.size);
return {
status: 'optimized',
asset,
optimizedSize: optimizedImageBlob.size,
optimizedUrl,
};
}

Expand Down Expand Up @@ -232,11 +239,17 @@ async function fetchOptimizableAssets(
const assets: Asset[] = [];
let count = 0;

// The CMA `size` filter expects an integer (number of bytes); a fractional
// megabyte threshold like 0.1 MB → 104857.6 bytes makes the API reject the
// query with `INVALID_FILTER_FIELDS_PARAM` ("Could not coerce value 0.10485760
// to IntType"). Floor here so users can keep entering decimal MB values.
const sizeThresholdBytes = Math.floor(largeAssetThresholdBytes);

for await (const upload of client.uploads.listPagedIterator({
filter: {
fields: {
type: { eq: 'image' },
size: { gte: largeAssetThresholdBytes },
size: { gte: sizeThresholdBytes },
},
},
})) {
Expand Down Expand Up @@ -302,7 +315,12 @@ async function processPageQueueTask({

if (result.status === 'optimized' && result.optimizedSize) {
acc.optimizedAssets.push(
assetToOptimizedAsset(asset, asset.size, result.optimizedSize),
assetToOptimizedAsset(
asset,
asset.size,
result.optimizedSize,
result.optimizedUrl,
),
);
acc.optimizedSizeTotal += result.optimizedSize;
acc.optimized++;
Expand Down Expand Up @@ -761,6 +779,7 @@ const OptimizeAssetsPage = ({ ctx }: Props) => {
: result.failedAssets
}
category={selectedCategory}
isPreview={isPreviewing}
onClose={() => setSelectedCategory(null)}
ctx={ctx}
/>
Expand Down
6 changes: 6 additions & 0 deletions asset-optimization/src/utils/optimizationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ export interface OptimizedAsset {
id: string;
path: string;
url: string;
/**
* Imgix URL with the optimization parameters applied. Populated only on
* preview runs so the UI can offer a "view what this would look like" link
* without dereferencing the original (still-unmodified) asset URL.
*/
optimizedUrl?: string;
originalSize: number;
optimizedSize: number;
}
Expand Down
6 changes: 5 additions & 1 deletion field-anchor-menu/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# Scroll to Field

A simple plugin that displays a menu on the sidebar with anchor links
to all fields in your record. Click on the link to scroll to the selected field.
to all fields in your record. Click on the link to scroll to the selected field.

## Changelog

- 0.1.14 - Fixed the "minimum number of fields" plugin setting silently reverting to the default of 5 every time. The config screen used a `TextField`, which submitted the value as a string; the runtime then failed `typeof value === 'number'` and `normalizeGlobalParams` reset it. The field now coerces the value to an integer on submit, validates that it is at least 1, and renders as a numeric input so the browser surfaces the constraint up front.
Loading