Skip to content

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

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

feat(image-editor): add Angular image editor#36236
oidacra wants to merge 3 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

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 `` `src` is a computed `/contentAsset/image/{id}/{field}/filter/...` URL, so every control change rebuilds the URL and the server renders the adjusted image. Live preview uses a two-layer crossfade with debounced sizing.

State is an events-based NgRx Signal Store (`@ngrx/signals/events`) — a deliberate departure from the repo's `withMethods` convention, chosen for the removable applied-edits list + named undo/redo (a replayable command stream).

What's included

  • Library `@dotcms/image-editor`: root dialog, header, canvas (crossfade, zoom, crop/focal overlays), side panels (Adjust / Transform / File info / History), footer.
  • Server-side preview via the filter-URL builder (single source of truth), Download, and Save-to-tempfile (`_imageToolSaveFile=true`).
  • Command history: removable applied-edits list + undo/redo, with 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 integration.
  • UX: design-aligned accordion (collapsed by default, open state persisted to localStorage), editable Adjust number fields synced with the sliders, gradient slider tracks, Shift-to-lock-aspect cropping, robust loading/error/animation handling (respects `prefers-reduced-motion`).

Testing

  • `nx lint image-editor` / `nx lint edit-content` — clean
  • `nx test image-editor` — 180 passing
  • `nx test edit-content` — 1968 passing
  • Verified live against a local dotCMS (demo content): preview, crop, focal, download, save, undo/redo, panel persistence.

Closes #36063

