Skip to content

feat(image-editor): add Angular image editor#36236

Open
oidacra wants to merge 27 commits into
mainfrom
issue-36063-image-editor-build-dotimageeditorcomponent-modal-s
Open

feat(image-editor): add Angular image editor#36236
oidacra wants to merge 27 commits into
mainfrom
issue-36063-image-editor-build-dotimageeditorcomponent-modal-s

Conversation

@oidacra

@oidacra oidacra commented Jun 18, 2026

Copy link
Copy Markdown
Member

Summary

CleanShot.2026-06-24.at.16.03.56.mp4

Replaces the legacy Dojo ImageEditor.js with a new standalone Angular library
@dotcms/image-editor, wired into Edit Content's binary field through an
IMAGE_EDITOR_LAUNCHER seam (Angular-only path).

The editor is a viewer of a dotCMS endpoint: the <img> src is a computed
/contentAsset/image/{id}/{field}/filter/... URL, so every control change rebuilds
the 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 of
one vertical signalStoreFeature per 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

  • Library @dotcms/image-editor: root dialog, header, canvas (crossfade, zoom +
    pan, crop/focal overlays), side panels (Adjust / Transform / File info / History),
    footer (Cancel + Download).
  • Server-side preview via the filter-URL builder (single source of truth) and Download.
  • Command history: removable applied-edits list + undo/redo, keyboard shortcuts
    (Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z, Ctrl+Y), ignored while a text field is focused.
  • Launcher seam in libs/edit-content (token + Angular launcher) and binary-field wiring.
  • Canvas interactions: zoom with drag-to-pan, fit-to-screen, crop-to-current-view,
    focal-centered aspect crop.

Preview robustness

  • Preview fetched as a complete, verified blob (local object URL), so a
    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

  • Constants in one image-editor.constants.ts; types/models in models/image-editor.models.ts.
  • Store split into store/features/with-*.feature.ts + store-utils.ts; store.ts is a thin composition.

Testing

  • nx lint image-editor / nx lint edit-content — clean
  • nx test image-editor258 passing (per-feature unit specs + store-utils + dimensions.util + integration; store-feature branch coverage ~100%)
  • nx test edit-content1961 passing (28 skipped)
  • Verified live (local dotCMS, demo content): preview, crop, focal, zoom/pan, fit, download, undo/redo (incl. Cmd+Z), panel persistence.

Issues

@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @oidacra's task in 6m 41s —— View job


Rollback Safety Analysis

  • Read rollback-unsafe categories reference
  • Get full PR diff
  • Analyze diff against all unsafe categories
  • Post result and apply label

Result: ✅ Safe to Roll Back

Label applied: AI: Safe To Rollback

Scope of changes checked:

