feat(image-editor): add Angular image editor#36236
Conversation
|
Claude finished @oidacra's task in 2m 18s —— View job Rollback Safety Analysis
Result: ✅ Safe to Roll Back Added label: Scope of changes analyzed: The entire diff between
No matches found for: C-1 (structural DB change), C-2 (ES mapping), C-3 (JSON model version), C-4 (DROP TABLE/COLUMN), H-1 (destructive backfill), H-2 (RENAME), H-3 (PK restructuring), H-4 (new field type), H-5 (storage provider), H-6 (DROP PROCEDURE), H-7 (NOT NULL without default), H-8 (VTL viewtool contract), M-1 (column type change), M-2 (push bundle format), M-3 (REST contract break), M-4 (OSGi interface). The new image editor is entirely behind the |
🤖 Bedrock Review —
|
03448ba to
c2f4635
Compare
c2f4635 to
a1d5b80
Compare
a1d5b80 to
5f38ef3
Compare
- Give scale/resize their own 'resize' history category (no longer coalesce with brightness/hue/saturation under 'adjust'). - Undo/redo guard also treats role=combobox (PrimeNG select/dropdown/autocomplete) as an editable target so shortcuts don't hijack keys inside open controls. - Binary field launcher subscribe gets an error callback (no silent failure). - Transform panel dispatches the displayed (computed outputDimensions) value for the unchanged axis, so persisted output dims match what the field shows. - History entry ids use a monotonic counter instead of Date.now() (collision-free, fake-timer stable). - with-preview resolveSize$ gets catchError so a dispatch throw can't freeze the size readout; remove console.warn from panel-state storage; fix canvas/launcher-token comments. Test additions: quality lower-bound clamp, download append/remove asserts.
…ory step Edit history coalesced on the broad FilterCategory, so changing brightness then hue (both 'adjust') overwrote the brightness step instead of recording a new one — leaving only the last edit per category. The same affected flipH/flipV, scale/output-dims and compression/quality. Coalescing now keys on a per-control 'coalesceKey' on the history entry: repeated edits to one control still merge into a single step, but switching to a different control in the same category starts a new step.
…rmed A crop drawn before rotating was emitted after the Rotate filter and applied with original-image coordinates, so the server cropped the already-rotated (width/height-swapped) image and cut the wrong region. The filter chain now orders Crop relative to rotate/flip by the order the user performed them (read from history): crop-then-rotate builds Crop,Rotate (crop the original, then rotate the result); rotate-then-crop keeps Rotate,Crop. The crop overlay now converts the box against the displayed (post-transform) image's intrinsic size instead of the original asset, so a crop drawn on a rotated preview scales correctly as well.
Esc closed the dialog directly through PrimeNG's closeOnEscape, skipping the unsaved-changes guard. Esc now routes through requestClose() (closeOnEscape is disabled on the dialog): it prompts to discard when there are edits and closes immediately when clean. A re-entry guard stops a repeated Esc from re-opening the prompt.
…F on libvips File info now shows the effective output Format (the chosen compression format, or the source format derived from the asset MIME type when not converting) and the Focal point (the store value, defaulting to the centre 0.50, 0.50 since it is not read back from the asset yet). AVIF is a libvips-only output format, so the AVIF compression option is offered only when IMAGE_API_USE_LIBVIPS is enabled. The UI reads it through DotPropertiesService.getKey; the key is whitelisted in ConfigurationResource so the configuration endpoint exposes it to the client.
…tor-build-dotimageeditorcomponent-modal-s # Conflicts: # core-web/tsconfig.base.json
🤖 Bedrock Review —
|
…order, config, docs) - SCSS: use the PrimeNG 21 --p-* theme tokens (canvas/crop/focal overlays) instead of the bare --surface-*/--primary-* names, which are undefined under the styled theme and silently fell back to hardcoded hex (active controls ignored the theme). - service: getFileSize returns null (not 0) for a missing/unreadable Content-Length so an unknown size renders as the em dash, not a misleading 0.0 KB; inject DOCUMENT instead of the global document/new Image(); log asset-metadata load failures. - with-preview: order Crop relative to rotate/flip by the LAST crop entry so a crop -> rotate -> crop sequence orders against the current crop, not the stale first. - tsconfig: drop the stray @dotcms/agentic-tools path mapping (no such file, unused). - config: declare IMAGE_API_USE_LIBVIPS=false so the endpoint returns a real boolean. - docs: correct the focal-point (editor-state-only), save-flow, address-bar tool list, and coalesceHistory JSDoc placement.
…y failure - Bail out of onEditImage() when the field name/variable haven't resolved instead of leaking undefined into the launcher params (the image-editor lib treats them as required strings; edit-content's strict:false hid the mismatch). - Surface a failure of the editor stream to the user with an error toast (optional MessageService, best-effort) in addition to the console log, so an edit that fails to apply is no longer silently dropped.
🤖 Bedrock Review —
|
…agName constant Add IMAGE_API_USE_LIBVIPS to FeatureFlagName and reference it in ConfigurationResource's whitelist/boolean-flag sets instead of a bare string literal, matching the pattern of the other entries.
- Add redo-disabled, async clipboard-copy success, and clipboard-error tests to the address bar; assert the copy toast after the promise settles (whenStable). - Add a quality-slider dispatch test to the file-info panel. - Restore the Dispatcher.prototype/confirm spies after each test in the shell spec (clearAllMocks does not restore spied methods). - Scope the with-crop TestBed configuration into per-suite beforeEach blocks. - Add data-testid to the crop-mask panels and query them by test id. - Drive the crop preset/orientation through the dropdown and buttons instead of poking the component's internal signals.
…omponents Per ANGULAR_STANDARDS, prefix every component signal member (input/output/computed/ signal/viewChild) with $. Inputs and outputs carry an explicit alias matching the original name so public binding names — and therefore parent templates — are unchanged. Updated all internal references in each component's .ts, template, and spec, plus the cross-component reads (canvas -> crop overlay $naturalCropSize, shell spec $close/$cancel, address-bar spec output names).
🤖 Bedrock Review —
|
|
Tick the box to add this pull request to the merge queue (same as
|
Summary
CleanShot.2026-06-24.at.16.03.56.mp4
Replaces the legacy Dojo
ImageEditor.jswith a new standalone Angular library@dotcms/image-editor, wired into Edit Content's binary field through anIMAGE_EDITOR_LAUNCHERseam (Angular-only path).The editor is a viewer of a dotCMS endpoint: the
<img>srcis a computed/contentAsset/image/{id}/{field}/filter/...URL, so every control change rebuildsthe URL and the server renders the adjusted image — no client-side pixel work.
State is an events-based NgRx Signal Store (
@ngrx/signals/events), composed ofone vertical
signalStoreFeatureper area of functionality (adjust, transform,crop, focal point, file info, tools, history, asset, preview, download) — each
bundling its own reducers, selectors and effects.
Scope note
This PR delivers the editor shell, the server-side preview pipeline, the panels and
the crop/focal tools, plus Download. Saving the edited image back to the field
is intentionally deferred to #36067 — the editor currently previews and downloads.
What's included
@dotcms/image-editor: root dialog, header, canvas (crossfade, zoom +pan, crop/focal overlays), side panels (Adjust / Transform / File info / History),
footer (Cancel + Download).
(Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z, Ctrl+Y), ignored while a text field is focused.
libs/edit-content(token + Angular launcher) and binary-field wiring.focal-centered aspect crop.
Preview robustness
partially-generated server response can't paint a truncated frame; incomplete
responses are detected and retried silently.
decode()+ natural-dimension gate before promoting a frame.Code organization
image-editor.constants.ts; types/models inmodels/image-editor.models.ts.store/features/with-*.feature.ts+store-utils.ts;store.tsis a thin composition.Testing
nx lint image-editor/nx lint edit-content— cleannx test image-editor— 258 passing (per-feature unit specs + store-utils + dimensions.util + integration; store-feature branch coverage ~100%)nx test edit-content— 1961 passing (28 skipped)Issues
/contentAsset/imagefilter URLsAngularImageEditorLaunchermodal is implemented, but true full-screen sizing is not working yet (follow-up there)This PR fixes: #36063