From 93fa05d04a0be78f7f31cad9f4bc92474d76286a Mon Sep 17 00:00:00 2001 From: Rias Date: Thu, 21 May 2026 17:27:42 +0200 Subject: [PATCH 1/4] Implement image transform create/edit in Inertia --- .../craftcms-cp/src/styles/form.styles.ts | 4 + resources/js/components/form/ColorInput.vue | 190 ++++++++ .../transforms/EditImageTransformPage.vue | 452 ++++++++++++++++++ .../transforms/ImageTransformsIndexPage.vue} | 45 +- .../pages/settings/assets/transforms/types.ts | 23 + resources/public/images/transforms/crop.svg | 35 ++ resources/public/images/transforms/fit.svg | 35 ++ .../public/images/transforms/letterbox.svg | 36 ++ .../public/images/transforms/stretch.svg | 37 ++ .../Settings/ImageTransformsController.php | 184 ++++--- .../Settings/VolumesController.php | 5 +- src/Image/Data/ImageTransform.php | 3 +- src/View/LegacyAssets/EditTransformAsset.php | 26 - .../ImageTransformsControllerTest.php | 54 ++- tests/Feature/Integration/PagesTest.php | 2 +- .../edittransform/EditTransformAsset.php | 15 +- 16 files changed, 1022 insertions(+), 124 deletions(-) create mode 100644 resources/js/components/form/ColorInput.vue create mode 100644 resources/js/pages/settings/assets/transforms/EditImageTransformPage.vue rename resources/js/pages/{SettingsImageTransformsIndexPage.vue => settings/assets/transforms/ImageTransformsIndexPage.vue} (81%) create mode 100644 resources/js/pages/settings/assets/transforms/types.ts create mode 100644 resources/public/images/transforms/crop.svg create mode 100644 resources/public/images/transforms/fit.svg create mode 100644 resources/public/images/transforms/letterbox.svg create mode 100644 resources/public/images/transforms/stretch.svg delete mode 100644 src/View/LegacyAssets/EditTransformAsset.php diff --git a/packages/craftcms-cp/src/styles/form.styles.ts b/packages/craftcms-cp/src/styles/form.styles.ts index a328d52b580..03feebe72ac 100644 --- a/packages/craftcms-cp/src/styles/form.styles.ts +++ b/packages/craftcms-cp/src/styles/form.styles.ts @@ -92,4 +92,8 @@ export const inputStyles = css` :host([center]) ::slotted([slot='input']) { text-align: center; } + + ::slotted([slot='input']) { + width: 100%; + } `; diff --git a/resources/js/components/form/ColorInput.vue b/resources/js/components/form/ColorInput.vue new file mode 100644 index 00000000000..cdad2b8d1c7 --- /dev/null +++ b/resources/js/components/form/ColorInput.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/resources/js/pages/settings/assets/transforms/EditImageTransformPage.vue b/resources/js/pages/settings/assets/transforms/EditImageTransformPage.vue new file mode 100644 index 00000000000..067d5815eab --- /dev/null +++ b/resources/js/pages/settings/assets/transforms/EditImageTransformPage.vue @@ -0,0 +1,452 @@ + + + + + diff --git a/resources/js/pages/SettingsImageTransformsIndexPage.vue b/resources/js/pages/settings/assets/transforms/ImageTransformsIndexPage.vue similarity index 81% rename from resources/js/pages/SettingsImageTransformsIndexPage.vue rename to resources/js/pages/settings/assets/transforms/ImageTransformsIndexPage.vue index bd368725a0a..2c2ceac169a 100644 --- a/resources/js/pages/SettingsImageTransformsIndexPage.vue +++ b/resources/js/pages/settings/assets/transforms/ImageTransformsIndexPage.vue @@ -8,6 +8,7 @@ import { create, destroy, + edit, index as imageTransformsIndex, } from '@actions/Settings/ImageTransformsController'; import AdminTable from '@/components/AdminTable/AdminTable.vue'; @@ -15,25 +16,9 @@ import Empty from '@/components/Empty.vue'; import {router} from '@inertiajs/vue3'; import {index} from '@actions/Settings/VolumesController'; + import type {ExistingImageTransform} from '@/pages/settings/assets/transforms/types'; - export interface ImageTransform { - id: number; - name: string; - handle: string; - width: number; - height: number; - format: any; - quality: number; - mode: string; - position: string; - interlace: string; - fill: any; - upscale: boolean; - uid: string; - parameterChangeTime: any[]; - } - - function deleteTransform(transform: ImageTransform) { + function deleteTransform(transform: ExistingImageTransform) { if ( confirm( t('Are you sure you want to delete the “{name}” transform?', { @@ -41,22 +26,32 @@ }) ) ) { - router.delete(destroy(transform.id)); + router + .optimistic<{transforms: Array}>((props) => ({ + transforms: props.transforms.filter(({id}) => id !== transform.id), + })) + .delete(destroy(transform.id), { + preserveScroll: true, + }); } } const props = defineProps<{ - transforms: Array; + transforms: Array; }>(); const columnVisibility = ref({ name: true, handle: true, }); - const columnHelper = createCraftColumnHelper(); + const columnHelper = createCraftColumnHelper(); const columns = ref([ columnHelper.link('name', { header: t('Name'), + props: ({row}) => ({ + href: edit(row.original.handle).url, + inertia: true, + }), }), columnHelper.handle('handle'), columnHelper.accessor('mode', { @@ -94,7 +89,7 @@ return columns.value; }, enableSorting: false, - getCoreRowModel: getCoreRowModel(), + getCoreRowModel: getCoreRowModel(), state: { get columnVisibility() { return columnVisibility.value; @@ -104,7 +99,7 @@ const navItems = computed(() => { return { - volumes: {label: t('Volumes'), url: index().url}, + volumes: {label: t('Volumes'), url: index().url, active: false}, transforms: { label: t('Image Transforms'), url: imageTransformsIndex().url, @@ -119,7 +114,6 @@