Skip to content

feat(image-editor): wire Save and focal-point persistence for the new image editor#36350

Open
oidacra wants to merge 19 commits into
mainfrom
issue-36067-wire-image-editor-save
Open

feat(image-editor): wire Save and focal-point persistence for the new image editor#36350
oidacra wants to merge 19 commits into
mainfrom
issue-36067-wire-image-editor-save

Conversation

@oidacra

@oidacra oidacra commented Jun 29, 2026

Copy link
Copy Markdown
Member

Summary

Wires the new Angular image editor's Save to the temp-file flow and completes
focal-point persistence for the new Edit Content.

On Save, the editor GETs the contentAsset filter URL it already builds for preview, with
binaryFieldId=<fieldVar>&_imageToolSaveFile=true appended; the servlet stages the filtered
render as a DotCMSTempFile, the dialog closes returning it, and the field stages it (preview
refreshes; the temp id is sent as the field value on check-in — the source binary is never
overwritten). A focal point is folded into the same request (/filter/FocalPoint/fp/x,y +
overwrite=true) and committed via copyMetadata; the editor seeds the marker from the asset's
stored focal on open and treats a focal-only change as dirty.

Closes #36067.

What changed

Image editor (libs/image-editor) — new withSave feature + saveRequested/saveSucceeded/saveFailed
events + saveEditedImage GET; buildSaveUrl reuses the preview filter chain, appends the save
flags, and folds in the focal point (epsilon-compared against the seeded baseline — recenter-to-clear
and untouched-seed both handled); the dialog closes with the temp on success and stays open + surfaces
the error on failure; focal seeded on open from ImageEditorOpenParams.focalPoint; isDirty widened to
detect a focal move (epsilon-tolerant); footer Save button (+ i18n) shows a loading spinner while the
editor is busy applying a filter or saving.

Binary field (libs/edit-content)onEditImage reads the asset's stored focal
({field}MetaData.focalPoint, via parseFocalPoint) and passes it to the launcher so the marker
re-seeds on reopen; null-metadata guards (handleTempFile, getFileMetadata) so the image-tool temp
(metadata: null) renders instead of crashing.

BackendBinaryExporterServlet re-wraps the temp after copying the rendered bytes so it carries
real metadata (image/mimeType/dimensions) — it was returning metadata:null/image:false/mimeType:unknown,
which left the field showing a file icon — mirroring TempFileAPI.createTempFile;
DefaultTransformStrategy.addBinaries exposes focalPoint on the content REST read path (mirrors
BinaryViewStrategy) so the editor can re-seed.

Acceptance criteria

Test plan

Unit: buildSaveUrl (filters, focal seed-vs-centre, recenter-to-clear, epsilon), withSave (status
transitions + the built Save URL), footer Save (click, loading spinner on busy/saving), dialog
close-on-success / no-close-on-failure, focal seeding, isDirty (epsilon), parseFocalPoint,
null-metadata guards, DefaultTransformStrategy focalPoint — 334 (image-editor) + 1980 (edit-content)
passing
, lint clean, backend compiles.

Manual (local, FEATURE_FLAG_NEW_IMAGE_EDITOR=true): open the editor on a Binary field → apply a filter /
move the focal point → Save → the dialog closes; after Publish the field shows the edited image;
reopen the editor → the focal marker is restored.

Notes

oidacra added 3 commits June 29, 2026 14:49
Add a withSave feature (save events + saveEditedImage GET) that builds the
contentAsset filter URL with _imageToolSaveFile=true, closes the dialog with the
returned temp file on success and surfaces the error on failure. Fold the focal
point into the save chain (epsilon-compared against the seeded baseline), seed it
on open from ImageEditorOpenParams, widen isDirty to detect a focal move, and add a
footer Save button that shows a loading spinner while busy or saving.
…p in binary field

onEditImage reads the asset's stored focal point ({field}MetaData.focalPoint) and
passes it to the launcher so the marker re-seeds on reopen. Guard the null metadata
that image-tool temp files return so the field renders instead of crashing.
…lPoint on read

The _imageToolSaveFile save block created the temp from the empty file, so it came
back with metadata:null/image:false; re-wrap it after copying the rendered bytes so
it carries real metadata (mirrors TempFileAPI.createTempFile). Expose focalPoint on
DefaultTransformStrategy.addBinaries (mirrors BinaryViewStrategy) so the editor can
re-seed the marker on reopen.
@oidacra oidacra force-pushed the issue-36067-wire-image-editor-save branch from 920d5d6 to dedc5f4 Compare June 29, 2026 18:49
@github-actions github-actions Bot added Area : Backend PR changes Java/Maven backend code Area : Frontend PR changes Angular/TypeScript frontend code labels Jun 29, 2026
@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @oidacra's task in 2m 20s —— View job