File Change type Rollback risk
dotCMS/src/main/java/com/dotcms/featureflag/FeatureFlagName.java Added constant FEATURE_FLAG_NEW_IMAGE_EDITOR ✅ Additive — N-1 simply lacks the constant; no runtime impact
dotCMS/src/main/java/com/dotcms/rest/api/v1/system/ConfigurationResource.java Added the new flag to the feature-flag whitelist ✅ Additive — N-1 won't expose this flag key, which was always false by default anyway
core-web/libs/image-editor/** (all new files) New Angular library @dotcms/image-editor ✅ New code behind feature flag — N-1 never loads it
core-web/libs/edit-content/** Wiring via IMAGE_EDITOR_LAUNCHER DI seam ✅ Feature-flag gated; N-1 falls back to the legacy Dojo editor
core-web/libs/dotcms-models/src/lib/shared-models.ts Added FEATURE_FLAG_NEW_IMAGE_EDITOR to FeaturedFlags enum ✅ Additive TypeScript enum entry

Categories checked and cleared:

  • C-1 Structural Data Model — no DB migration tasks, no data movement
  • C-2 ES Mapping — no Elasticsearch changes
  • C-3 Content JSON version bump — no ImmutableContentlet / CURRENT_MODEL_VERSION changes
  • C-4 DROP TABLE/COLUMN — none
  • H-1 through H-8 — no renames, PK changes, data transformations, VTL viewtool changes, or binary storage changes
  • M-1 through M-4 — no column type changes, push bundle format changes, REST API contract changes (the whitelist addition is purely additive), or OSGi interface changes

Why rolling back is safe: On N-1, FEATURE_FLAG_NEW_IMAGE_EDITOR is absent from the codebase. Since its default is false, the binary field in Edit Content v2 falls back to the existing Dojo editor — the same behavior as before this PR. No data was written, migrated, or transformed; no external API consumers were broken.

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

🤖 Bedrock Review — deepseek.v3.2

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:125#imageEditorLauncher is injected with { optional: true } but onEditImage() doesn't handle the case where it's null (when isAvailable() returns false). The method will call launcher.open() on null, causing a runtime error. This matters because the feature flag could be off, or the launcher might not be provided in non-Angular contexts.

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:395 — The onEditImage method calls launcher.open() without checking if launcher is defined after the !launcher?.isAvailable() guard. If launcher is null but isAvailable() would be false, the guard passes (because !null?.isAvailable() is true), then the method proceeds to the legacy path. This is correct, but the comment on line 125 incorrectly states it "safely no-ops". It should clarify that the fallback works.

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/angular-image-editor.launcher.ts:34#enabled signal uses initialValue: false. This means isAvailable() returns false until the server responds, causing a brief flash of the legacy editor even if the feature flag is enabled. This could cause a race condition where the user clicks "edit" before the flag loads, triggering the legacy editor incorrectly. Consider if this is acceptable or if the UI should be disabled until the flag is known.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:452 — The #cropOverlay view child is typed as DotImageEditorCropOverlayComponent but the template uses dot-image-editor-crop-overlay without a template reference variable (#). The view child will not find the component instance, causing applyCrop() and cancelCrop() calls in the footer to fail. This matters because the crop overlay's apply/cancel actions will be broken.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:454 — The #focalOverlay view child has the same issue: no template reference variable, so it will be undefined. The focal overlay interactions may fail.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:456 — The #displayImg view child is used to observe the image's rendered rect via ResizeObserver. However, the displayImg element is referenced in the template only by #displayImg on the <img> tag, which is correct. Ensure the ResizeObserver is properly cleaned up in DestroyRef to prevent memory leaks.

[🟠 High] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.spec.ts:40 — The test suite globally mocks URL.createObjectURL and URL.revokeObjectURL. This is a shared resource; other tests in the same test run may rely on the real implementation. This could cause flaky tests or false positives. It's safer to mock per-test or restore after each test.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.spec.ts:45 — The test stubs HTMLImageElement.prototype.decode globally. This affects all tests running in the same context, potentially breaking other specs. Use jest.spyOn(instance, 'decode') instead.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.spec.ts:48naturalWidth and naturalHeight are defined as getters on the prototype, affecting all image elements. This could cause unrealistic test behavior elsewhere. Prefer mocking per instance.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.spec.ts:344 — The dispatchedEvent helper function searches for events by type suffix, which is fragile if event types change. It's used across multiple tests; a change in event naming could break all tests. Consider a more robust method.

[🟡 Medium] core-web/libs/edit-content/src/lib/edit-content.shell.component.ts:25DialogService is provided at the component level. This is correct for scoping, but ensure that any parent injector doesn't already provide DialogService (which could cause conflicts). The comment explains the intent, which is good.

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.spec.ts:359 — The test 'should fall back to the legacy Dojo editor when the new editor is disabled' mocks DotBinaryFieldEditImageService.prototype.openImageEditor. This modifies the prototype, which could affect other tests running in the same suite. Use jest.spyOn(instance, 'openImageEditor') instead.

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/angular-image-editor.launcher.spec.ts:15featureFlag$ is a BehaviorSubject but the test uses featureFlag$.next(false) to simulate the flag being off. However, the service's #enabled signal is derived from getFeatureFlagWithDefault, which returns the subject. Changing the subject after service creation tests the reactive behavior, which is correct. Ensure the service is recreated for each test to avoid state leakage.

The PR introduces a new image editor with a feature flag and fallback to legacy. The changes are mostly clean, but there are a few issues with optional injection handling, view child references, and test pollution that need attention.


Run: #28042028043 · tokens: in: 21199 · out: 1407 · total: 22606

@oidacra oidacra force-pushed the issue-36063-image-editor-build-dotimageeditorcomponent-modal-s branch from 03448ba to c2f4635 Compare June 18, 2026 21:27
@oidacra oidacra force-pushed the issue-36063-image-editor-build-dotimageeditorcomponent-modal-s branch from c2f4635 to a1d5b80 Compare June 19, 2026 14:46
@oidacra oidacra force-pushed the issue-36063-image-editor-build-dotimageeditorcomponent-modal-s branch from a1d5b80 to 5f38ef3 Compare June 19, 2026 14:51
@oidacra oidacra changed the title feat(image-editor): add Angular image editor with events-based signalStore feat(image-editor): add Angular image editor Jun 19, 2026
@oidacra oidacra marked this pull request as ready for review June 22, 2026 17:14
@oidacra oidacra requested review from hmoreras and nicobytes June 22, 2026 17:15
oidacra added 14 commits June 25, 2026 10:15
…+ download only)

Saving the edited image back to the field is its own issue, so remove the
real-save path from this PR cleanly (no dead scaffolding):

- service: drop saveEditedImage + persistFocalPoint (and #toTempFile)
- store: replace withSave with withDownload (only the download$ effect); drop the
  save events (saveRequested/saveAsRequested/saveSucceeded/saveFailed), the
  saveStatus/savedTempFile state, SaveStatus type and SaveEditedImageResponse;
  isBusy now reflects only previewStatus
- footer: drop the Save / Save-as split button (Cancel + Download remain)
- root dialog: no longer closes with a saved temp file (closes via Cancel/Esc)
- the Angular launcher already maps onClose -> null, so open() now resolves null
- remove the orphaned footer.save/saving/save-as i18n keys
- prune the corresponding tests

The editor is now preview + edit + download; the save issue reintroduces the
save flow as a cohesive unit. image-editor 258 tests green; edit-content
unaffected (1961).

Refs #36063
- Repurpose the canvas maximize control as a full-screen toggle that
  expands the dialog to the viewport and back, easing the resize and
  honouring prefers-reduced-motion; move fit-to-screen onto the
  zoom-value control so it is preserved.
- Resize the host PrimeNG dialog via the injected Dialog instance
  (container()) rather than a DOM query.
- Group the editor's transient view state into one withView feature
  (active tool + isFullscreen), replacing withTools.
- Consolidate full-screen style/transition constants into
  image-editor.constants.ts; add full-screen i18n keys.

Refs #36062
…_IMAGE_EDITOR

The new @dotcms/image-editor opens only when the flag is on; otherwise the
binary field falls back to the legacy Dojo image editor, so behavior is
unchanged by default.

- Frontend: add FEATURE_FLAG_NEW_IMAGE_EDITOR to FeaturedFlags; the Angular
  launcher's isAvailable() resolves the flag via DotPropertiesService;
  onEditImage falls back to the legacy editor when off.
- Backend: declare the flag in FeatureFlagName, expose it in
  ConfigurationResource (boolean set + white list), default it false in
  dotmarketing-config.properties.

Refs #36062
The crop box is drawn in the displayed (flipped/rotated) preview's
coordinates, but buildFilterChain emitted Crop first, so the server cropped
the original image and then flipped it — mirroring the selected region (a
left crop returned the right side under horizontal flip). Emit Crop after
Rotate/Flip so it acts on the image as displayed, matching the legacy editor
which appends Crop to the already-transformed chain.

Refs #36066
… primary, Discard outlined)

Mirror the edit-content unsaved-changes prompt (unsavedChangesGuard) so the
"Discard changes?" confirmation no longer renders two primary buttons. Keep
editing is the primary (accept) action; Discard is the secondary outlined
(reject) action. Dismissals (X / ESC / mask click) now keep editing instead of
discarding. Frontend-only: reuses existing message keys.
The focal overlay converted a painted (zoom-scaled) pointer offset against
imageRect, which is in unscaled (logical) px, so clicks landed off-target at
any zoom other than 100%. Pass the stage zoom scale to the overlay and divide
the painted offset by it in #setFromClient, fixing both click and drag
placement while zoomed in or out.
…oter bands

Switch the editor canvas from dark chrome to a light theme: the image viewer is
white and the address-bar header and footer bands sit one shade darker than white
(surface-100) with muted dark text/icons and hairline dividers. The focal aspect
pills and error overlay are re-toned for the light surface; the floating tool rail
stays dark over the image for contrast.
…next to close

Relocate the full-screen toggle from the canvas address bar to the editor header,
grouped with the close (X) button as the dialog's window controls. The header now
dispatches imageEditorViewEvents; the address bar keeps the URL, zoom and undo/redo
controls. Tests moved accordingly.
…oint, smooth drag

- Remove the Focal Point tool entirely (overlay component, with-focal-point store
  feature, focal events/state/models, focalPointPop animation, nudge constants and
  the tool-rail entry) — out of scope for this editor.
- Move aspect ratios into the crop tool: Free / 1:1 / 16:9 / 4:3 reshape and lock
  the crop box, with editable width/height pixel inputs (disabled on Free).
- Replace the empty gray footer band with a floating action bar revealed on hover.
- Fix crop drag/resize jank: coalesce pointer moves to one update per animation
  frame off the Angular zone, give the size readout value-based equality so a move
  does not re-render the inputs, and replace the viewport-sized box-shadow dim with
  four cheap solid panels (flat 60fps, dim stays visible while dragging).
…th crop/grab tools

- Replace all PrimeIcons and the inline tool-rail SVGs with the official Material
  Symbols font (global; projected into PrimeNG buttons via #icon templates,
  pButton child content, and toggleButton #content).
- Move the move(grab) + crop tools out of the floating tool rail (now deleted) into
  the address bar, restyled as UVE toolbar-center rounded pills: tools | URL | zoom.
  The URL pill grows to fill, pushing the tool and zoom pills to the edges; the
  active tool renders as a white circle inside its pill (the editor's mode).
- White header band; the dialog window-control icons (open_in_full / close) use the
  Material Symbols size (Tailwind ! to beat the global 24px).
…verflows header

- Measure the image via its layout box (offsetLeft/Top/Width/Height) instead of
  getBoundingClientRect()/scale. The stage's transform transition let a mid-flight
  measurement stick ~2% small, so the default crop box fell a few px short of the
  image edges; the layout box is transform-independent and always matches.
- Address-bar pills no longer shrink (flex: 0 0 auto) and the URL pill flexes from
  basis 0, so a long filter-chain URL truncates instead of pushing the zoom and
  undo/redo controls out of the header.
…n zoomed

- Address bar: use --p-surface-* tokens (PrimeNG 21) instead of the undefined
  --surface-* that fell back to hardcoded gray-200. Pill fill -> surface-100,
  active tool -> primary-50/700 tint (matches the design's .iem-iconbtn.active),
  border/divider -> theme slate tokens.
- Canvas: constrain the pan offset so a zoomed image always covers the stage;
  dragging can no longer pull empty space past the top/left (or bottom/right)
  edge. Re-clamp on zoom in/out.
…pdown

- Add `avif` compression mode -> builds the libvips-only `/filter/avif/avif_q/{q}`
  chain (same 0..100 quality as jpeg/webp). Registered lowercase as `avif` in
  VipsImageFilterApiImpl; requires IMAGE_API_USE_LIBVIPS=true on the server.
- Replace the compression p-selectButton with a p-select dropdown now that there
  are five options (none/auto/jpeg/webp/avif); translated item/selectedItem
  templates, appendTo body.
- Wire model (CompressionMode, FilterName), COMPRESSION_LABELS, i18n key, and
  builder/panel tests.
…change

main dropped DotPropertiesService.getFeatureFlagWithDefault(key, default) in
favor of getFeatureFlag(key): Observable<boolean> (the convention the other
new-editor flags use). The rebase surfaced this as a build break in the image
editor launcher. Switch to getFeatureFlag; the flag still ships as false in
dotmarketing-config.properties for rollback safety, and toSignal keeps it off
until the server replies.
@oidacra oidacra force-pushed the issue-36063-image-editor-build-dotimageeditorcomponent-modal-s branch from 43fdfd5 to fe49dea Compare June 25, 2026 14:34
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🤖 Bedrock Review — qwen.qwen3-next-80b-a3b

New Issues

  • 🔴 Critical: core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:398fileName: this.contentlet?.fileName ?? metadata?.name may pass null to ImageEditorOpenParams.fileName, which is non-optional in @dotcms/image-editor — causes runtime crash when editor opens
  • 🟠 High: core-web/libs/image-editor/src/lib/components/dot-image-editor-address-bar/dot-image-editor-address-bar.component.ts:102navigator.clipboard.writeText() called without user gesture validation — fails silently in Safari and strict privacy browsers, no fallback or user feedback beyond toast
  • 🟡 Medium: core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:400getFileMetadata(this.contentlet) returns Partial<DotFileMetadata> but fileName is assigned directly — if metadata.name is undefined (not null), fileName becomes undefined, which is not handled by ImageEditorOpenParams
  • 🟡 Medium: core-web/libs/image-editor/src/lib/components/dot-image-editor-address-bar/dot-image-editor-address-bar.component.ts:102navigator.clipboard.writeText() called without checking document.hasFocus() or user activation — violates browser security policy in many contexts
  • 🟡 Medium: core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/angular-image-editor.launcher.ts:58dismissableMask: false is set but closeOnEscape: true is also set — user can still close dialog via Escape key, which may bypass save intent; no confirmation or unsaved edit warning
  • 🟡 Medium: core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.tsstageTransform() uses zoomLevel() and panOffset() without bounds checking — may cause CSS transform overflow or layout thrashing on extreme values

Existing

  • 🔴 Critical: core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:409 — fileName may be null when both contentlet.fileName and metadata.name are null, but ImageEditorOpenParams.fileName is non-optional — causes runtime crash
  • 🟡 Medium: core-web/libs/image-editor/src/lib/components/dot-image-editor-address-bar/dot-image-editor-address-bar.component.ts:103 — navigator.clipboard.writeText() called without user gesture or focus check — may fail silently in Safari and other browsers
  • 🟡 Medium: core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:147 — stageTransform() uses zoomLevel() and panOffset() without bounds checking — may cause CSS transform overflow or layout thrashing

Resolved

  • core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.spec.ts:301 — Legacy dotBinaryFieldEditImageService.openImageEditor test removed; replaced with launcher-based test
  • core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.spec.ts:145NgZone and SpyObject removed from test setup; replaced with mock launcher
  • core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:390 — Legacy openImageEditor call now gated by isAvailable() check; no longer called unconditionally

Run: #28177839653 · tokens: in: 21843 · out: 1361 · total: 23204

…ive zoom %, panels open by default

- Crop bar: replace the aspect-preset button row with a dropdown (Free/1:1/16:9/4:3)
  and add a landscape/portrait orientation toggle. cropAspect is now derived as the
  preset ratio inverted for portrait; orientation is disabled for Free and 1:1.
- Zoom readout: show the scale relative to the image's NATURAL pixels
  (fitRatio x zoomLevel), so a huge image shown whole reads as e.g. 30% instead of a
  misleading 100%; 100% means 1:1. Internal zoom mechanics unchanged.
- Accordion panels: default to all sections open (adjust/transform/fileinfo/history)
  when there is no stored layout, instead of all collapsed.
- Add the missing crop aspect/width/height i18n keys plus orientation labels.
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🤖 Bedrock Review — qwen.qwen3-next-80b-a3b

New Issues

  • 🔴 Critical: core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:409fileName may be null when both contentlet.fileName and metadata.name are null, but ImageEditorOpenParams.fileName is non-optional — causes runtime crash
  • 🟠 High: core-web/libs/image-editor/src/lib/components/dot-image-editor-address-bar/dot-image-editor-address-bar.component.ts:102navigator.clipboard.writeText() called without user gesture validation — fails silently in Safari and strict privacy browsers
  • 🟡 Medium: core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:400getFileMetadata(this.contentlet) returns Partial<DotFileMetadata> — if metadata.name is undefined, fileName becomes undefined and breaks ImageEditorOpenParams
  • 🟡 Medium: core-web/libs/image-editor/src/lib/components/dot-image-editor-address-bar/dot-image-editor-address-bar.component.ts:102navigator.clipboard.writeText() called without checking document.hasFocus() or user activation — violates browser security policy
  • 🟡 Medium: core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:147stageTransform() uses zoomLevel() and panOffset() without bounds checking — may cause CSS transform overflow or layout thrashing

Existing

  • 🔴 Critical: core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:398 — fileName may be null when metadata.name is undefined, but ImageEditorOpenParams.fileName is non-optional — causes runtime crash
  • 🟠 High: core-web/libs/image-editor/src/lib/components/dot-image-editor-address-bar/dot-image-editor-address-bar.component.ts:102 — navigator.clipboard.writeText() called without user gesture validation — fails silently in Safari and strict privacy browsers
  • 🟡 Medium: core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:400 — getFileMetadata(this.contentlet) returns Partial — if metadata.name is undefined, fileName becomes undefined and breaks ImageEditorOpenParams
  • 🟡 Medium: core-web/libs/image-editor/src/lib/components/dot-image-editor-address-bar/dot-image-editor-address-bar.component.ts:102 — navigator.clipboard.writeText() called without checking document.hasFocus() or user activation — violates browser security policy
  • 🟡 Medium: core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:147 — stageTransform() uses zoomLevel() and panOffset() without bounds checking — may cause CSS transform overflow or layout thrashing

Resolved

  • core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:409 — now checks contentlet?.fileName ?? metadata?.name but still lacks null guard for fileName — issue not resolved, remains critical
  • core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:398 — same as above, still broken

Run: #28179069572 · tokens: in: 22100 · out: 1377 · total: 23477

@oidacra

oidacra commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

Love this so much and am happy to ship as is but here are some of my thoughts:

  • vertical "phone" crop as well ✅ fixed
  • the image is 2752 x 1536 but my zoom says I am viewing at 100% ✅ fixed
  • Transform and file info should be open be default ✅ now opened by default
  • We now have avif for compression as well. ✅ added AVIF.
  • We now have different crop options I will create an issue for the smartcrop only, we need add a button for that (design)
  • I don't see focalpoint picker anywhere, am I missing it same as preview, I will create a issue for that one
  • Where is the save button to save the new rendition to the content all the flow of saving, is an issue [IMAGE EDITOR] Wire image editor Save, Download, and Cancel to FileFieldStore temp-file flow #36067

…tor state)

- Address bar: add an "open in new tab" button in the URL pill that opens the
  current preview URL in a new tab (same absolute URL the copy action uses).
- Re-add the focal point as a canvas tool: a header toggle next to crop plus a
  draggable marker overlay that records a normalized 0..1 point in the store
  (withFocalPoint reducer; not in the filter chain or history).
- The focal point is intentionally NOT persisted on its own. In dotCMS the
  focal write only stages to a temp slot (FocalPointImageFilter prepends TMP::,
  committed at check-in), so it belongs to the Save flow (#36067). The marker
  just records the point in editor state for Save to persist later.
@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🤖 Bedrock Review — qwen.qwen3-next-80b-a3b

New Issues

  • 🟠 High: core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:404fileName: this.contentlet?.fileName ?? metadata?.name may be undefined when metadata.name is undefined, but ImageEditorOpenParams.fileName is non-optional — causes runtime crash in Angular component
  • 🟠 High: core-web/libs/image-editor/src/lib/components/dot-image-editor-address-bar/dot-image-editor-address-bar.component.ts:117navigator.clipboard.writeText() called without user gesture validation — fails silently in Safari and strict privacy browsers
  • 🟡 Medium: core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:147stageTransform() uses zoomLevel() and panOffset() without bounds checking — may cause CSS transform overflow or layout thrashing

Existing

  • 🟡 Medium: core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:400getFileMetadata(this.contentlet) returns Partial<DotFileMetadata> — if metadata.name is undefined, fileName becomes undefined and breaks ImageEditorOpenParams
  • 🟡 Medium: core-web/libs/image-editor/src/lib/components/dot-image-editor-address-bar/dot-image-editor-address-bar.component.ts:102navigator.clipboard.writeText() called without checking document.hasFocus() or user activation — violates browser security policy
  • 🟡 Medium: core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:147stageTransform() uses zoomLevel() and panOffset() without bounds checking — may cause CSS transform overflow or layout thrashing

Resolved

  • core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:398fileName null check now handled via ?? metadata?.name and optional injection guard
  • core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:409fileName null path now guarded by ImageEditorOpenParams input validation via launcher
  • core-web/libs/image-editor/src/lib/components/dot-image-editor-address-bar/dot-image-editor-address-bar.component.ts:102navigator.clipboard.writeText() now wrapped in try/catch with fallback toast (non-blocking error handling)

Run: #28191502664 · tokens: in: 22233 · out: 954 · total: 23187

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

Projects

Status: No status

2 participants