+
\ No newline at end of file
diff --git a/resources/public/images/transforms/fit.svg b/resources/public/images/transforms/fit.svg
new file mode 100644
index 00000000000..6efbcf45d81
--- /dev/null
+++ b/resources/public/images/transforms/fit.svg
@@ -0,0 +1,35 @@
+
+
\ No newline at end of file
diff --git a/resources/public/images/transforms/letterbox.svg b/resources/public/images/transforms/letterbox.svg
new file mode 100644
index 00000000000..2abefaf7fdb
--- /dev/null
+++ b/resources/public/images/transforms/letterbox.svg
@@ -0,0 +1,36 @@
+
+
\ No newline at end of file
diff --git a/resources/public/images/transforms/stretch.svg b/resources/public/images/transforms/stretch.svg
new file mode 100644
index 00000000000..fcb21cf0be6
--- /dev/null
+++ b/resources/public/images/transforms/stretch.svg
@@ -0,0 +1,37 @@
+
+
\ No newline at end of file
diff --git a/resources/templates/settings/assets/transforms/_index.twig b/resources/templates/settings/assets/transforms/_index.twig
deleted file mode 100644
index 9b286632a35..00000000000
--- a/resources/templates/settings/assets/transforms/_index.twig
+++ /dev/null
@@ -1,62 +0,0 @@
-{% extends "settings/assets/_layout" %}
-{% set selectedNavItem = 'transforms' %}
-{% set readOnly = readOnly ?? false %}
-
-{% do registerLegacyAsset('CraftCms\\Cms\\View\\LegacyAssets\\AdminTableAsset') -%}
-
-
-{% if readOnly %}
- {% set contentNotice = readOnlyNotice() %}
-{% endif %}
-
-{% block content %}
-
-
- {% if not readOnly %}
-
- {% endif %}
-{% endblock %}
-
-{% set tableData = [] %}
-{% for transform in transforms %}
- {% if transform.mode %}
- {% set mode = modes[transform.mode] %}
- {% endif %}
-
- {% set tableData = tableData|merge([{
- id: transform.id,
- title: transform.name|t('site'),
- url: url('settings/assets/transforms/' ~ transform.handle),
- handle: transform.handle,
- mode: mode ?? null,
- dimensions: (transform.width ? transform.width : 'Auto'|t('app')|e) ~ " × " ~ (transform.height ? transform.height : 'Auto'|t('app')|e),
- interlace: transform.interlace ? transform.interlace|capitalize : 'None'|t('app')|e,
- format: transform.format ? transform.format|capitalize : 'Auto'|t('app')|e,
- }]) %}
-{% endfor %}
-
-{% js %}
-var columns = [
- { name: '__slot:title', title: Craft.t('app', 'Name') },
- { name: '__slot:handle', title: Craft.t('app', 'Handle') },
- { name: 'mode', title: Craft.t('app', 'Mode'), },
- { name: 'dimensions', title: Craft.t('app', 'Dimensions'), },
- { name: 'interlace', title: Craft.t('app', 'Interlace'), },
- { name: 'format', title: Craft.t('app', 'Format'), }
-];
-
-let config = {
- columns: columns,
- container: '#transforms-vue-admin-table',
- emptyMessage: Craft.t('app', 'No image transforms exist yet.'),
- tableData: {{ tableData|json_encode|raw }},
-};
-
-{% if not readOnly %}
- config['deleteAction'] = 'image-transforms/delete';
-{% endif %}
-
-new Craft.VueAdminTable(config);
-{% endjs %}
diff --git a/resources/templates/settings/assets/transforms/_settings.twig b/resources/templates/settings/assets/transforms/_settings.twig
deleted file mode 100644
index 1a95fdb257b..00000000000
--- a/resources/templates/settings/assets/transforms/_settings.twig
+++ /dev/null
@@ -1,319 +0,0 @@
-{% extends '_layouts/cp.twig' %}
-
-{% set readOnly = readOnly ?? false %}
-{% set fullPageForm = not readOnly %}
-
-{% set crumbs = [
- { label: "Settings"|t('app'), url: url('settings') },
- { label: "Assets"|t('app'), url: url('settings/assets') },
- { label: "Image Transforms"|t('app'), url: url('settings/assets/transforms') }
-] %}
-
-{% set formActions = [
- {
- label: 'Save and continue editing'|t('app'),
- redirect: 'settings/assets/transforms/{handle}'|hash,
- shortcut: true,
- retainScroll: true,
- },
-] %}
-
-{% set defaultPositionLabel = "Default Focal Point"|t('app') %}
-{% set letterboxPositionLabel = "Image Position"|t('app') %}
-{% set mode = old('mode', transform.mode) %}
-{% set fill = old('fill', transform.fill) %}
-{% set quality = old('quality', transform.quality) %}
-{% set format = old('format', transform.format) %}
-{% set qualityPickerValue = 0 %}
-{% if quality %}
- {% set matchingQualityOption = qualityPickerOptions|filter(o => quality >= o.value)|last %}
- {% set qualityPickerValue = matchingQualityOption ? matchingQualityOption.value : 10 %}
-{% endif %}
-
-{% import '_includes/forms.twig' as forms %}
-
-{% if readOnly %}
- {% set contentNotice = readOnlyNotice() %}
-{% endif %}
-
-{% block content %}
- {% if not readOnly %}
- {{ actionInput('image-transforms/save') }}
- {{ redirectInput('settings/assets/transforms') }}
-
- {% if transform.id %}{{ hiddenInput('transformId', transform.id) }}{% endif %}
- {% endif %}
-
- {{ forms.textField({
- first: true,
- label: "Name"|t('app'),
- id: 'name',
- name: 'name',
- value: old('name', transform.name),
- errors: transform.errors.get('name'),
- autofocus: true,
- required: true,
- disabled: readOnly,
- }) }}
-
- {{ forms.textField({
- label: "Handle"|t('app'),
- id: "handle",
- name: "handle",
- class: 'code',
- autocorrect: false,
- autocapitalize: false,
- value: old('handle', transform.handle),
- errors: transform.errors.get('handle'),
- required: true,
- disabled: readOnly,
- }) }}
-
- {% embed '_includes/forms/field.twig' with {
- label: 'Mode'|t('app')
- } %}
- {% block input %}
-
-
-
-
-
-
- {% endblock %}
- {% endembed %}
-
-
- {{ forms.colorField({
- label: 'Fill Color',
- name: 'fill',
- value: mode == 'letterbox' and fill != 'transparent' ? fill,
- errors: transform.errors.get('fill'),
- disabled: readOnly,
- }) }}
-
-
-
- {% set positionLabel = mode == 'letterbox' ? letterboxPositionLabel : defaultPositionLabel %}
- {{ forms.selectField({
- label: positionLabel,
- id: 'position',
- name: 'position',
- options: {
- 'top-left': "Top-Left"|t('app'),
- 'top-center': "Top-Center"|t('app'),
- 'top-right': "Top-Right"|t('app'),
- 'center-left': "Center-Left"|t('app'),
- 'center-center': "Center-Center"|t('app'),
- 'center-right': "Center-Right"|t('app'),
- 'bottom-left': "Bottom-Left"|t('app'),
- 'bottom-center': "Bottom-Center"|t('app'),
- 'bottom-right': "Bottom-Right"|t('app')
- },
- value: mode in ['crop', 'letterbox'] ? old('position', transform.position) : 'center-center',
- disabled: readOnly,
- }) }}
-
-
- {{ forms.textField({
- label: "Width"|t('app'),
- id: "width",
- name: "width",
- size: 5,
- value: old('width', transform.width),
- errors: transform.errors.get('width'),
- disabled: readOnly,
- }) }}
-
- {{ forms.textField({
- label: "Height"|t('app'),
- id: "height",
- name: "height",
- size: 5,
- value: old('height', transform.height),
- errors: transform.errors.get('height'),
- disabled: readOnly,
- }) }}
-
- {{ forms.lightswitchField({
- label: 'Allow Upscaling'|t('app'),
- id: 'upscale',
- name: 'upscale',
- on: old('upscale', transform.upscale ?? config('craft.general.upscaleImages')),
- errors: transform.errors.get('upscale'),
- disabled: readOnly,
- }) }}
-
- {% embed '_includes/forms/field.twig' with {
- label: 'Quality'|t('app'),
- errors: transform.errors.get('quality'),
- } %}
- {% block input %}
- {% import '_includes/forms.twig' as forms %}
-
-
- {{ forms.select({
- id: 'quality-picker',
- options: [
- {label: 'Auto'|t('app'), value: 0},
- ]|merge(qualityPickerOptions),
- value: qualityPickerValue,
- describedBy: describedBy,
- disabled: readOnly,
- }) }}
-
-
- {{ forms.text({
- id: 'quality',
- class: [
- 'ltr',
- quality == 0 ? 'hidden',
- ]|filter,
- name: 'quality',
- value: quality,
- size: 5,
- type: 'number',
- min: 1,
- max: 100,
- describedBy: describedBy,
- disabled: readOnly,
- }) }}
-
-
- {% endblock %}
- {% endembed %}
-
- {{ forms.selectField({
- label: "Interlacing"|t('app'),
- id: "interlace",
- name: "interlace",
- options: [
- {label: 'None'|t('app'), value: 'none'},
- {label: 'Line'|t('app'), value: 'line'},
- {label: 'Plane'|t('app'), value: 'plane'},
- {label: 'Partition'|t('app'), value: 'partition'},
- ],
- value: old('interlace', transform.interlace ?? 'none'),
- errors: transform.errors.get('interlace'),
- disabled: readOnly,
- }) }}
-
- {% set formatOptions = [
- {label: 'Auto', value: null},
- {label: 'jpg', value: 'jpg'},
- {label: 'png', value: 'png'},
- {label: 'gif', value: 'gif'},
- ] %}
-
- {% if format == 'webp' or Images.supportsWebP %}
- {% set formatOptions = formatOptions|merge([{label: 'webp', value: 'webp'}]) %}
- {% endif %}
-
- {% if format == 'avif' or Images.supportsAvif %}
- {% set formatOptions = formatOptions|merge([{label: 'avif', value: 'avif'}]) %}
- {% endif %}
-
- {{ forms.selectField({
- label: "Image Format"|t('app'),
- id: "format",
- name: "format",
- instructions: "The image format that transformed images should use."|t('app'),
- value: format,
- errors: transform.errors.get('format'),
- options: formatOptions,
- disabled: readOnly,
- }) }}
-
-{% endblock %}
-
-
-{% js %}
- {% if not old('handle', transform.handle) %}new Craft.HandleGenerator('#name', '#handle');{% endif %}
-
- $('#mode input').change(function() {
- const value = $(this).val();
-
- // Letterbox mode requires a position:
- if (value == 'letterbox') {
- $('#fill-color').removeClass('hidden');
- $('#position-container label[for="position"]').text(`{{ letterboxPositionLabel }}`);
- } else {
- $('#fill-color').addClass('hidden');
- $('#position-container label[for="position"]').text(`{{ defaultPositionLabel }}`);
- }
-
- // Crop and letterbox modes requires a position:
- if (value == 'crop' || value == 'letterbox') {
- $('#position-container').removeClass('hidden');
- } else {
- $('#position-container').addClass('hidden');
- }
- });
-
- const qualityPickerOptions = {{ qualityPickerOptions|map(o => o.value)|json_encode|raw }};
- const $qualityPicker = $('#quality-picker');
- const $qualityInput = $('#quality');
-
- $qualityPicker.on('change', (ev) => {
- const pickerValue = $qualityPicker.val();
- $qualityInput.val(pickerValue);
-
- if (pickerValue === '0') {
- $qualityInput.addClass('hidden');
- } else {
- $qualityInput.removeClass('hidden');
- }
- });
-
- $qualityInput.on('input', (ev) => {
- let quality = parseInt($qualityInput.val());
- if (isNaN(quality)) {
- quality = 0;
- }
-
- let pickerValue;
- if (quality) {
- // Default to Low, even if quality is < 10
- pickerValue = 10;
- for (let i = 0; i < qualityPickerOptions.length; i++) {
- if (quality >= qualityPickerOptions[i]) {
- pickerValue = qualityPickerOptions[i];
- } else {
- break;
- }
- }
- } else {
- // Auto
- pickerValue = 0;
- }
-
- $qualityPicker.val(pickerValue);
- });
-{% endjs %}
diff --git a/routes/actions.php b/routes/actions.php
index b1ce41e66e9..75c2182d605 100644
--- a/routes/actions.php
+++ b/routes/actions.php
@@ -71,7 +71,6 @@
use CraftCms\Cms\Http\Controllers\RelationalFieldsController;
use CraftCms\Cms\Http\Controllers\Settings\EntryTypesController;
use CraftCms\Cms\Http\Controllers\Settings\FilesystemsController;
-use CraftCms\Cms\Http\Controllers\Settings\ImageTransformsController;
use CraftCms\Cms\Http\Controllers\Settings\SectionsController;
use CraftCms\Cms\Http\Controllers\Settings\UserSettingsController;
use CraftCms\Cms\Http\Controllers\Settings\VolumesController;
@@ -431,7 +430,6 @@
Route::middleware([RequireAdminChanges::class])->group(function () {
Route::post('volumes/save-volume', [VolumesController::class, 'save']);
Route::post('volumes/reorder-volumes', [VolumesController::class, 'reorder']);
- Route::post('image-transforms/save', [ImageTransformsController::class, 'save']);
});
// Plugins
diff --git a/routes/cp.php b/routes/cp.php
index 7125d5b5686..3933ccb3d30 100644
--- a/routes/cp.php
+++ b/routes/cp.php
@@ -262,10 +262,19 @@
Route::middleware(RequireAdminChanges::class)->get('settings/assets/volumes/new', [VolumesController::class, 'create']);
Route::get('settings/assets/volumes/{volumeId}', [VolumesController::class, 'edit'])->whereNumber('volumeId');
Route::middleware(RequireAdminChanges::class)->delete('settings/assets/volumes/{volumeId}', [VolumesController::class, 'destroy'])->whereNumber('volumeId');
- Route::get('settings/assets/transforms', [ImageTransformsController::class, 'index']);
- Route::middleware(RequireAdminChanges::class)->get('settings/assets/transforms/new', [ImageTransformsController::class, 'create']);
- Route::get('settings/assets/transforms/{transformHandle}', [ImageTransformsController::class, 'edit']);
- Route::middleware(RequireAdminChanges::class)->delete('settings/assets/transforms/{transformId}', [ImageTransformsController::class, 'destroy']);
+
+ // Transforms
+ Route::prefix('settings/assets/transforms')->name('settings.assets.transforms.')->group(function () {
+ Route::get('/', [ImageTransformsController::class, 'index'])->name('index');
+
+ Route::middleware(RequireAdminChanges::class)->group(function () {
+ Route::get('new', [ImageTransformsController::class, 'create'])->name('create');
+ Route::post('/', [ImageTransformsController::class, 'store']);
+ Route::delete('{transformId}', [ImageTransformsController::class, 'destroy'])->name('destroy');
+ });
+
+ Route::get('{transformHandle}', [ImageTransformsController::class, 'edit'])->name('edit');
+ });
// Sites
Route::get('settings/sites', [SitesController::class, 'index'])
diff --git a/src/Cp/Concerns/CanSelect.php b/src/Cp/Concerns/CanSelect.php
index ac40aaa3d10..3414feb4ce9 100644
--- a/src/Cp/Concerns/CanSelect.php
+++ b/src/Cp/Concerns/CanSelect.php
@@ -8,7 +8,7 @@ trait CanSelect
{
abstract public function label(): string;
- /** @return list */
+ /** @return list */
public static function asOptions(): array
{
return array_map(
diff --git a/src/Http/Controllers/Settings/ImageTransformsController.php b/src/Http/Controllers/Settings/ImageTransformsController.php
index 919b35e0bf3..13c189c678d 100644
--- a/src/Http/Controllers/Settings/ImageTransformsController.php
+++ b/src/Http/Controllers/Settings/ImageTransformsController.php
@@ -4,67 +4,60 @@
namespace CraftCms\Cms\Http\Controllers\Settings;
-use CraftCms\Cms\Config\GeneralConfig;
use CraftCms\Cms\Http\RespondsWithFlash;
+use CraftCms\Cms\Http\Responses\CpScreenResponse;
use CraftCms\Cms\Image\Data\ImageTransform;
+use CraftCms\Cms\Image\Enums\ImageTransformFormat;
+use CraftCms\Cms\Image\Enums\ImageTransformInterlace;
+use CraftCms\Cms\Image\Enums\ImageTransformMode;
+use CraftCms\Cms\Image\Enums\ImageTransformPosition;
+use CraftCms\Cms\Image\Enums\ImageTransformQuality;
+use CraftCms\Cms\Image\Images;
use CraftCms\Cms\Image\ImageTransforms;
use CraftCms\Cms\Support\Url;
use CraftCms\Cms\Validation\Rules\ColorRule;
-use CraftCms\Cms\View\LegacyAssets\EditTransformAsset;
-use CraftCms\Cms\View\LegacyAssets\InternalAssetRegistry;
-use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
+use Imagine\Image\Format;
use Inertia\Inertia;
use Symfony\Component\HttpFoundation\Response;
-use function CraftCms\Cms\craftAsset;
use function CraftCms\Cms\t;
class ImageTransformsController
{
use RespondsWithFlash;
- private bool $readOnly;
-
- public function __construct(GeneralConfig $generalConfig)
- {
- $this->readOnly = ! $generalConfig->allowAdminChanges;
- }
-
public function index(ImageTransforms $imageTransforms)
{
- $transforms = $imageTransforms
- ->getAllTransforms()
- ->sort(fn (ImageTransform $a, ImageTransform $b): int => t($a->name, category: 'site') <=> t($b->name, category: 'site'))
- ->values();
-
- return Inertia::render('settings/ImageTransforms', [
+ return Inertia::render('settings/assets/transforms/Index', [
'crumbs' => fn () => [
['label' => t('Settings'), 'url' => Url::cpUrl('settings')],
- ['label' => t('Transforms')],
+ ['label' => t('Assets'), 'url' => Url::cpUrl('settings/assets/transforms')],
+ ['label' => t('Image Transforms')],
],
'title' => t('Image Transforms'),
- 'transforms' => $transforms,
- 'modes' => ImageTransform::modes(),
+ 'transforms' => $imageTransforms
+ ->getAllTransforms()
+ ->sortBy(fn (ImageTransform $transform): string => t($transform->name, category: 'site')),
+ 'modes' => ImageTransformMode::asOptions(),
]);
}
- public function create(): View
+ public function create(Images $images): CpScreenResponse
{
- abort_if($this->readOnly, 403, 'Administrative changes are disallowed in this environment.');
-
- return $this->editView();
+ return $this->editScreen(new ImageTransform, $images);
}
- public function edit(ImageTransforms $imageTransforms, string $transformHandle): View
+ public function edit(ImageTransforms $imageTransforms, Images $images, string $transformHandle): CpScreenResponse
{
$transform = $imageTransforms->getTransformByHandle($transformHandle);
+
abort_if(is_null($transform), 404, 'Transform not found');
- return $this->editView($transformHandle, $transform);
+ return $this->editScreen($transform, $images);
}
- public function save(Request $request, ImageTransforms $imageTransforms): Response
+ public function store(Request $request, ImageTransforms $imageTransforms): Response
{
$transform = new ImageTransform;
$transform->id = $request->integer('transformId') ?: null;
@@ -103,66 +96,54 @@ public function save(Request $request, ImageTransforms $imageTransforms): Respon
return $this->asModelFailure($transform, modelName: 'transform');
}
- return $this->asModelSuccess($transform, t('Transform saved.'), 'transform');
+ return $this->asModelSuccess(
+ $transform,
+ t('Transform saved.'),
+ 'transform',
+ redirect: $this->getPostedRedirectUrl($transform)
+ ?? Url::cpUrl("settings/assets/transforms/$transform->handle"),
+ );
}
- public function destroy(Request $request, ImageTransforms $imageTransforms, int $transformId): Response
+ public function destroy(ImageTransforms $imageTransforms, int $transformId): Response
{
$imageTransforms->deleteTransformById($transformId);
return $this->asSuccess();
}
- private function editView(?string $transformHandle = null, ?ImageTransform $transform = null): View
+ private function editScreen(ImageTransform $transform, Images $images): CpScreenResponse
{
- $transform ??= new ImageTransform;
- app(InternalAssetRegistry::class)->register(EditTransformAsset::class);
-
$title = $transform->id
? (trim((string) $transform->name) ?: t('Edit Image Transform'))
: t('Create a new image transform');
- [$qualityPickerOptions, $qualityPickerValue] = $this->qualityPickerData($transform);
-
- return view('settings/assets/transforms/_settings', [
- 'handle' => $transformHandle,
- 'transform' => $transform,
- 'title' => $title,
- 'qualityPickerOptions' => $qualityPickerOptions,
- 'qualityPickerValue' => $qualityPickerValue,
- 'readOnly' => $this->readOnly,
- 'baseIconsUrl' => craftAsset('legacy/edittransform/dist/images'),
- ]);
+ return new CpScreenResponse()
+ ->title($title)
+ ->addCrumb(t('Settings'), 'settings')
+ ->addCrumb(t('Assets'), 'settings/assets/transforms')
+ ->addCrumb(t('Image Transforms'), 'settings/assets/transforms')
+ ->addCrumb($title)
+ ->redirectUrl('settings/assets/transforms')
+ ->inertiaPage('settings/assets/transforms/Edit', [
+ 'transform' => $transform,
+ 'modeOptions' => ImageTransformMode::asOptions(),
+ 'positionOptions' => ImageTransformPosition::asOptions(),
+ 'interlaceOptions' => ImageTransformInterlace::asOptions(),
+ 'formatOptions' => $this->formatOptions($images, $transform),
+ 'qualityOptions' => ImageTransformQuality::asOptions(),
+ ]);
}
/**
- * @return array{0: array, 1: int}
+ * @return array
*/
- private function qualityPickerData(ImageTransform $transform): array
+ private function formatOptions(Images $images, ImageTransform $transform): array
{
- $qualityPickerOptions = [
- ['label' => t('Low'), 'value' => 10],
- ['label' => t('Medium'), 'value' => 30],
- ['label' => t('High'), 'value' => 60],
- ['label' => t('Very High'), 'value' => 80],
- ['label' => t('Maximum'), 'value' => 100],
- ];
-
- if ($transform->quality) {
- // Default to Low, even if quality is < 10.
- $qualityPickerValue = 10;
- foreach ($qualityPickerOptions as $option) {
- if ($transform->quality >= $option['value']) {
- $qualityPickerValue = $option['value'];
- } else {
- break;
- }
- }
- } else {
- // Auto
- $qualityPickerValue = 0;
- }
-
- return [$qualityPickerOptions, $qualityPickerValue];
+ return collect(ImageTransformFormat::asOptions())
+ ->prepend(['label' => t('Auto'), 'value' => ''])
+ ->reject(fn (array $option) => $option['value'] === ImageTransformFormat::WEBP->value && $transform->format !== Format::ID_WEBP && ! $images->getSupportsWebP())
+ ->reject(fn (array $option) => $option['value'] === ImageTransformFormat::AVIF->value && $transform->format !== Format::ID_AVIF && ! $images->getSupportsAvif())
+ ->all();
}
}
diff --git a/src/Http/Controllers/Settings/VolumesController.php b/src/Http/Controllers/Settings/VolumesController.php
index 47bc582ccbe..ce7cdda6596 100644
--- a/src/Http/Controllers/Settings/VolumesController.php
+++ b/src/Http/Controllers/Settings/VolumesController.php
@@ -55,10 +55,11 @@ public function index(Request $request, Volumes $volumes)
return Inertia::render('settings/Volumes', [
'crumbs' => fn () => [
['label' => t('Settings'), 'url' => Url::cpUrl('settings')],
- ['label' => t('Assets')],
+ ['label' => t('Assets'), 'url' => Url::cpUrl('settings/assets')],
+ ['label' => t('Volumes')],
],
'sort' => $sort,
- 'title' => t('Asset Settings'),
+ 'title' => t('Volume Settings'),
'volumes' => $volumes->getAllVolumes(...),
]);
}
diff --git a/src/Image/Data/ImageTransform.php b/src/Image/Data/ImageTransform.php
index fee3cd641de..6cfe2c741e7 100644
--- a/src/Image/Data/ImageTransform.php
+++ b/src/Image/Data/ImageTransform.php
@@ -5,42 +5,22 @@
namespace CraftCms\Cms\Image\Data;
use CraftCms\Cms\Component\Component;
+use CraftCms\Cms\Database\Table;
use CraftCms\Cms\Image\Contracts\ImageTransformerInterface;
+use CraftCms\Cms\Image\Enums\ImageTransformFormat;
+use CraftCms\Cms\Image\Enums\ImageTransformInterlace;
+use CraftCms\Cms\Image\Enums\ImageTransformMode;
+use CraftCms\Cms\Image\Enums\ImageTransformPosition;
use CraftCms\Cms\Image\ImageTransformer;
use CraftCms\Cms\Validation\Rules\HandleRule;
use DateTimeInterface;
use Illuminate\Validation\Rule;
use Override;
-use function CraftCms\Cms\t;
-
class ImageTransform extends Component
{
public const string DEFAULT_TRANSFORMER = ImageTransformer::class;
- private const array POSITIONS = [
- 'top-left',
- 'top-center',
- 'top-right',
- 'center-left',
- 'center-center',
- 'center-right',
- 'bottom-left',
- 'bottom-center',
- 'bottom-right',
- ];
-
- private const array MODES = [
- 'crop',
- 'fit',
- 'stretch',
- 'letterbox',
- ];
-
- private const array INTERLACES = ['none', 'line', 'plane', 'partition'];
-
- private const array FORMATS = ['jpg', 'gif', 'png', 'webp', 'avif'];
-
public ?int $id = null;
public ?string $name = null;
@@ -77,21 +57,6 @@ public function getIsNamedTransform(): bool
return $this->id && $this->getTransformer() === self::DEFAULT_TRANSFORMER;
}
- /**
- * Returns the available transform modes.
- *
- * @return array
- */
- public static function modes(): array
- {
- return [
- 'crop' => t('Crop'),
- 'fit' => t('Fit'),
- 'stretch' => t('Stretch'),
- 'letterbox' => t('Letterbox'),
- ];
- }
-
/**
* Returns the transformer class.
*
@@ -142,14 +107,14 @@ public function getRules(): array
{
return [
'name' => ['required', 'string'],
- 'handle' => ['required', 'string', new HandleRule],
+ 'handle' => ['required', 'string', new HandleRule, Rule::unique(Table::IMAGETRANSFORMS, 'handle')->ignore($this->id)],
'width' => ['nullable', 'integer', 'min:1'],
'height' => ['nullable', 'integer', 'min:1'],
- 'mode' => ['required', Rule::in(self::MODES)],
- 'position' => ['required', Rule::in(self::POSITIONS)],
- 'interlace' => ['required', Rule::in(self::INTERLACES)],
+ 'mode' => ['required', Rule::enum(ImageTransformMode::class)],
+ 'position' => ['required', Rule::enum(ImageTransformPosition::class)],
+ 'interlace' => ['required', Rule::enum(ImageTransformInterlace::class)],
'quality' => ['nullable', 'integer', 'min:1', 'max:100'],
- 'format' => ['nullable', Rule::in(self::FORMATS)],
+ 'format' => ['nullable', Rule::enum(ImageTransformFormat::class)],
];
}
}
diff --git a/src/Image/Enums/ImageTransformFormat.php b/src/Image/Enums/ImageTransformFormat.php
new file mode 100644
index 00000000000..4d4c047f811
--- /dev/null
+++ b/src/Image/Enums/ImageTransformFormat.php
@@ -0,0 +1,23 @@
+name;
+ }
+}
diff --git a/src/Image/Enums/ImageTransformInterlace.php b/src/Image/Enums/ImageTransformInterlace.php
new file mode 100644
index 00000000000..cf30e88df85
--- /dev/null
+++ b/src/Image/Enums/ImageTransformInterlace.php
@@ -0,0 +1,29 @@
+ t('None'),
+ self::Line => t('Line'),
+ self::Plane => t('Plane'),
+ self::Partition => t('Partition'),
+ };
+ }
+}
diff --git a/src/Image/Enums/ImageTransformMode.php b/src/Image/Enums/ImageTransformMode.php
new file mode 100644
index 00000000000..c983e325bd6
--- /dev/null
+++ b/src/Image/Enums/ImageTransformMode.php
@@ -0,0 +1,29 @@
+ t('Crop'),
+ self::Fit => t('Fit'),
+ self::Stretch => t('Stretch'),
+ self::Letterbox => t('Letterbox'),
+ };
+ }
+}
diff --git a/src/Image/Enums/ImageTransformPosition.php b/src/Image/Enums/ImageTransformPosition.php
new file mode 100644
index 00000000000..dbd39d23eb2
--- /dev/null
+++ b/src/Image/Enums/ImageTransformPosition.php
@@ -0,0 +1,39 @@
+ t('Top-Left'),
+ self::TopCenter => t('Top-Center'),
+ self::TopRight => t('Top-Right'),
+ self::CenterLeft => t('Center-Left'),
+ self::CenterCenter => t('Center-Center'),
+ self::CenterRight => t('Center-Right'),
+ self::BottomLeft => t('Bottom-Left'),
+ self::BottomCenter => t('Bottom-Center'),
+ self::BottomRight => t('Bottom-Right'),
+ };
+ }
+}
diff --git a/src/Image/Enums/ImageTransformQuality.php b/src/Image/Enums/ImageTransformQuality.php
new file mode 100644
index 00000000000..d18b60ad77c
--- /dev/null
+++ b/src/Image/Enums/ImageTransformQuality.php
@@ -0,0 +1,31 @@
+ t('Low'),
+ self::Medium => t('Medium'),
+ self::High => t('High'),
+ self::VeryHigh => t('Very High'),
+ self::Maximum => t('Maximum'),
+ };
+ }
+}
diff --git a/src/View/LegacyAssets/EditTransformAsset.php b/src/View/LegacyAssets/EditTransformAsset.php
deleted file mode 100644
index d76e0ba1f4f..00000000000
--- a/src/View/LegacyAssets/EditTransformAsset.php
+++ /dev/null
@@ -1,26 +0,0 @@
-cssFile(craftAsset('legacy/edittransform/dist/css/transforms.css'));
- }
-}
diff --git a/tests/Feature/Http/Controllers/Settings/ImageTransformsControllerTest.php b/tests/Feature/Http/Controllers/Settings/ImageTransformsControllerTest.php
index 55ce0bf548e..bd088b2289b 100644
--- a/tests/Feature/Http/Controllers/Settings/ImageTransformsControllerTest.php
+++ b/tests/Feature/Http/Controllers/Settings/ImageTransformsControllerTest.php
@@ -5,16 +5,23 @@
use CraftCms\Cms\Cms;
use CraftCms\Cms\Http\Controllers\Settings\ImageTransformsController;
use CraftCms\Cms\Image\Data\ImageTransform as ImageTransformData;
+use CraftCms\Cms\Image\Enums\ImageTransformInterlace;
+use CraftCms\Cms\Image\Enums\ImageTransformMode;
+use CraftCms\Cms\Image\Enums\ImageTransformPosition;
+use CraftCms\Cms\Image\Enums\ImageTransformQuality;
use CraftCms\Cms\Image\ImageTransforms;
use CraftCms\Cms\Image\Models\ImageTransform as ImageTransformModel;
+use CraftCms\Cms\Support\Url;
use CraftCms\Cms\User\Elements\User;
use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Crypt;
use Inertia\Testing\AssertableInertia;
use function CraftCms\Cms\t;
use function Pest\Laravel\actingAs;
use function Pest\Laravel\deleteJson;
use function Pest\Laravel\get;
+use function Pest\Laravel\post;
use function Pest\Laravel\postJson;
beforeEach(function () {
@@ -69,7 +76,7 @@ function validTransformData(array $overrides = []): array
get(action([ImageTransformsController::class, 'index']))->assertRedirect();
get(action([ImageTransformsController::class, 'create']))->assertRedirect();
get(action([ImageTransformsController::class, 'edit'], ['transformHandle' => $transform->handle]))->assertRedirect();
- postJson(action([ImageTransformsController::class, 'save']))->assertUnauthorized();
+ postJson(action([ImageTransformsController::class, 'store']))->assertUnauthorized();
deleteJson(action([ImageTransformsController::class, 'destroy'], [$transform->id]))->assertUnauthorized();
});
@@ -80,31 +87,42 @@ function validTransformData(array $overrides = []): array
get(action([ImageTransformsController::class, 'index']))
->assertInertia(fn (AssertableInertia $page) => $page->where('readOnly', true));
get(action([ImageTransformsController::class, 'edit'], ['transformHandle' => $transform->handle]))
- ->assertOk()
- ->assertSee(t("Changes to these settings aren\u{2019}t permitted in this environment."));
+ ->assertInertia(fn (AssertableInertia $page) => $page
+ ->component('settings/assets/transforms/Edit')
+ ->where('readOnly', true));
get(action([ImageTransformsController::class, 'create']))->assertForbidden();
- postJson(action([ImageTransformsController::class, 'save']), validTransformData())->assertForbidden();
+ postJson(action([ImageTransformsController::class, 'store']), validTransformData())->assertForbidden();
deleteJson(action([ImageTransformsController::class, 'destroy'], [$transform->id]))->assertForbidden();
});
it('renders index', function () {
get(action([ImageTransformsController::class, 'index']))
- ->assertInertia(fn (AssertableInertia $page) => $page->component('settings/ImageTransforms'));
+ ->assertInertia(fn (AssertableInertia $page) => $page->component('settings/assets/transforms/Index'));
});
it('renders create', function () {
get(action([ImageTransformsController::class, 'create']))
- ->assertOk()
- ->assertSee(t('Create a new image transform'));
+ ->assertInertia(fn (AssertableInertia $page) => $page
+ ->component('settings/assets/transforms/Edit')
+ ->where('title', t('Create a new image transform'))
+ ->where('transform.id', null)
+ ->has('modeOptions', count(ImageTransformMode::cases()))
+ ->has('positionOptions', count(ImageTransformPosition::cases()))
+ ->has('interlaceOptions', count(ImageTransformInterlace::cases()))
+ ->has('qualityOptions', count(ImageTransformQuality::cases())));
});
it('renders edit for an existing transform', function () {
$transform = createTestTransform();
get(action([ImageTransformsController::class, 'edit'], ['transformHandle' => $transform->handle]))
- ->assertOk()
- ->assertSee($transform->name);
+ ->assertInertia(fn (AssertableInertia $page) => $page
+ ->component('settings/assets/transforms/Edit')
+ ->where('title', $transform->name)
+ ->where('transform.id', $transform->id)
+ ->where('transform.name', $transform->name)
+ ->where('transform.handle', $transform->handle));
});
it('returns 404 for a missing transform handle', function () {
@@ -117,7 +135,7 @@ function validTransformData(array $overrides = []): array
$payload = validTransformData();
- postJson(action([ImageTransformsController::class, 'save']), $payload)
+ postJson(action([ImageTransformsController::class, 'store']), $payload)
->assertOk()
->assertJsonPath('modelName', 'transform');
@@ -131,6 +149,27 @@ function validTransformData(array $overrides = []): array
->and($transform->name)->toBe($payload['name']);
});
+it('redirects to the saved transform edit page when saving and continuing', function () {
+ $payload = validTransformData([
+ 'handle' => 'continuedTransform',
+ ]);
+
+ post(action([ImageTransformsController::class, 'store']), $payload)
+ ->assertRedirect(Url::cpUrl('settings/assets/transforms/continuedTransform'))
+ ->assertSessionHas('success', t('Transform saved.'));
+});
+
+it('redirects to the posted redirect when saving normally', function () {
+ $payload = validTransformData([
+ 'handle' => 'normallySavedTransform',
+ 'redirect' => Crypt::encrypt('settings/assets/transforms'),
+ ]);
+
+ post(action([ImageTransformsController::class, 'store']), $payload)
+ ->assertRedirect(Url::cpUrl('settings/assets/transforms'))
+ ->assertSessionHas('success', t('Transform saved.'));
+});
+
it('updates an existing transform', function () {
$transform = createTestTransform([
'name' => 'Original Name',
@@ -138,7 +177,7 @@ function validTransformData(array $overrides = []): array
'width' => 100,
]);
- postJson(action([ImageTransformsController::class, 'save']), validTransformData([
+ postJson(action([ImageTransformsController::class, 'store']), validTransformData([
'transformId' => $transform->id,
'name' => 'Updated Name',
'handle' => $transform->handle,
@@ -157,7 +196,7 @@ function validTransformData(array $overrides = []): array
});
it('rejects save when both width and height are missing', function () {
- postJson(action([ImageTransformsController::class, 'save']), validTransformData([
+ postJson(action([ImageTransformsController::class, 'store']), validTransformData([
'width' => '',
'height' => '',
]))
@@ -173,7 +212,7 @@ function validTransformData(array $overrides = []): array
'fill' => 'abc',
]);
- postJson(action([ImageTransformsController::class, 'save']), $payload)->assertOk();
+ postJson(action([ImageTransformsController::class, 'store']), $payload)->assertOk();
$service = app(ImageTransforms::class);
$service->reset();
diff --git a/tests/Feature/Image/Data/ImageTransformTest.php b/tests/Feature/Image/Data/ImageTransformTest.php
new file mode 100644
index 00000000000..8b8a7ae585a
--- /dev/null
+++ b/tests/Feature/Image/Data/ImageTransformTest.php
@@ -0,0 +1,204 @@
+ 'Thumbnail',
+ 'handle' => 'thumb',
+ 'width' => 200,
+ 'height' => 200,
+ 'mode' => 'crop',
+ 'position' => 'center-center',
+ 'interlace' => 'none',
+ ]);
+
+ expect($transform->validate())->toBeTrue();
+ });
+
+ test('fails without name', function () {
+ $transform = new ImageTransform([
+ 'handle' => 'thumb',
+ ]);
+
+ expect($transform->validate())->toBeFalse()
+ ->and($transform->errors()->has('name'))->toBeTrue();
+ });
+
+ test('fails without handle', function () {
+ $transform = new ImageTransform([
+ 'name' => 'Thumb',
+ ]);
+
+ expect($transform->validate())->toBeFalse()
+ ->and($transform->errors()->has('handle'))->toBeTrue();
+ });
+
+ it('requires valid handles', function (string $handle, bool $expected) {
+ $transform = new ImageTransform([
+ 'name' => 'Test',
+ 'handle' => $handle,
+ ]);
+
+ expect($transform->validate())->toBe($expected);
+ })->with([
+ 'camel case' => ['validHandle', true],
+ 'underscore' => ['valid_handle', true],
+ 'hyphen' => ['invalid-handle', false],
+ 'leading number' => ['1invalid', false],
+ 'reserved word' => ['handle', false],
+ ]);
+
+ test('fails with duplicate handle', function () {
+ $original = new ImageTransform([
+ 'name' => 'Original',
+ 'handle' => 'thumb',
+ 'width' => 100,
+ ]);
+
+ expect(app(ImageTransforms::class)->saveTransform($original))->toBeTrue();
+
+ $duplicate = new ImageTransform([
+ 'name' => 'Duplicate',
+ 'handle' => 'thumb',
+ 'width' => 200,
+ ]);
+
+ expect($duplicate->validate())->toBeFalse()
+ ->and($duplicate->errors()->has('handle'))->toBeTrue();
+ });
+
+ test('fails with invalid mode', function () {
+ $transform = new ImageTransform([
+ 'name' => 'Test',
+ 'handle' => 'test',
+ 'mode' => 'invalid',
+ ]);
+
+ expect($transform->validate())->toBeFalse()
+ ->and($transform->errors()->has('mode'))->toBeTrue();
+ });
+
+ test('fails with invalid position', function () {
+ $transform = new ImageTransform([
+ 'name' => 'Test',
+ 'handle' => 'test',
+ 'position' => 'invalid',
+ ]);
+
+ expect($transform->validate())->toBeFalse()
+ ->and($transform->errors()->has('position'))->toBeTrue();
+ });
+
+ test('fails with invalid interlace', function () {
+ $transform = new ImageTransform([
+ 'name' => 'Test',
+ 'handle' => 'test',
+ 'interlace' => 'invalid',
+ ]);
+
+ expect($transform->validate())->toBeFalse()
+ ->and($transform->errors()->has('interlace'))->toBeTrue();
+ });
+
+ test('fails with quality out of range', function (int $quality) {
+ $transform = new ImageTransform([
+ 'name' => 'Test',
+ 'handle' => 'test',
+ 'quality' => $quality,
+ ]);
+
+ expect($transform->validate())->toBeFalse()
+ ->and($transform->errors()->has('quality'))->toBeTrue();
+ })->with([
+ 'zero' => [0],
+ 'over 100' => [101],
+ 'negative' => [-1],
+ ]);
+
+ test('accepts valid quality values', function (int $quality) {
+ $transform = new ImageTransform([
+ 'name' => 'Test',
+ 'handle' => 'test',
+ 'quality' => $quality,
+ ]);
+
+ expect($transform->validate())->toBeTrue();
+ })->with([
+ 'min' => [1],
+ 'max' => [100],
+ 'mid' => [50],
+ ]);
+
+ test('fails with invalid format', function () {
+ $transform = new ImageTransform([
+ 'name' => 'Test',
+ 'handle' => 'test',
+ 'format' => 'bmp',
+ ]);
+
+ expect($transform->validate())->toBeFalse()
+ ->and($transform->errors()->has('format'))->toBeTrue();
+ });
+
+ test('accepts valid formats', function (string $format) {
+ $transform = new ImageTransform([
+ 'name' => 'Test',
+ 'handle' => 'test',
+ 'format' => $format,
+ ]);
+
+ expect($transform->validate())->toBeTrue();
+ })->with([
+ 'jpg' => ['jpg'],
+ 'gif' => ['gif'],
+ 'png' => ['png'],
+ 'webp' => ['webp'],
+ 'avif' => ['avif'],
+ ]);
+
+ test('accepts null format', function () {
+ $transform = new ImageTransform([
+ 'name' => 'Test',
+ 'handle' => 'test',
+ 'format' => null,
+ ]);
+
+ expect($transform->validate())->toBeTrue();
+ });
+
+ test('fails with zero width', function () {
+ $transform = new ImageTransform([
+ 'name' => 'Test',
+ 'handle' => 'test',
+ 'width' => 0,
+ ]);
+
+ expect($transform->validate())->toBeFalse()
+ ->and($transform->errors()->has('width'))->toBeTrue();
+ });
+
+ test('accepts valid positions', function (string $position) {
+ $transform = new ImageTransform([
+ 'name' => 'Test',
+ 'handle' => 'test',
+ 'position' => $position,
+ ]);
+
+ expect($transform->validate())->toBeTrue();
+ })->with([
+ 'top-left' => ['top-left'],
+ 'top-center' => ['top-center'],
+ 'top-right' => ['top-right'],
+ 'center-left' => ['center-left'],
+ 'center-center' => ['center-center'],
+ 'center-right' => ['center-right'],
+ 'bottom-left' => ['bottom-left'],
+ 'bottom-center' => ['bottom-center'],
+ 'bottom-right' => ['bottom-right'],
+ ]);
+});
diff --git a/tests/Feature/Integration/PagesTest.php b/tests/Feature/Integration/PagesTest.php
index 6b7050975e7..b612f0ed9a4 100644
--- a/tests/Feature/Integration/PagesTest.php
+++ b/tests/Feature/Integration/PagesTest.php
@@ -85,13 +85,13 @@
],
[
'url' => '/settings/assets',
- 'title' => 'Asset Settings',
+ 'title' => 'Volume Settings',
'component' => 'settings/Volumes',
],
[
'url' => '/settings/assets/transforms',
'title' => 'Image Transforms',
- 'component' => 'settings/ImageTransforms',
+ 'component' => 'settings/assets/transforms/Index',
],
[
'url' => '/settings/routes',
diff --git a/tests/Unit/Image/Data/ImageTransformTest.php b/tests/Unit/Image/Data/ImageTransformTest.php
index 3a3261fbb6c..374939f53ce 100644
--- a/tests/Unit/Image/Data/ImageTransformTest.php
+++ b/tests/Unit/Image/Data/ImageTransformTest.php
@@ -61,15 +61,6 @@
});
});
-describe('modes', function () {
- test('returns all four modes', function () {
- $modes = ImageTransform::modes();
-
- expect($modes)->toHaveKeys(['crop', 'fit', 'stretch', 'letterbox'])
- ->and($modes)->toHaveCount(4);
- });
-});
-
describe('transformer', function () {
test('defaults to ImageTransformer', function () {
$transform = new ImageTransform;
@@ -152,185 +143,6 @@
});
});
-describe('validation', function () {
- test('validates a complete transform', function () {
- $transform = new ImageTransform([
- 'name' => 'Thumbnail',
- 'handle' => 'thumb',
- 'width' => 200,
- 'height' => 200,
- 'mode' => 'crop',
- 'position' => 'center-center',
- 'interlace' => 'none',
- ]);
-
- expect($transform->validate())->toBeTrue();
- });
-
- test('fails without name', function () {
- $transform = new ImageTransform([
- 'handle' => 'thumb',
- ]);
-
- expect($transform->validate())->toBeFalse()
- ->and($transform->errors()->has('name'))->toBeTrue();
- });
-
- test('fails without handle', function () {
- $transform = new ImageTransform([
- 'name' => 'Thumb',
- ]);
-
- expect($transform->validate())->toBeFalse()
- ->and($transform->errors()->has('handle'))->toBeTrue();
- });
-
- it('requires valid handles', function (string $handle, bool $expected) {
- $transform = new ImageTransform([
- 'name' => 'Test',
- 'handle' => $handle,
- ]);
-
- expect($transform->validate())->toBe($expected);
- })->with([
- 'camel case' => ['validHandle', true],
- 'underscore' => ['valid_handle', true],
- 'hyphen' => ['invalid-handle', false],
- 'leading number' => ['1invalid', false],
- 'reserved word' => ['handle', false],
- ]);
-
- test('fails with invalid mode', function () {
- $transform = new ImageTransform([
- 'name' => 'Test',
- 'handle' => 'test',
- 'mode' => 'invalid',
- ]);
-
- expect($transform->validate())->toBeFalse()
- ->and($transform->errors()->has('mode'))->toBeTrue();
- });
-
- test('fails with invalid position', function () {
- $transform = new ImageTransform([
- 'name' => 'Test',
- 'handle' => 'test',
- 'position' => 'invalid',
- ]);
-
- expect($transform->validate())->toBeFalse()
- ->and($transform->errors()->has('position'))->toBeTrue();
- });
-
- test('fails with invalid interlace', function () {
- $transform = new ImageTransform([
- 'name' => 'Test',
- 'handle' => 'test',
- 'interlace' => 'invalid',
- ]);
-
- expect($transform->validate())->toBeFalse()
- ->and($transform->errors()->has('interlace'))->toBeTrue();
- });
-
- test('fails with quality out of range', function (int $quality) {
- $transform = new ImageTransform([
- 'name' => 'Test',
- 'handle' => 'test',
- 'quality' => $quality,
- ]);
-
- expect($transform->validate())->toBeFalse()
- ->and($transform->errors()->has('quality'))->toBeTrue();
- })->with([
- 'zero' => [0],
- 'over 100' => [101],
- 'negative' => [-1],
- ]);
-
- test('accepts valid quality values', function (int $quality) {
- $transform = new ImageTransform([
- 'name' => 'Test',
- 'handle' => 'test',
- 'quality' => $quality,
- ]);
-
- expect($transform->validate())->toBeTrue();
- })->with([
- 'min' => [1],
- 'max' => [100],
- 'mid' => [50],
- ]);
-
- test('fails with invalid format', function () {
- $transform = new ImageTransform([
- 'name' => 'Test',
- 'handle' => 'test',
- 'format' => 'bmp',
- ]);
-
- expect($transform->validate())->toBeFalse()
- ->and($transform->errors()->has('format'))->toBeTrue();
- });
-
- test('accepts valid formats', function (string $format) {
- $transform = new ImageTransform([
- 'name' => 'Test',
- 'handle' => 'test',
- 'format' => $format,
- ]);
-
- expect($transform->validate())->toBeTrue();
- })->with([
- 'jpg' => ['jpg'],
- 'gif' => ['gif'],
- 'png' => ['png'],
- 'webp' => ['webp'],
- 'avif' => ['avif'],
- ]);
-
- test('accepts null format', function () {
- $transform = new ImageTransform([
- 'name' => 'Test',
- 'handle' => 'test',
- 'format' => null,
- ]);
-
- expect($transform->validate())->toBeTrue();
- });
-
- test('fails with zero width', function () {
- $transform = new ImageTransform([
- 'name' => 'Test',
- 'handle' => 'test',
- 'width' => 0,
- ]);
-
- expect($transform->validate())->toBeFalse()
- ->and($transform->errors()->has('width'))->toBeTrue();
- });
-
- test('accepts valid positions', function (string $position) {
- $transform = new ImageTransform([
- 'name' => 'Test',
- 'handle' => 'test',
- 'position' => $position,
- ]);
-
- expect($transform->validate())->toBeTrue();
- })->with([
- 'top-left' => ['top-left'],
- 'top-center' => ['top-center'],
- 'top-right' => ['top-right'],
- 'center-left' => ['center-left'],
- 'center-center' => ['center-center'],
- 'center-right' => ['center-right'],
- 'bottom-left' => ['bottom-left'],
- 'bottom-center' => ['bottom-center'],
- 'bottom-right' => ['bottom-right'],
- ]);
-});
-
describe('DEFAULT_TRANSFORMER constant', function () {
test('points to ImageTransformer class', function () {
expect(ImageTransform::DEFAULT_TRANSFORMER)->toBe(ImageTransformer::class);
diff --git a/workbench/app/Providers/TypeScriptTransformerServiceProvider.php b/workbench/app/Providers/TypeScriptTransformerServiceProvider.php
index 6db3a4d8d5a..06b5842cebf 100644
--- a/workbench/app/Providers/TypeScriptTransformerServiceProvider.php
+++ b/workbench/app/Providers/TypeScriptTransformerServiceProvider.php
@@ -4,6 +4,7 @@
namespace Workbench\App\Providers;
+use CraftCms\Cms\Image\Data\ImageTransform;
use CraftCms\Cms\Route\Data\Route;
use CraftCms\Cms\Update\Data\Updates;
use DateTimeInterface;
@@ -24,6 +25,7 @@ protected function configure(TypeScriptTransformerConfigFactory $config): void
->replaceType(DateTimeInterface::class, 'string')
->provider(new ClassListTransformedProvider(
[
+ ImageTransform::class,
Updates::class,
Route::class,
],
diff --git a/yii2-adapter/legacy/web/assets/edittransform/EditTransformAsset.php b/yii2-adapter/legacy/web/assets/edittransform/EditTransformAsset.php
index af7ff2bf291..1ced6e3e607 100644
--- a/yii2-adapter/legacy/web/assets/edittransform/EditTransformAsset.php
+++ b/yii2-adapter/legacy/web/assets/edittransform/EditTransformAsset.php
@@ -8,7 +8,7 @@
namespace craft\web\assets\edittransform;
use craft\web\AssetBundle;
-use CraftCms\Cms\View\LegacyAssets\InternalAssetRegistry;
+use craft\web\assets\cp\CpAsset;
/**
* Asset bundle for the Edit Transform page
@@ -16,8 +16,13 @@
*/
class EditTransformAsset extends AssetBundle
{
- public function registerAssetFiles($view)
- {
- app(InternalAssetRegistry::class)->register(\CraftCms\Cms\View\LegacyAssets\EditTransformAsset::class);
- }
+ public $sourcePath = '@craftcms/resources/legacy/edittransform/dist';
+
+ public $depends = [
+ CpAsset::class,
+ ];
+
+ public $css = [
+ 'css/transforms.css',
+ ];
}