Rollback Safety Analysis

  • Read rollback-unsafe categories reference
  • Get full PR diff
  • Analyze diff against every unsafe category
  • Apply AI: Safe To Rollback label

Result: ✅ Safe To Rollback

After analyzing the PR diff against all rollback-unsafe categories, no unsafe patterns were found.

Categories checked

Category Verdict Notes
C-1 Structural Data Model Change ✅ Safe No runonce tasks, no schema changes to contentlet, identifier, multi_tree, etc.
C-2 Elasticsearch Mapping Change ✅ Safe No changes to ESMappingAPIImpl, ESMappingUtilHelper, or mapping constants
C-3 Content JSON Model Version Bump ✅ Safe No changes to CURRENT_MODEL_VERSION or ImmutableContentlet
C-4 DROP TABLE / DROP COLUMN ✅ Safe No DDL drop statements found
H-1 One-Way Data Migration / Destructive Backfill ✅ Safe No data transformation tasks
H-2 RENAME TABLE / RENAME COLUMN ✅ Safe No rename operations
H-3 Primary Key Restructuring ✅ Safe No PK changes
H-4 New ContentType Field Type ✅ Safe No new field types registered
H-5 Binary Storage Provider Change ✅ Safe No storage provider config changes; BinaryExporterServlet changes are behavioral only (temp file re-wrap + focal-point write)
H-6 DROP PROCEDURE / FUNCTION ✅ Safe No stored procedure drops
H-7 NOT NULL Column Without Default ✅ Safe No schema changes at all
H-8 VTL Viewtool Contract Change ✅ Safe No viewtool classes modified; DefaultTransformStrategy adds a focalPoint key to the {field}MetaData map, but this is an additive change — N-1 would simply return a map without focalPoint, which is the prior behavior (gracefully absent, not broken)
M-1 Non-Broadening Column Type Change ✅ Safe No column type changes
M-2 Push Publishing Bundle Format Change ✅ Safe No bundler or handler changes
M-3 REST/GraphQL API Contract Change ✅ Safe The focalPoint key added to binary metadata responses is purely additive — existing consumers receive it as an optional extra field and N-1 simply omits it
M-4 OSGi Plugin API Breakage ✅ Safe No OSGi interface changes

Summary of backend changes

  • DefaultTransformStrategy.java — additive: exposes focalPoint in the {field}MetaData map on the content REST read path (new key, no old key removed)
  • BinaryExporterServlet.java — behavioral: re-wraps temp file after copy to populate metadata, and writes focal point onto the temp — no schema changes
  • FeatureFlagName.java / ConfigurationResource.java / dotmarketing-config.propertiesFEATURE_FLAG_NEW_IMAGE_EDITOR constant removed entirely (backend + frontend both updated); N-1 rollback would simply re-add the constant from the previous binary — no data or state affected
  • Language.properties — two new i18n keys added (additive, safe)

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

🤖 dotBot Review (Bedrock)

Reviewed 39 file(s); 12 candidate(s) → 6 confirmed, 0 uncertain (unverified, kept for review).

⚠️ Coverage capped: 0 file(s) + 6 lower-severity candidate(s) skipped (limits: 40 files, 12 candidates).

Confirmed findings

  • 🔴 Critical core-web/libs/dotcms-models/src/lib/shared-models.ts:39 — Removed feature flag breaks image editor availability
    The removal of FEATURE_FLAG_NEW_IMAGE_EDITOR from FeaturedFlags enum breaks feature flag checks required for new image editor functionality. The PR's test plan explicitly requires FEATURE_FLAG_NEW_IMAGE_EDITOR=true for manual validation, indicating backend/frontend code still relies on this flag for gating. This would cause runtime errors in feature checks and disable the editor even when configured.
  • 🟠 High core-web/libs/image-editor/src/lib/services/dot-image-editor.service.ts:140 — State-changing operation using GET method
    The code uses HttpClient.get() to trigger a save operation via '/contentAsset/image/filter' with _imageToolSaveFile=true parameter. This performs a state-changing file write via GET request, violating REST idempotency expectations and CSRF protection patterns. While dotCMS backend may have CSRF protections, using GET for writes is an anti-pattern that exposes unnecessary risk.
  • 🟠 High dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationResource.java:234 — Potential NullPointerException when copying metadata
    In BinaryExporterServlet, if contentlet.getBinaryMetadata(fieldVar) returns null, the subsequent call to metadata.copyMetadata(tempFile) will throw a NullPointerException. The code lacks a null check before accessing the metadata, leading to a crash when handling requests for content without binary metadata.
  • 🟠 High core-web/libs/image-editor/src/lib/store/features/with-save.feature.ts:73 — Focal point not added when metadata is null
    When tempFile.metadata is null, the code sets metadata to null and does not include the focalPoint, violating persistence requirements. The ternary operator returns null in the else clause, skipping focalPoint assignment. This breaks focal point saving for assets without existing metadata, directly contradicting the PR's stated goal of focal-point persistence.
  • 🟡 Medium core-web/libs/edit-content/src/lib/fields/dot-edit-content-file-field/utils/focal-point.util.ts:23 — Missing range validation for focal point coordinates
    The parseFocalPoint function in focal-point.util.ts does not validate that the parsed x and y values are within the 0-1 range required for normalized focal points. This allows invalid values (e.g., 1.5 or -0.3) to be returned, which could cause rendering issues in the image editor or persist invalid data to the backend. The current code checks for valid numbers but not their range, leaving the system vulnerable to out-of-bounds input.
  • 🟡 Medium core-web/libs/image-editor/src/lib/components/dot-image-editor/dot-image-editor.component.ts:94 — Potential memory leak from unmanaged subscription
    The subscription in DotImageEditorComponent uses takeUntilDestroyed() without providing DestroyRef, which may not properly unsubscribe when the component is destroyed. The component is not marked as standalone, requiring explicit DestroyRef injection. The current implementation lacks DestroyRef injection and argument in takeUntilDestroyed(), risking uncleaned subscriptions.

