Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
214200c
Refactor dot-usage-shell component for improved UI and structure
fmontes Dec 24, 2025
f3201ce
Update dot-usage-shell component for improved skeleton loading and st…
fmontes Dec 24, 2025
81bd166
Enhance dot-usage-shell component with last updated timestamp
fmontes Dec 24, 2025
66503e6
Refactor dot-usage component tests to use structured metrics format
fmontes Dec 24, 2025
879ce37
Refactor dot-usage service and shell component tests to utilize new H…
fmontes Dec 24, 2025
67e26d1
Add dot-usage service and tests for usage summary functionality
fmontes Dec 24, 2025
317f1ad
Enhance DotUsageShellComponent tests to improve error handling and lo…
fmontes Dec 24, 2025
daa6ae1
Add informational message to DotUsageShellComponent for analytics upd…
fmontes Dec 24, 2025
625dde3
Enhance DotUsageShellComponent with accessibility improvements and er…
fmontes Dec 24, 2025
c90d04b
add layout tree
fmontes Dec 24, 2025
1c31dfb
Implement zoom and pan functionality in EditEmaEditor component
fmontes Dec 25, 2025
162a545
Refactor EditEmaEditor component to improve message handling and ifra…
fmontes Dec 25, 2025
4453853
Refactor EditEmaEditor component: rename zoom and pan method, update …
fmontes Dec 25, 2025
8615a44
Refactor EditEmaEditor component: rename zoom and pan method, update …
fmontes Dec 26, 2025
7fce1e8
Refactor DotUveBridgeService: streamline imports and simplify sendMes…
fmontes Dec 26, 2025
a5d3e22
Enhance EmaPageDropzone component: add zoomLevel input and adjust pos…
fmontes Dec 26, 2025
33fbe2b
Update DotUvePaletteComponent: change icon in tab header and remove u…
fmontes Dec 26, 2025
dfb4695
Refactor DotUvePaletteComponent: improve container label retrieval an…
fmontes Dec 26, 2025
25cb615
Enhance DotUvePaletteComponent: add contentlet nodes to layout tree s…
fmontes Dec 26, 2025
7f5076c
Update DotUvePaletteComponent: enhance tree display with overflow han…
fmontes Dec 26, 2025
d9f9ad9
DotUveDragDropService: clean up
fmontes Dec 26, 2025
b8c3ded
Refactor DotUveActionsHandlerService: streamline imports and remove u…
fmontes Dec 26, 2025
6f3469c
Enhance DotUvePaletteComponent and EditEmaEditor: add node selection …
fmontes Dec 26, 2025
db80e44
Adjust scroll position calculation in EditEmaEditor to account for zo…
fmontes Dec 26, 2025
7a4dd9b
Enhance DotUvePaletteComponent: implement drag-and-drop functionality…
fmontes Dec 26, 2025
9336d55
Integrate DotPageLayoutService into DotUvePaletteComponent and withSa…
fmontes Dec 26, 2025
bfeae75
Update _tree.scss and DotUvePaletteComponent: adjust tree node margin…
fmontes Dec 26, 2025
5b86cfd
Enhance EditEmaEditor: implement dynamic contentlet form generation w…
fmontes Dec 27, 2025
6a0b2fc
Enhance EditEmaEditor: add hidden inode field to form, implement load…
fmontes Dec 27, 2025
a70a80e
Refactor DotUvePaletteComponent: replace tree structure with DotRowRe…
fmontes Dec 28, 2025
11b3f7b
Enhance DotRowReorderComponent: implement expandable row functionalit…
fmontes Dec 28, 2025
06031dc
Update withSave function: add logic to re-fetch page content after sa…
fmontes Dec 28, 2025
34166dc
Enhance DotRowReorderComponent: implement column drag-and-drop functi…
fmontes Dec 28, 2025
9a05ac9
Enhance DotRowReorderComponent: add edit row dialog functionality, al…
fmontes Dec 28, 2025
01e5b4c
Enhance DotRowReorderComponent: add functionality to edit column styl…
fmontes Dec 28, 2025
a2787aa
Enhance DotRowReorderComponent: add column drag/sort animations to im…
fmontes Dec 28, 2025
22fdb67
Enhance EditEmaEditor: add cancel functionality to reset selected con…
fmontes Dec 28, 2025
0ebaca4
Refactor DotEmaShellComponent and update routing: streamline service …
fmontes Dec 28, 2025
def181c
Update styles in EditEmaEditor and TemplateBuilder: adjust grid colum…
fmontes Dec 28, 2025
de10f91
clean up state management
fmontes Dec 28, 2025
a9c1beb
Enhance EditEmaEditor and Zoom Controls: Introduce a sticky toolbar w…
fmontes Dec 28, 2025
caaed75
Implement UVE Refactor: Introduce a new service architecture by extra…
fmontes Dec 28, 2025
08968fd
Refactor updateRows method: Move from withSave.ts to withLayout.ts fo…
fmontes Dec 28, 2025
b5879c2
Enhance UVE Refactor Handoff: Add sections for palette UI visual grou…
fmontes Dec 28, 2025
07f3169
Address responsive preview issues caused by zoom implementation: docu…
fmontes Dec 28, 2025
ca0acce
Refactor DotUveContentletToolsComponent: Implement hover and selected…
fmontes Dec 30, 2025
5f38afe
Implement palette toggle functionality in EditEmaEditor: Add button t…
fmontes Dec 30, 2025
325fab0
Add right sidebar toggle functionality in EditEmaEditor: Introduce a …
fmontes Dec 30, 2025
c2c1edf
Update right sidebar toggle in EditEmaEditor: Replace icon with SVG i…
fmontes Dec 30, 2025
c39211a
Implement URL copy functionality in EditEmaEditor: Add a button to co…
fmontes Dec 30, 2025
b52fad0
Enhance UVE Refactor Handoff: Implement dual overlay system for conte…
fmontes Dec 30, 2025
d2ffc36
Add draggable attribute to toolbar icons in EditEmaEditor: Update lef…
fmontes Dec 30, 2025
b8f0e8c
Refactor right sidebar visibility in EditEmaEditor: Update sidebar re…
fmontes Dec 30, 2025
18a1658
Refactor spacing variables in SCSS files: Remove unused spacing varia…
fmontes Dec 30, 2025
674277d
Remove unused padding from editor content preview in SCSS file: Strea…
fmontes Dec 30, 2025
7fab8f9
Refactor height calculation in DotUveIframeComponent: Implement a mor…
fmontes Dec 30, 2025
38cddee
fix(edit-ema): replace unreliable iframe height calculation with post…
fmontes Dec 30, 2025
a2f76a1
refactor(edit-ema): enhance iframe styling and remove unused contentl…
fmontes Dec 31, 2025
83b052e
feat(edit-ema): integrate right sidebar state management into uveStore
fmontes Dec 31, 2025
4216d09
refactor(edit-ema): clean up event listener for iframe height reporting
fmontes Dec 31, 2025
88c8c54
fix(UVE_REFACTOR_HANDOFF): resolve responsive preview issues caused b…
fmontes Dec 31, 2025
b42aefe
move handoff doc
fmontes Dec 31, 2025
30d1a1a
chore(UVE_REFACTOR_HANDOFF): update production readiness plan with cr…
fmontes Dec 31, 2025
0a88295
fix(edit-ema): prevent accidental global content edits with copy cont…
fmontes Dec 31, 2025
4d42524
refactor(edit-ema): extract contentlet saving logic into a separate m…
fmontes Dec 31, 2025
b4e7aaf
refactor(edit-ema): extract toolbar presentational components for con…
fmontes Jan 2, 2026
3febd49
refactor(edit-ema): convert palette components to container/presentat…
fmontes Jan 3, 2026
de47a4a
feat(edit-ema): extract DotUveContentletQuickEditComponent as prese…
fmontes Jan 3, 2026
4b5a797
refactor(edit-ema): enhance component structure and documentation in …
fmontes Jan 3, 2026
d4a3f92
feat(edit-ema): enhance DotEmaShellComponent with local state managem…
fmontes Jan 3, 2026
da62e92
feat(edit-ema): implement local computed properties in EditEmaEditor …
fmontes Jan 3, 2026
5d030ce
refactor(edit-ema): restructure UVE store with nested UI state (Phase…
fmontes Jan 3, 2026
98283fd
refactor(edit-ema): group UI panel preferences under editor.panels
fmontes Jan 3, 2026
a4f15b6
refactor(uve-store): Phase 4 - Remove cross-feature dependencies
fmontes Jan 4, 2026
b966239
refactor(edit-ema): implement nested editor and toolbar state in UVE …
fmontes Jan 4, 2026
ac83562
refactor(edit-ema): remove pageAPIResponse and normalize UVE store state
fmontes Jan 4, 2026
037cfb3
refactor(edit-ema): reorganize UVE store state with logical groupings…
fmontes Jan 4, 2026
a7e121b
refactor(edit-ema): eliminate impossible states in UVE store with sta…
fmontes Jan 4, 2026
a75be72
refactor(edit-ema): flatten UVE store feature composition and elimina…
fmontes Jan 4, 2026
bffcb94
refactor(edit-ema): implement factory parameter pattern and improve n…
fmontes Jan 4, 2026
1aeb698
refactor(edit-ema): consolidate lock logic to eliminate duplicate com…
fmontes Jan 4, 2026
9b69fa1
refactor(uve-store): Phase 4 - eliminate cross-feature dependencies w…
fmontes Jan 4, 2026
cccfb39
refactor(uve-store): Phase 5.1 - move to shared contract, eliminate …
fmontes Jan 4, 2026
85a4a44
refactor(edit-ema): rename withToolbar to withView for semantic clarity
fmontes Jan 4, 2026
a5380de
test(edit-ema): fix test suite after toolbar→view store refactoring
fmontes Jan 4, 2026
a42adcd
test(dot-uve): update tests to reflect new state structure and add Do…
fmontes Jan 4, 2026
f3e74f0
test(dot-toggle-lock-button): enhance unit tests for lock button beha…
fmontes Jan 4, 2026
0a33529
feat(uve): address known issues with feature flags and quick editor f…
fmontes Jan 4, 2026
17c64a4
test(edit-ema): enhance unit tests for EditEmaEditorComponent with ne…
fmontes Jan 4, 2026
ff32fd7
refactor(edit-ema): replace DotUveZoomService with UVEStore for zoom …
fmontes Jan 5, 2026
c6acbb5
refactor(edit-ema): update page lock logic to incorporate feature fla…
fmontes Jan 5, 2026
3028987
refactor(edit-ema): streamline page context and lock handling logic
fmontes Jan 5, 2026
1b3957b
refactor(edit-ema): update tab mapping for UVE palette component
fmontes Jan 5, 2026
e1340e7
test(dot-uve-contentlet-quick-edit): enhance unit tests for form rend…
fmontes Jan 5, 2026
c8e8e39
test(dot-uve-contentlet-tools): enhance unit tests for contentlet sel…
fmontes Jan 5, 2026
96725c7
test(dot-uve-iframe): add unit tests for DotUveIframeComponent
fmontes Jan 5, 2026
83accea
test(dot-row-reorder): add unit tests for DotRowReorderComponent
fmontes Jan 5, 2026
98e24aa
test(dot-uve-toolbar): enhance unit tests for DotUveToolbarComponent
fmontes Jan 5, 2026
8fb48a6
test(dot-uve-toolbar): update unit tests for DotUveToolbarComponent
fmontes Jan 5, 2026
eab050b
test(dot-ema-shell): enhance unit tests for DotEmaShellComponent
fmontes Jan 5, 2026
f877bc9
Merge branch 'main' into uve-experiment
fmontes Jan 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
753 changes: 753 additions & 0 deletions core-web/UVE_REFACTOR_HANDOFF.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,4 @@ export class DotUsageService {
return 'usage.dashboard.error.generic';
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@
.p-tree-container {
.p-treenode {
padding: 0;
margin: 0;
margin: 2px 0;
outline: 0;

.p-treenode-content {
border-radius: $border-radius-xs;
transition: none;
padding: $spacing-0 $spacing-1;
padding: 0 $spacing-0;

.p-tree-toggler {
margin-right: $spacing-1;
margin-right: $spacing-0;
width: $spacing-5;
height: $spacing-5;
color: $black;
Expand Down
3 changes: 0 additions & 3 deletions core-web/libs/dotcms-scss/shared/_spacing.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,3 @@ $spacing-8: 4rem; // 64px
$spacing-9: 4.5rem; // 72px
$spacing-10: 7rem; // 112px
$spacing-11: 11rem; // 176px

// New Spacing Variables
$spacing-width-m: 0.875rem; // 14px
253 changes: 249 additions & 4 deletions core-web/libs/portlets/edit-ema/portlet/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,252 @@
# portlets-edit-ema-portlet
# Edit EMA Portlet

This library was generated with [Nx](https://nx.dev).
Universal Visual Editor (UVE) portlet for dotCMS - enables in-context editing of pages with drag-and-drop content management.

## Running unit tests
## Architecture

Run `nx test portlets-edit-ema-portlet` to execute the unit tests.
This portlet follows a **Container/Presentational Component Pattern** to maintain clear separation of concerns:

- **Smart Containers**: Inject `UVEStore`, manage state, pass data down via `@Input`
- **Presentational Components**: No store injection, receive all data via `@Input`, emit events via `@Output`

### Component Tree

```
DotEmaShellComponent (Smart Container) [UVEStore]
├─ EditEmaNavigationBarComponent (Smart Container) [UVEStore]
│ └─ Receives navigation data via @Input + reads from UVEStore
├─ Route → EditEmaEditorComponent (Smart Container) [UVEStore]
├─ DotUveToolbarComponent (Smart Container) [UVEStore]
│ ├─ DotToggleLockButtonComponent (Presentational) [NO STORE]
│ │ └─ @Input: toggleLockOptions | @Output: toggleLockClick
│ ├─ DotEmaInfoDisplayComponent (Presentational) [NO STORE]
│ │ └─ @Input: options | @Output: actionClicked
│ ├─ DotUveDeviceSelectorComponent (Presentational) [NO STORE]
│ │ └─ @Input: state, devices | @Output: stateChange
│ ├─ EditEmaLanguageSelectorComponent (Presentational) [NO STORE]
│ │ └─ @Input: contentLanguageId, pageLanguageId | @Output: change
│ └─ ... (most toolbar children are presentational)
├─ DotUvePaletteComponent (Smart Container) [UVEStore]
│ ├─ Uses local signalState for tab management (not global store)
│ └─ DotUvePaletteListComponent (Smart Container) [DotPaletteListStore + GlobalStore]
│ └─ @Input: listType, languageId, pagePath, variantId
│ └─ Uses feature store for palette-specific state management
├─ DotUveContentletQuickEditComponent (Presentational) [NO STORE]
│ └─ @Input: data (ContentletEditData), loading | @Output: submit, cancel
│ └─ Dynamic form generation based on field types
├─ EmaPageDropzoneComponent (Presentational) [NO STORE]
│ └─ @Input: containers, dragItem, zoomLevel
│ └─ Pure canvas for drag-and-drop operations
└─ DotUveLockOverlayComponent (Smart Container) [UVEStore]
└─ Reads toggle lock state directly from UVEStore
```

## Key Patterns

### Local vs Global State

**Local Component State** (use `signalState()`):
- UI-specific state (tab selection, form validation, etc.)
- State that doesn't need cross-component coordination
- Example: `DotUvePaletteComponent` tab management

**Global Store State** (use `UVEStore`):
- Shared feature state (page data, contentlet selection, etc.)
- Cross-component coordination required
- Example: `activeContentlet`, `pageAPIResponse`

**Feature Store State** (use component-specific store):
- Domain-specific state for a feature (palette list data, search params, pagination)
- Encapsulated within a feature boundary
- Example: `DotPaletteListStore` for palette list management

### Container Component Pattern (with UVEStore)

```typescript
// Smart Container (reads from global store, manages state)
@Component({ selector: 'dot-uve-palette' })
export class DotUvePaletteComponent {
protected readonly uveStore = inject(UVEStore);

// Read from global store
readonly $languageId = computed(() => this.uveStore.$languageId());
readonly $pagePath = computed(() => this.uveStore.$pageURI());

// Local UI state
readonly #localState = signalState({ currentTab: 0 });
}
```

### Container Component Pattern (with Feature Store)

```typescript
// Smart Container with feature store
@Component({ selector: 'dot-uve-palette-list' })
export class DotUvePaletteListComponent {
readonly #paletteListStore = inject(DotPaletteListStore);
readonly #globalStore = inject(GlobalStore);

// Inputs from parent (for initialization)
$type = input.required<DotUVEPaletteListTypes>({ alias: 'listType' });
$languageId = input.required<number>({ alias: 'languageId' });

// Read from feature store
protected readonly $contenttypes = this.#paletteListStore.contenttypes;
protected readonly $pagination = this.#paletteListStore.pagination;
}
```

### Presentational Component Pattern

```typescript
// Presentational (receives props, emits events, NO store injection)
@Component({ selector: 'dot-uve-contentlet-quick-edit' })
export class DotUveContentletQuickEditComponent {
// NO store injection!

// Inputs (data down from parent container)
data = input.required<ContentletEditData>({ alias: 'data' });
loading = input<boolean>(false, { alias: 'loading' });

// Outputs (events up to parent container)
submit = output<Record<string, unknown>>();
cancel = output<void>();
}
```

## Benefits

### Testability
- **Presentational components**: Easier to test (no mock store needed, pure input/output testing)
- **Container components**: Clear boundaries (test store interactions separately)
- **Feature stores**: Isolated testing of domain logic separate from UI

### Reusability
- **Presentational components**: Can be reused in different contexts without store coupling
- **Feature stores**: Domain logic can be shared across multiple components
- **Clear interfaces**: Well-defined @Input/@Output contracts

### Maintainability
- **Clear separation of concerns**: Smart containers vs presentational components vs feature stores
- **Localized changes**:
- Global store changes affect only global state consumers
- Feature store changes affect only feature-specific components
- Presentational component changes are isolated
- **Easier refactoring**: Components can be converted between patterns as needs evolve

## State Management Architecture

### UVEStore State Structure

The UVEStore uses a **nested state structure** to clearly separate concerns:

```typescript
interface UVEState {
// ============ DOMAIN STATE (Source of Truth) ============
languages: DotLanguage[];
isEnterprise: boolean;
currentUser?: CurrentUser;
experiment?: DotExperiment;
pageAPIResponse?: DotCMSPageAsset;

// ============ UI STATE (Transient) ============
// Nested editor state
editor: {
// Functional editor state
dragItem: EmaDragItem | null;
bounds: Container[];
state: EDITOR_STATE;
activeContentlet: ContentletPayload | null;
contentArea: ContentletArea | null;

// UI panel preferences (user-configurable)
panels: {
palette: { open: boolean };
rightSidebar: { open: boolean };
};

// Editor-specific data
ogTags: any | null;
styleSchemas: StyleEditorFormSchema[];
};

// Nested toolbar state
toolbar: {
device: DotDeviceListItem | null;
orientation: Orientation | null;
socialMedia: string | null;
isEditState: boolean;
isPreviewModeActive: boolean;
ogTagsResults: SeoMetaTagsResult[] | null;
};
}
```

### Accessing Nested State in Components

**Container components** access nested state directly:

```typescript
// Access editor functional state
const dragItem = this.uveStore.editor().dragItem;
const editorState = this.uveStore.editor().state;

// Access panel preferences
const isPaletteOpen = this.uveStore.editor().panels.palette.open;
const isSidebarOpen = this.uveStore.editor().panels.rightSidebar.open;

// Access toolbar state
const device = this.uveStore.toolbar().device;
const socialMedia = this.uveStore.toolbar().socialMedia;
```

**Updating nested state** requires spreading the parent object:

```typescript
// Update panel state
setPaletteOpen(open: boolean) {
const editor = store.editor();
patchState(store, {
editor: {
...editor,
panels: {
...editor.panels,
palette: { open }
}
}
});
}
```

### Benefits of Nested State

- **Clear Grouping**: Related state is grouped together (editor.panels groups all panel preferences)
- **Easier to Reason About**: The structure mirrors the UI hierarchy
- **Better Type Safety**: TypeScript can catch errors at deeper levels
- **Reduced Prop Drilling**: Components get logical state chunks instead of individual properties

## Running Tests

Run unit tests for this portlet:
```bash
nx test portlets-edit-ema-portlet
```

Run specific test file:
```bash
nx test portlets-edit-ema-portlet --testFile=path/to/test.spec.ts
```

## Development

This library uses:
- **Angular 19+** with standalone components
- **NgRx Signals** for state management
- **Modern Angular syntax**: `@if`, `@for`, `input()`, `output()`
- **PrimeNG** for UI components
- **Jest + Spectator** for testing
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ const messages = {
const store = {
paletteOpen: () => false,
setPaletteOpen: jest.fn(),
$editorProps: () => ({
palette: {
paletteClass: 'palette-class'
}
}),
pageParams: () => ({
language_id: '3',
personaId: '123'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ export class EditEmaNavigationBarComponent {

uveStore = inject(UVEStore);

$editorProps = this.uveStore.$editorProps;

$params = this.uveStore.pageParams;

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@if ($shellProps()?.canRead) {
@if ($canRead()) {
@if ($toggleLockOptions()?.showBanner && $showBanner()) {
<p-messages severity="warning" data-testid="message">
<ng-template pTemplate="content">
Expand Down Expand Up @@ -38,16 +38,18 @@

<router-outlet />
<dot-edit-ema-navigation-bar
[items]="$shellProps().items"
[items]="$menuItems()"
(action)="handleItemAction($event)"
data-testId="ema-nav-bar" />
<p-toast position="top-center" data-testId="ema-toast" />
<dot-page-tools-seo [currentPageUrlParams]="$shellProps().seoParams" #pageTools />
data-testId="ema-nav-bar"></dot-edit-ema-navigation-bar>
<p-toast position="top-center" data-testId="ema-toast"></p-toast>
<dot-page-tools-seo
[currentPageUrlParams]="$seoParams()"
#pageTools></dot-page-tools-seo>
} @else {
@if ($shellProps().error?.code === 401) {
@if ($errorDisplay()?.code === 401) {
<dot-not-license />
} @else if ($shellProps()?.error?.pageInfo) {
<dot-info-page [info]="$shellProps().error.pageInfo" />
} @else if ($errorDisplay()?.pageInfo) {
<dot-info-page [info]="$errorDisplay().pageInfo" />
}
}

Expand Down
Loading
Loading