Repository navigation
[6.x] Element index Thumb Size setting - #20124
Merged
Merged
Conversation
Image analysis tasks add instructions and JSON schema properties to a shared prompt, and apply their part of the response to the asset. Tasks that resolve to the same provider and model share one request per image, with a scaled-down copy of it. Images are queued for analysis when they're uploaded, replaced, or indexed, and with a new "Analyze images" action, all of which only fill in what images are missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Craft writes alt text for images in the language of each site with its own alt text, per the volume's alt text translation method, filling in images that are missing it when they're analyzed. Alternative Text fields get a "Generate alt text" action that fills in a suggestion for editors to review before saving, through a new generic craft:generate-field-value field action event. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Craft sets the focal points of images that don't have one when they're analyzed, so their crops keep the subject in view. The point is kept within the image, and the asset's transforms are invalidated when it changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assets get searchable `keywords` and `visibleText` attributes, stored in new `assets` columns and exposed to GraphQL. The "Tag images" AI task fills them in for images that haven't been tagged, with keywords in the primary site's language and any text that appears in the image. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Image colors now include a palette of the image's main colors, sampled on the server and in the browser for uploads, with labels() naming them in plain words such as "red" or "teal" via a new ColorNames helper. Images are searchable by those names, the palette and labels are exposed to GraphQL, and images sampled before palettes were get sampled again the next time they're indexed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saving and indexing assets now check which image analysis tasks can run, which reads cached capability checks. Injecting AiManager there failed where laravel/ai isn't booted, such as the Yii2 adapter's tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # src/Asset/Elements/Asset.php # src/Element/Queries/AssetQuery.php
# Conflicts: # src/Asset/Elements/Asset.php # src/Element/Queries/AssetQuery.php
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ture/ai-image-tasks
# Conflicts: # src/Asset/Elements/Asset.php # src/Database/Migrations/Install.php
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
They're listed in DEFAULT_TYPES, like other registries' built-in types, rather than registered from the asset service provider. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
An image whose palette is red can now be found by searching rot on a German site, as well as red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Images' colors, and the keywords and text the Tag images AI task finds, are now stored together in assets' analysis column, read through Asset::$analysis (an ImageAnalysis) and the analysis GraphQL field. Asset::$colors and the colors GraphQL field have moved there. Replacing an image's file, or indexing one that's changed, now starts its analysis over with only the new colors, so the new file gets tagged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Indexing only samples colors and queues AI analysis for new and changed files, unless the Asset Indexes utility's Analyze images option, or the index-assets commands' --analyzeImages option, is on. Then it also does both for unchanged images that are missing something. It's stored on indexing sessions, since the utility indexes over several requests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Image colors now include each region's 10th and 90th percentile WCAG relative luminance, sampled on the server and in the browser alike, and ImageColors::textColor() picks white or black text for the whole image or one of its edges, with the contrast ratio, so templates can tell when text needs a scrim. Also available as the luminance and textColor GraphQL fields. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Images now store the title, description, alt text, creator, credit, copyright, keywords, date taken, and camera embedded in their XMP, IPTC, and EXIF data in analysis.metadata, and whether the file has GPS coordinates in it, without the coordinates. Uploads are read before they're cleaned, and the GPS flag is checked again after cleaning, so it reflects the stored file. Indexing reads unchanged images that are missing metadata when asked to analyze images. Embedded keywords are searchable. Alt text from metadata now comes from the same reader, which also converts text that isn't UTF-8 without erroring. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Titles images that are still titled after their filename, such as “IMG 4759”, in each site's language, sharing one title between sites that share titles, like Generate alt text does for alt text. Asset::hasDefaultTitle() says whether a title is still the filename one. New uploads also take their title from their embedded metadata, if they have one, so the metadata is now read in beforeSave(), where titles are set, which is still before the file is cleaned. AltTextSite is now TranslatedSite, shared by both tasks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Finds what kind of image each image is (photo, illustration, screenshot, diagram, logo, document, or pattern) and stores it in analysis.type, as an ImageType. It's searchable by the type's name and its label in the site's language, and available as the type GraphQL field. Image analysis tasks that both change an asset's analysis in the same request, like this and Tag images, now build on each other's changes through ImageAnalysisChanges::get(), rather than the last one replacing the others'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Now that missing metadata also means an image is sampled again, the tests for images sampled before palettes or luminance store metadata, so they only pass because of the part they're about. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Image types are indexed as the imageType search attribute, so searches like imageType:logo find only images classified as logos, rather than as part of keywords, where they mixed with AI and embedded tags. Plain searches still find them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Image types are no longer a search attribute; keywords hold only AI and embedded keywords. Instead, a generated, indexed assets.imageType column mirrors analysis.type, and it backs AssetQuery::imageType(), the “Image Type” asset condition rule, and the imageType GraphQL argument. Asset::getImageType() returns an image's type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Asset search keywords now only add the keywords and text found in images to what was already indexed, so the colors search attribute is gone. ColorNames::label() still translates color names for display. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
The browser sampler seeded its k-means++ clustering with mulberry32, while Intervention draws from PHP's Mt19937 Randomizer, so the two started from different centroids and found different palettes and dominant colors for the same image. Port PHP's Randomizer over Mt19937 and use it in the browser, and test both samplers against the same fixture. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Control panel uploads now read images' embedded metadata in the browser, with a port of ImageMetadataReader, and send it with their colors, so the server can store both without reading the file. Images staged on a remote disk are no longer downloaded unless they're cleaned (metadata is read first, so cleaning can't lose it) or their dimensions can't be read from the start of the file. If the uploader sent no colors or metadata, the AnalyzeImage job samples them instead; it's otherwise only queued when AI tasks are pending. New assets for files already in place, as direct uploads leave them, are queued the same way. Staged uploads are copied into their volume within S3 when both disks share credentials and an endpoint, instead of being downloaded and uploaded again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Upload completion requests now send the browser's colors and metadata as `analysis`, shaped like the analysis they're stored in, and `Asset::$uploadColors` is replaced by `$uploadAnalysis`. Only the colors and metadata are taken from it; what AI tasks find can't be set from a request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
# Conflicts: # CHANGELOG.md
It samples missing metadata as well as colors. Shortened the help text and command option descriptions, and added the option's strings to en/app.php. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
Vue sets <craft-thumbnail>'s width and height as properties straight from the template's strings, and compared as strings "80" >= "120", so a portrait image got a landscape box: a square that letterboxed it, hiding its rounded corners. Compare them as numbers. Amp-Thread-ID: https://ampcode.com/threads/T-01a17da9-ff5c-7129-89d3-a833d37fee76
thumb-size sizes a chip's thumbnail on its own scale (sm through 2xl, with lg matching a card's thumbnail), and a chip without a size of its own takes the one that suits it. prefix-placement="block-start" stacks the prefix above the label, for tiles in a grid, with the checkbox and suffix in a row along the top. select-disabled disables the selection checkbox. ElementHtml gains a matching thumbSize config option, backed by the new ChipThumbSize enum, so server-rendered chips request transforms at their thumbnail's size. File and folder icons grow with the thumbnail, and plain chips no longer inset a leading thumbnail. Amp-Thread-ID: https://ampcode.com/threads/T-01a17da9-ff5c-7129-89d3-a833d37fee76
Table and structure views offer sm, md, and lg thumbnails, small by default; thumbnail views offer all five sizes, lg by default, matching their previous size. Each view's size is kept in the index's view state and sent as a thumbSize param, so the server renders the thumbnails at it, and switching views brings the new view's size along. Thumbnail tiles are now <craft-chip prefix-placement="block-start">, sized by thumb-size, with the grid's columns widening to suit. Amp-Thread-ID: https://ampcode.com/threads/T-01a17da9-ff5c-7129-89d3-a833d37fee76
# Conflicts: # resources/js/modules/uploader/asset-upload-queue.ts
📚 Storybook previews@craftcms/ui — open Storybook Changed components: resources/js — open Storybook Changed components: |
Asset edit pages' Info panels now show what was found in an image as a separate group after the rest of its metadata: its type, a bar of its main colors sized by how much of the image each covers, its embedded title, alt text, creator, credit, copyright, date taken, and camera, and where it was taken, with a map drawn from OpenStreetMap tiles (inverted for the dark theme) and a Google Maps link. The asset's folder is now labeled "Folder" everywhere. Asset indexes get matching table columns and card attributes. Image metadata now includes GPS coordinates, read before uploads are cleaned (kept out of GraphQL and sandboxed templates), and the city, state, and country, in both the PHP reader and its browser port; `hasLocation` is gone. Image colors now include palette weights, sampled the same way on the server and in the browser. Embedded descriptions are searchable along with embedded keywords. The PHP reader no longer misses XMP packets that end past the first 8 KB of a file, since fread() can return less than asked. Elements can show groups of metadata, separated by a rule, through ElementInterface::getMetadataGroups(), and ContentHtml::metadataHtml() takes several groups and lists of values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
…ture/ai-image-tasks
…t text Uploads and the analysis queue job now fill in an image's alt text and title from its embedded metadata through Asset::valuesFromEmbeddedMetadata(), with ImageMetadata::suggestedAlt(), so they follow the same rules: titles are filled in whenever they're still the filename one, including when a file is replaced. Adds the assets.analyze MCP tool, which queues images for the image analysis tasks that are set up, and lets content-model audit checks return notes; the missing-alt-text check's note says whether "Generate alt text" is set up, and to use assets.analyze if so. The AI docs and docblock examples use a plugin task that doesn't overlap with Craft's own image tasks, and point image tasks at ImageAnalysisTask. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxhedKGW7eCLF4xLBVmh6o
…ment-index-thumb-size
Covers thumb-size at each step, an explicit size overriding the one thumb-size picks, select-disabled on the Selectable story, and a portrait thumbnail with string width/height in chips at each thumb-size. Amp-Thread-ID: https://ampcode.com/threads/T-01a17da9-ff5c-7129-89d3-a833d37fee76 Co-authored-by: Amp <amp@ampcode.com>
The View menu listed table columns in the thumbnail and cards views too, where they do nothing. The legacy index hides them there. Co-authored-by: Amp <amp@ampcode.com> Amp-Thread-ID: https://ampcode.com/threads/T-01a17da9-ff5c-7129-89d3-a833d37fee76
craft-popover capped its pane at 40vh, so a View menu opening near the top of the page scrolled its Table Columns behind the footer with most of the window free below it. A Popper modifier now measures the room between the pane and the viewport edge it opens toward, and the pane uses that as its max height. --popover-max-block-size still wins, and 40vh remains the fallback when there's nothing to measure. Amp-Thread-ID: https://ampcode.com/threads/T-01a17da9-ff5c-7129-89d3-a833d37fee76 Co-authored-by: Amp <amp@ampcode.com>
…s/cms into element-index-thumb-size
Changing the Thumb Size reloaded the whole result view. The View menu now resizes the chips already on screen and scales their thumbnails to fill the new size, then quietly re-requests the rows so thumbnails rendered for that size replace them. The element selector modal's visitor now honors silent visits too. Amp-Thread-ID: https://ampcode.com/threads/T-01a17da9-ff5c-7129-89d3-a833d37fee76 Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-01a17da9-ff5c-7129-89d3-a833d37fee76 Co-authored-by: Amp <amp@ampcode.com>
…s/cms into element-index-thumb-size
The Chip docs page embeds stories by hand, so the new ones only showed in the sidebar. Adds Thumbnail Size and Prefix Placement sections, and the select-disabled example under Accessibility. The thumbnail's string width/height check moves from a story's play function to a browser test, leaving the story as a docs example. Amp-Thread-ID: https://ampcode.com/threads/T-01a17da9-ff5c-7129-89d3-a833d37fee76 Co-authored-by: Amp <amp@ampcode.com>
A medium chip's thumbnail was inset to keep the chip as tall as a medium button, which with medium's larger padding left it at 22px, smaller than a small chip's 26px. It now keeps its own 32px. The chip docs say how the thumbnail steps with size, and that thumb-size sets a larger one. Amp-Thread-ID: https://ampcode.com/threads/T-01a17da9-ff5c-7129-89d3-a833d37fee76 Co-authored-by: Amp <amp@ampcode.com>
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds a Thumb Size setting to element index View menus, and redraws thumbnail view tiles as chips so they share the same sizes. Resolves #20083.
smmdlgsmsmmdlgxl2xllg(unchanged from before)<craft-chip>changes:sizestays on the sharedsmall | medium | largescale.thumb-size="sm|md|lg|xl|2xl"sets the thumbnail (26, 60, 120, 180, 240px). A chip without its ownsizetakes the one that suits it:sm→small,md→medium,lg+→large.prefix-placement="block-start"stacks the thumbnail above the label, with the checkbox and suffix along the top. Thumbnail views now use this instead of bespoke tile markup.select-disabled.PHP gets a matching
ChipThumbSizeenum and athumbSizeoption forElementHtml::chipHtml()/elementChipHtml(), so transforms are requested at the thumbnail's actual size. Each view remembers its own size in the index's view state; switching views carries the new view's size along.Commits:
<craft-thumbnail>compared stringwidth/height("80" >= "120"), so portrait images got a letterboxed square box and their rounded corners didn't show.thumb-sizeandprefix-placementtocraft-chip— component + PHP API, file/folder icons scale with the thumbnail, plain chips no longer inset a leading thumbnail.thumbSizeparam,ElementThumbsdrawn as stacked chips.Evidence
View menu, thumbnail view:
Thumbnail view at
sm,md,lg(default),xl,2xl:Table view title chips at
sm,md,lg:Tests:
Related issues