…Store (#36063)

New @dotcms/image-editor library — a full-screen "Edit image" modal that renders a
live, server-side preview by building dotCMS /contentAsset/image filter URLs (a viewer
of the endpoint). State is an @ngrx/signals events-based store (eventGroup/withReducer/
on/injectDispatch + rxMethod effects) with adjust/transform/crop/focalPoint/fileInfo/
zoom slices and a coalesced command history (undo/redo + removable applied edits).

- DotImageEditorComponent (OnPush) opened via PrimeNG DialogService
- Canvas with two-layer image crossfade + skeleton/spinner/error+retry
- Tool rail (move/crop/focal), accordion panels (Adjust/Transform/File info/History),
  footer (Cancel/Download/Save split button)
- IMAGE_EDITOR_LAUNCHER seam (Angular/Legacy/Noop); binary field 'Edit image' now opens
  the new editor and saves via the _imageToolSaveFile temp-file flow
- 79 edit.content.image-editor.* i18n keys; Storybook story for isolated testing

Closes #36063
@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Claude finished @oidacra's task in 1m 11s —— View job


Rollback Safety Analysis

  • Read rollback-unsafe categories reference
  • Get full PR diff
  • Analyze diff against all unsafe categories
  • Apply appropriate label

Result: ✅ Safe To Rollback

All 81 changed files are pure frontend changes:

File category Rollback-unsafe?
Angular TypeScript components / store / services / utils ✅ No — frontend-only
SCSS / HTML templates ✅ No — UI only
Jest configs / tsconfig / project.json ✅ No — build config only
dotCMS/src/main/webapp/WEB-INF/messages/Language.properties ✅ No — additive i18n keys only

No matches against any unsafe category:

  • C-1 — No runonce DB migration tasks
  • C-2 — No Elasticsearch mapping changes
  • C-3 — No contentlet_as_json model version bump
  • C-4 — No DROP TABLE / DROP COLUMN
  • H-1 through H-8 — No data migrations, renames, new field types, storage changes, or VTL viewtool contract changes
  • M-1 through M-4 — No column type changes, push bundle format changes, REST/GraphQL API contract changes, or OSGi interface changes

The PR introduces a brand-new standalone Angular library (@dotcms/image-editor) wired into the binary field via an injectable launcher token. Rolling back to the previous release simply removes the new editor; the binary field continues functioning without it (the launcher token injection is optional).

The label AI: Safe To Rollback has been added.

@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/edit-content.shell.component.ts:2-25 — Added DialogService to providers but missing DynamicDialogModule import; Angular will throw a runtime error because DialogService depends on DynamicDialogModule.

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:389-432onEditImage() calls this.$field()?.name which could be undefined; fieldName parameter may be undefined passed to launcher.

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:395-432getFileMetadata(this.contentlet) may return null; casting to Partial<DotFileMetadata> could mask a null reference when accessing metadata?.name or metadata?.contentType.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-address-bar/dot-image-editor-address-bar.component.ts:44-71copyUrl() uses navigator.clipboard.writeText without feature detection; may throw in insecure contexts (HTTP) or older browsers.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:67-73imageRect signal is undefined initially; passed to child overlays without null guard in template (though they check imageRect()). Could cause early render errors.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:120-124#resizeObserver not cleaned up in ngOnDestroy (only in #destroyRef callback). Fine, but if component is destroyed before observer is set, callback may not run.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:133-145onPendingLoaded calls img.decode() without a timeout; could hang if decode never resolves/rejects.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:152-155onPendingError dispatches previewErrored but does not clear pending URL; may cause duplicate error dispatches if error repeats.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:179-182applyFocalCrop() dispatches aspectCropApplied but does not exit tool; store should handle tool transition.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:189-201 — Zoom controls (zoomIn, zoomOut, fit) only update local zoomLevel signal; not synchronized with store's zoom signal, causing inconsistency.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.ts:210-237#measureImageRect uses getBoundingClientRect which can cause layout thrashing if called frequently; but only called on resize and load, so likely okay.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-crop-overlay/dot-image-editor-crop-overlay.component.html:1-30imageRect() used in *ngIf but also passed to child components via [imageRect] binding; if imageRect() changes rapidly, overlay may flicker.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-crop-overlay/dot-image-editor-crop-overlay.component.ts (not in diff) — Missing file in diff; cannot review for bugs like pointer event leaks or keydown handling.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-focal-overlay/dot-image-editor-focal-overlay.component.ts (not in diff) — Missing file in diff; cannot review for bugs like focal point coordinate validation.

[🟠 High] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.spec.ts:35-45 — Test overrides HTMLImageElement.prototype.decode globally; may affect other tests if not restored. Should be scoped to test suite.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.spec.ts:48flushDecode helper uses setTimeout; may introduce flakiness if promise resolution timing varies.

[🟡 Medium] core-web/libs/image-editor/src/lib/components/dot-image-editor-canvas/dot-image-editor-canvas.component.spec.ts:274dispatchedEvent helper searches event type by substring; could match unintended events if naming overlaps.

[🟡 Medium] core-web/libs/image-editor/src/lib/animations/image-editor.animations.ts — New animations; no performance issues seen, but prefer '0ms' for reduced motion as documented.

[🟡 Medium] core-web/libs/image-editor/.eslintrc.json — New ESLint config overrides rules but sets empty rules; no issues but could inherit unwanted rules from base.

[🟡 Medium] core-web/libs/image-editor/jest.config.ts — New Jest config; standard, no issues.

[🟡 Medium] core-web/libs/image-editor/project.json — New Nx project; tags scope:dotcms-ui may need verification with team conventions.

[🟡 Medium] core-web/libs/image-editor/src/index.ts — Public API exports; ensure all exported symbols are intended for external use.

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/angular-image-editor.launcher.ts:59DialogService.open returns DynamicDialogRef; onClose may emit undefined on backdrop click. Handled via map((tempFile?: DotCMSTempFile) => tempFile ?? null).

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/angular-image-editor.launcher.spec.ts:29 — Mock DialogService.open returns { onClose }; should also mock close method if component calls it.

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/image-editor-launcher.token.ts:1-16 — New DI token; injection is optional in binary field, which is correct.

[🟠 High] core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.spec.ts:137imageEditorLauncherMock.isAvailable.mockReturnValue(true) in beforeEach but also set in beforeEach block; redundant but harmless.

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.spec.ts:304-334 — Tests for edit image launcher; missing test for when isAvailable() returns false (should no-op).

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.spec.ts:337-359 — Tests for apply edited temp file and cancelled editor; good coverage.

[🟠 High] core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:120#imageEditorLauncher injected as optional; if isAvailable() returns false, onEditImage no-ops. Should also hide edit button in UI? Not in diff.

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts:395-432mimeType parameter uses metadata?.contentType which could be undefined; launcher may expect string.

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/angular-image-editor.launcher.ts:30-58open method sets dismissableMask: false but closable: true and closeOnEscape: true; user can close via Esc or X, but not backdrop click. Intentional.

[🟡 Medium] core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/angular-image-editor.launcher.ts:47styleClass: 'dot-image-editor-dialog'; CSS class not defined in diff;


Run: #27848501771 · tokens: in: 21376 · out: 2048 · total: 23424

@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
- Accordion: design-aligned header (13px title, 11px subtitle, primary icon
  chip when open), collapsed by default, open sections persisted to localStorage
- Adjust values are editable number fields synced with the sliders (clamped)
- Undo/redo keyboard shortcuts on the dialog (Ctrl/Cmd+Z, Ctrl/Cmd+Shift+Z,
  Ctrl+Y); ignored while a text field is focused
- Crop: Shift-drag a corner locks the starting aspect ratio
- Address bar text/icon use the design's 78%-white dark-chrome tone
- Canvas/footer layout + padding, gradient adjust sliders, taller dialog
- Store: split panel events into Adjust/Transform/File-info groups, one
  withReducer per group, and move async effects to withEventHandlers
- Remove unused Storybook wiring (story + .storybook glob) and the unused
  legacy Dojo and noop launchers

Refs #36063
@oidacra oidacra force-pushed the issue-36063-image-editor-build-dotimageeditorcomponent-modal-s branch from a1d5b80 to 5f38ef3 Compare June 19, 2026 14:51
…y/focal fixes

- Accordion: flatten app-wide via CustomLaraPreset (square corners, opaque
  sticky section headers, animated chevron); smaller control labels/values
- Transform: editable Scale/Rotate number inputs; Scale now resizes
  (scale% x natural size) instead of being a no-op
- Preview: store-owned auto-retry on transient load failure, decode() +
  natural-dimension completeness guard, hidden pending preloader so
  half-painted frames never show
- History: removing an applied edit replays the remaining flow (field-level
  delta replay) instead of leaving stale effects baked in; de-duplicated and
  shrunk applied-edit labels
- Focal point: set live on drag (no Set click), no preview reload; focal-
  centered aspect crop (1:1 / 16:9 / 4:3) presented in the canvas dark bar;
  removed the no-op FocalPoint preview filter (save-time anchor only)
- File info: Original Size row
- Store: split panel events into Adjust/Transform/File-info groups with
  per-group reducers; effects via withEventHandlers
- i18n: remove orphaned history.category.*, transform.aspect, focal.done keys

Refs #36063
@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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Frontend PR changes Angular/TypeScript frontend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[IMAGE EDITOR] Build DotImageEditorComponent modal shell and image-editor signalStore

1 participant