Skip to content

Commit c2f4635

Browse files
committed
feat(image-editor): UI/UX polish for the Angular image editor
- 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 - Remove the unused Storybook story and the unused legacy Dojo launcher Refs #36063
1 parent 2c623b3 commit c2f4635

44 files changed

Lines changed: 1163 additions & 552 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

core-web/libs/edit-content/src/lib/edit-content.shell.component.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,25 @@ import { ChangeDetectionStrategy, Component } from '@angular/core';
22
import { RouterModule } from '@angular/router';
33

44
import { MessageService } from 'primeng/api';
5+
import { DialogService } from 'primeng/dynamicdialog';
56
import { ToastModule } from 'primeng/toast';
67

8+
import {
9+
AngularImageEditorLauncher,
10+
IMAGE_EDITOR_LAUNCHER
11+
} from './fields/shared/image-editor-launcher';
12+
713
@Component({
814
selector: 'dot-edit-content',
915
imports: [RouterModule, ToastModule],
10-
providers: [MessageService],
16+
providers: [
17+
MessageService,
18+
// Scope the new Angular image editor to the edit-content shell so it only
19+
// activates here and never leaks into the legacy/web-component path. DialogService
20+
// is required by AngularImageEditorLauncher to open the modal.
21+
DialogService,
22+
{ provide: IMAGE_EDITOR_LAUNCHER, useClass: AngularImageEditorLauncher }
23+
],
1124
template: '<p-toast /> <router-outlet />',
1225
styleUrls: ['./edit-content.shell.component.scss'],
1326
changeDetection: ChangeDetectionStrategy.OnPush

core-web/libs/edit-content/src/lib/fields/dot-edit-content-binary-field/dot-edit-content-binary-field.component.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ import { getFileMetadata, getUiMessage } from './utils/binary-field-utils';
6363

6464
import { DEFAULT_MONACO_CONFIG } from '../../models/dot-edit-content-field.constant';
6565
import { getFieldVariablesParsed, stringToJson } from '../../utils/functions.util';
66-
import { AngularImageEditorLauncher, IMAGE_EDITOR_LAUNCHER } from '../shared/image-editor-launcher';
66+
import { IMAGE_EDITOR_LAUNCHER } from '../shared/image-editor-launcher';
6767

6868
export const DEFAULT_BINARY_FIELD_MONACO_CONFIG: MonacoEditorConstructionOptions = {
6969
...DEFAULT_MONACO_CONFIG,
@@ -99,7 +99,6 @@ type SystemOptionsType = {
9999
DotBinaryFieldStore,
100100
DotLicenseService,
101101
DotBinaryFieldValidatorService,
102-
{ provide: IMAGE_EDITOR_LAUNCHER, useClass: AngularImageEditorLauncher },
103102
{
104103
multi: true,
105104
provide: NG_VALUE_ACCESSOR,
@@ -121,7 +120,10 @@ export class DotEditContentBinaryFieldComponent
121120
readonly #dotAiService = inject(DotAiService);
122121
readonly #dialogService = inject(DialogService);
123122
readonly #destroyRef = inject(DestroyRef);
124-
readonly #imageEditorLauncher = inject(IMAGE_EDITOR_LAUNCHER);
123+
// Optional: the launcher is provided by the Angular edit-content shell, so the new
124+
// image editor only activates there. When absent (e.g. a non-Angular host), or when
125+
// isAvailable() is false, `onEditImage()` safely no-ops.
126+
readonly #imageEditorLauncher = inject(IMAGE_EDITOR_LAUNCHER, { optional: true });
125127

126128
$isAIPluginInstalled = toSignal(this.#dotAiService.checkPluginInstallation(), {
127129
initialValue: false
@@ -399,12 +401,18 @@ export class DotEditContentBinaryFieldComponent
399401
* @memberof DotEditContentBinaryFieldComponent
400402
*/
401403
onEditImage() {
404+
const launcher = this.#imageEditorLauncher;
405+
406+
if (!launcher?.isAvailable()) {
407+
return;
408+
}
409+
402410
const inode = this.contentlet?.inode;
403411
const metadata = this.contentlet
404412
? (getFileMetadata(this.contentlet) as Partial<DotFileMetadata>)
405413
: null;
406414

407-
this.#imageEditorLauncher
415+
launcher
408416
.open({
409417
inode,
410418
tempId: this.tempId,

core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/angular-image-editor.launcher.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,16 @@ describe('AngularImageEditorLauncher', () => {
3636
expect(spectator.service.isAvailable()).toBe(true);
3737
});
3838

39-
it('should open the DotImageEditorComponent with a closable, escapable dialog', () => {
39+
it('should open the DotImageEditorComponent with a headerless, closable, escapable dialog', () => {
4040
spectator.service.open(params).subscribe();
4141

4242
expect(spectator.inject(DialogService).open).toHaveBeenCalledWith(
4343
DotImageEditorComponent,
4444
expect.objectContaining({
4545
data: params,
4646
modal: true,
47+
// The editor renders its own header; PrimeNG's chrome header is hidden.
48+
showHeader: false,
4749
closable: true,
4850
closeOnEscape: true
4951
})

core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/angular-image-editor.launcher.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,15 @@ export class AngularImageEditorLauncher implements DotImageEditorLauncher {
3232
*/
3333
open(params: ImageEditorOpenParams): Observable<DotCMSTempFile | null> {
3434
const ref = this.#dialogService.open(DotImageEditorComponent, {
35-
header: undefined,
35+
// The editor renders its own header (title + close ✕), so hide PrimeNG's
36+
// chrome header to avoid a duplicate. Closing is handled by the internal ✕
37+
// (DotImageEditorHeaderComponent) and the Esc key (closeOnEscape).
38+
showHeader: false,
3639
data: params,
37-
width: 'min(92vw, 75rem)',
38-
height: '90%',
40+
// Large landscape dialog: wider than tall. Generous caps keep it big on wide
41+
// screens while staying within the viewport on smaller ones.
42+
width: 'min(96vw, 90rem)',
43+
height: 'min(96vh, 60rem)',
3944
modal: true,
4045
draggable: false,
4146
resizable: false,

core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/image-editor-launcher.token.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export type { DotImageEditorLauncher, ImageEditorOpenParams } from '@dotcms/imag
77
/**
88
* DI seam for launching the image editor from the binary field.
99
*
10-
* Providers swap implementations (Angular dialog, legacy Dojo bridge, or noop)
10+
* Providers swap implementations (the Angular dialog, or a noop fallback)
1111
* without the consuming field knowing which editor surfaces the result.
1212
*/
1313
export const IMAGE_EDITOR_LAUNCHER = new InjectionToken<DotImageEditorLauncher>(

core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,4 @@ export {
44
ImageEditorOpenParams
55
} from './image-editor-launcher.token';
66
export { AngularImageEditorLauncher } from './angular-image-editor.launcher';
7-
export { LegacyDojoImageEditorLauncher } from './legacy-dojo-image-editor.launcher';
87
export { NoopImageEditorLauncher } from './noop-image-editor.launcher';

core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/legacy-dojo-image-editor.launcher.spec.ts

Lines changed: 0 additions & 96 deletions
This file was deleted.

core-web/libs/edit-content/src/lib/fields/shared/image-editor-launcher/legacy-dojo-image-editor.launcher.ts

Lines changed: 0 additions & 65 deletions
This file was deleted.

core-web/libs/image-editor/src/lib/components/dot-image-editor-address-bar/dot-image-editor-address-bar.component.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
<div class="address-bar__group">
22
<i class="pi pi-link address-bar__link-icon" aria-hidden="true"></i>
3-
<input
4-
pInputText
5-
type="text"
6-
readonly
3+
<!-- Read-only, server-generated URL — plain text, not an editable input. -->
4+
<span
75
class="address-bar__field truncate font-mono"
8-
[value]="store.previewUrl()"
9-
data-testid="image-editor-address-field" />
6+
[title]="store.previewUrl()"
7+
data-testid="image-editor-address-field">
8+
{{ store.previewUrl() }}
9+
</span>
1010
<button
1111
pButton
1212
type="button"

core-web/libs/image-editor/src/lib/components/dot-image-editor-address-bar/dot-image-editor-address-bar.component.scss

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
gap: 1rem;
66
padding: 0.5rem 0.75rem;
77
background-color: rgba(28, 30, 38, 0.92);
8-
color: var(--surface-0, #ffffff);
8+
// Slightly-less-than-white text, matching the design's dark-chrome treatment
9+
// (resting elements at 78% white; full white is reserved for hover/active).
10+
color: rgba(255, 255, 255, 0.78);
911
}
1012

1113
.address-bar__group {
@@ -17,13 +19,14 @@
1719

1820
.address-bar__link-icon {
1921
flex: 0 0 auto;
20-
opacity: 0.7;
2122
}
2223

2324
.address-bar__field {
2425
flex: 1 1 auto;
2526
min-width: 0;
2627
max-width: 28rem;
28+
color: inherit;
29+
font-size: 0.8125rem;
2730
}
2831

2932
.address-bar__zoom-value {

0 commit comments

Comments
 (0)