us.deepseek.r1-v1:0 · Run: #28475231277 · tokens: in: 219357 · out: 66747 · total: 286104 · calls: 66 · est. ~$0.657

Comment thread core-web/libs/image-editor/src/lib/models/image-editor.models.ts
…ata on open

onEditImage called getFileMetadata(this.contentlet), but the raw @input contentlet
carries no fieldVariable, so it could not resolve {field}MetaData and returned {} —
dropping the stored focalPoint. Pass fieldVariable explicitly (as the preview does)
so the editor seeds the saved focal point instead of resetting to centre.
- Make the focal target dot red (var(--p-red-500)) instead of the UI primary blue
  so it reads as a focal marker and contrasts against arbitrary image content.
- Observe the stage (the image's offsetParent) in addition to the image: toggling
  full-screen re-centres the image without changing its own size, which a
  ResizeObserver on the image alone never reports, leaving imageRect.x/y stale and
  the focal/crop overlays mispositioned on the full-screen -> windowed transition.
Comment thread core-web/libs/image-editor/src/lib/models/image-editor.models.ts
Comment thread core-web/libs/image-editor/src/lib/store/features/with-save.feature.ts Outdated
- DefaultTransformStrategy: log (debug) when reading the focal point custom-meta
  fails instead of silently swallowing it in the Try.
- binary-field-utils.spec: split the focal-point sentinel test so the (0,0) case and
  the malformed/single-value case are asserted separately (the latter hits the NaN
  branch, not the (0,0) sentinel).
- onEditImage: document why focalPoint is read via a narrow cast (it is exposed at
  runtime but not declared on the shared DotFileMetadata model).
Comment thread core-web/libs/dotcms-models/src/lib/shared-models.ts
Comment thread core-web/libs/dotcms-models/src/lib/page-model-change-event.ts
Comment thread dotCMS/src/main/java/com/dotcms/featureflag/FeatureFlagName.java
…Content

Removing FEATURE_FLAG_NEW_IMAGE_EDITOR made the new Edit Content always open the
new Angular image editor, so the binary-field E2E that waited for the legacy Dojo
iframe (legacy-image-editor-iframe) timed out. Assert the new editor's dialog
(image-editor-root) instead. The legacy-editor describe block (legacy Edit Content,
CONTENT_EDITOR2_ENABLED=false) is unchanged and still asserts the Dojo editor.
Comment thread core-web/libs/dotcms-models/src/lib/dot-contentlets-events.model.ts
Comment thread dotCMS/src/main/java/com/dotcms/featureflag/FeatureFlagName.java
…' test

The calendar-field 'now' TIME default test compared getDate() day-of-month values
directly (Math.abs(actualDay - expectedDay) <= 2). Across a month boundary the
timezone-shifted UTC formValue rolls to the next month, so e.g. Jun 30 vs Jul 1
reads as |30 - 1| = 29 and fails — deterministically on month-end days (today is
Jun 30). Compare the whole-day distance between the two dates instead, which is
correct across month boundaries. Unrelated to the image-editor work; surfaced on
this PR's CI run.
Comment thread core-web/libs/dotcms-models/src/lib/shared-models.ts
@oidacra oidacra added this pull request to the merge queue Jun 30, 2026
@mergify

mergify Bot commented Jun 30, 2026

Copy link
Copy Markdown

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@erickgonzalez erickgonzalez removed this pull request from the merge queue due to a manual request Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code Area : Frontend PR changes Angular/TypeScript frontend code Area : SDK PR changes SDK libraries

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[IMAGE EDITOR] Wire image editor Save, Download, and Cancel to FileFieldStore temp-file flow

2 participants