From 4aaeb73816c7f729ad96d41fbb3403073c90ee25 Mon Sep 17 00:00:00 2001 From: Rafael Velazco Date: Fri, 6 Dec 2024 17:28:44 -0400 Subject: [PATCH] feat(FTM): New Toolbar - Implement Preview Button and Preview Toolbar (#30854) This pull request includes several updates to the `DotEmaShellComponent` and `DotUveToolbarComponent` to improve their functionality and styling. The most important changes include modifying location handling methods, updating button styles, and restructuring the toolbar component. ### Changes to `DotEmaShellComponent`: * Replaced `replaceState` with `go` in the `#updateLocation` method to improve URL handling. * Updated test cases to use `location.go` instead of `location.replaceState` for consistency. [[1]](diffhunk://#diff-8843e3a4ce8c16e83408b1a6dcc3ad54eaddd17f8e986bbdb502e11bd4446ab4L310-R310) [[2]](diffhunk://#diff-8843e3a4ce8c16e83408b1a6dcc3ad54eaddd17f8e986bbdb502e11bd4446ab4L379-R379) [[3]](diffhunk://#diff-8843e3a4ce8c16e83408b1a6dcc3ad54eaddd17f8e986bbdb502e11bd4446ab4L564-R564) ### Changes to `DotUveToolbarComponent`: * Added new styles for the toolbar and its components, including buttons and chips, to enhance the user interface. * Refactored the toolbar component template to include separate templates for edit and preview modes and added new buttons and functionality for better user experience. [[1]](diffhunk://#diff-9937556e73b051b878ba22ad1ce971a70019a617d7979b3e0bcc814801ad350bL1-R34)R1, [[2]](diffhunk://#diff-9937556e73b051b878ba22ad1ce971a70019a617d7979b3e0bcc814801ad350bR52-R115) * Updated test cases to reflect the new structure and functionality of the toolbar component, ensuring proper testing of the new features. [[1]](diffhunk://#diff-3eaa147616a5d1ff374a5fa27b0f38f0159a9039ef7e8d672dec43631f48a9e1R49-L53) [[2]](diffhunk://#diff-3eaa147616a5d1ff374a5fa27b0f38f0159a9039ef7e8d672dec43631f48a9e1L110-L169) [[3]](diffhunk://#diff-3eaa147616a5d1ff374a5fa27b0f38f0159a9039ef7e8d672dec43631f48a9e1L186-L189) [[4]](diffhunk://#diff-3eaa147616a5d1ff374a5fa27b0f38f0159a9039ef7e8d672dec43631f48a9e1L205-R201) [[5]](diffhunk://#diff-3eaa147616a5d1ff374a5fa27b0f38f0159a9039ef7e8d672dec43631f48a9e1L222-R237) ### Other Changes: * Added a new severity style for text buttons in `_splitbutton.scss` to standardize button appearance. * Updated button styles in `dot-ema-bookmarks.component.html` to include a smaller size class for better visual consistency. ### Video https://github.com/user-attachments/assets/576dce00-f7f0-47eb-a744-7eaac368dcf4 --- .../components/buttons/_splitbutton.scss | 15 + .../dot-ema-shell.component.spec.ts | 6 +- .../dot-ema-shell/dot-ema-shell.component.ts | 3 +- .../dot-ema-bookmarks.component.html | 2 +- .../dot-ema-bookmarks.component.spec.ts | 6 - .../dot-uve-toolbar.component.html | 129 ++++++-- .../dot-uve-toolbar.component.scss | 39 ++- .../dot-uve-toolbar.component.spec.ts | 277 +++++++++++------- .../dot-uve-toolbar.component.ts | 44 ++- .../edit-ema-editor.component.html | 5 +- .../edit-ema-editor.component.scss | 4 + .../edit-ema-editor.component.ts | 2 + .../lib/services/dot-page-api.service.spec.ts | 18 ++ .../src/lib/services/dot-page-api.service.ts | 20 +- .../src/lib/store/dot-uve.store.spec.ts | 14 + .../portlet/src/lib/store/dot-uve.store.ts | 3 + .../features/editor/toolbar/withUVEToolbar.ts | 31 +- .../lib/store/features/editor/withEditor.ts | 3 +- .../src/lib/store/features/load/withLoad.ts | 28 +- 19 files changed, 459 insertions(+), 190 deletions(-) diff --git a/core-web/libs/dotcms-scss/angular/dotcms-theme/components/buttons/_splitbutton.scss b/core-web/libs/dotcms-scss/angular/dotcms-theme/components/buttons/_splitbutton.scss index 53a2dcbba897..2e8f6f47045b 100644 --- a/core-web/libs/dotcms-scss/angular/dotcms-theme/components/buttons/_splitbutton.scss +++ b/core-web/libs/dotcms-scss/angular/dotcms-theme/components/buttons/_splitbutton.scss @@ -111,3 +111,18 @@ } } } + +// Severity for text button +.p-splitbutton.p-button-text { + @extend #text-primary-severity; + + .p-button { + &, + &:hover, + &:focus { + color: $color-palette-primary; + background-color: transparent; + border: transparent; + } + } +} diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts index ea38a9b6ff4a..bab367e5d557 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.spec.ts @@ -307,7 +307,7 @@ describe('DotEmaShellComponent', () => { it('should call store.loadPageAsset when the `loadPageAsset` is called', () => { const spyloadPageAsset = jest.spyOn(store, 'loadPageAsset'); const spyStoreLoadPage = jest.spyOn(store, 'loadPageAsset'); - const spyLocation = jest.spyOn(location, 'replaceState'); + const spyLocation = jest.spyOn(location, 'go'); spectator.detectChanges(); expect(spyloadPageAsset).toHaveBeenCalledWith(INITIAL_PAGE_PARAMS); @@ -376,7 +376,7 @@ describe('DotEmaShellComponent', () => { const spyStoreLoadPage = jest.spyOn(store, 'loadPageAsset'); const spyUrlTree = jest.spyOn(router, 'createUrlTree'); - const spyLocation = jest.spyOn(location, 'replaceState'); + const spyLocation = jest.spyOn(location, 'go'); store.loadPageAsset(newParams); spectator.detectChanges(); @@ -561,7 +561,7 @@ describe('DotEmaShellComponent', () => { it('should trigger a store reload if the url is the same', () => { const spyReload = jest.spyOn(store, 'reloadCurrentPage'); - const spyLocation = jest.spyOn(location, 'replaceState'); + const spyLocation = jest.spyOn(location, 'go'); spectator.detectChanges(); spectator.triggerEventHandler( diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.ts index e2ad62a4840b..c07053bcc3ee 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/dot-ema-shell/dot-ema-shell.component.ts @@ -223,7 +223,6 @@ export class DotEmaShellComponent implements OnInit, OnDestroy { */ #updateLocation(queryParams: Params = {}): void { const urlTree = this.#router.createUrlTree([], { queryParams }); - - this.#location.replaceState(urlTree.toString()); + this.#location.go(urlTree.toString()); } } diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-ema-bookmarks/dot-ema-bookmarks.component.html b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-ema-bookmarks/dot-ema-bookmarks.component.html index ff181a2141ef..80d882fe8600 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-ema-bookmarks/dot-ema-bookmarks.component.html +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-ema-bookmarks/dot-ema-bookmarks.component.html @@ -3,7 +3,7 @@ (click)="toggleBookmark()" [icon]="bookmarked() ? 'pi pi-star-fill' : 'pi pi-star'" [loading]="loading()" - styleClass="p-button-text" + styleClass="p-button-text p-button-sm" data-testId="bookmark-button" /> } @else { { providers: [ DialogService, HttpClient, - // { - // provide: UVEStore, - // useValue: { - // $previewMode: signal(false) - // } - // }, mockProvider(UVEStore, { $previewMode: signal(false) }), { provide: LoginService, diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.html b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.html index 01dd0ecbc8a5..1f2c2c1b48cd 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.html +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.html @@ -1,37 +1,37 @@ - -
- @if ($toolbar().editor) { - - - - - - - - } @else if ($toolbar().preview) { -
PREVIEW MODE CONTENT
- +@let preview = $isPreviewMode(); +@let runningExperiment = $toolbar().runningExperiment; + + +
+ @if (preview) { + + } @else { + }
+ @if (preview) { +
+
+ + +
+ + +
+ } +
- @if ($toolbar().runningExperiment; as runningExperiment) { + @if (runningExperiment) { @@ -49,10 +49,77 @@ [value]="$personaSelectorProps().value" #personaSelector data-testId="uve-toolbar-persona-selector" /> - Workflows + + @if (!preview) { + Workflows + }
+ + + + + + + + + + + + + +
+ + + + + + + + +
+ @if ($toolbar().showInfoDisplay) { } diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.scss b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.scss index e19f8332f248..d8049559596f 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.scss +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.scss @@ -1,7 +1,44 @@ @use "variables" as *; +::ng-deep { + .uve-toolbar { + padding: 0 $spacing-4; + transition: padding 0.2s ease; + border-color: $color-palette-primary-200; + border-top: 1px solid transparent; // Avoid jump + } + + .uve-toolbar-preview { + border-top-color: $color-palette-primary-200; + padding-inline: $spacing-5; + } + + .p-chip.uve-toolbar-chips { + height: $field-height-sm; + background-color: $color-palette-primary-op-10; + border-color: $color-palette-primary-op-10; + } +} + .p-toolbar-group-start, -.p-toolbar-group-end { +.p-toolbar-group-end, +.p-toolbar-group-center { + padding: $spacing-1 0; align-items: center; gap: $spacing-1; + align-self: stretch; +} + +.vertical-divider { + align-self: stretch; + position: relative; + &::before { + border-left: 1px solid $color-palette-primary-200; + position: absolute; + display: block; + top: 0; + left: 50%; + height: 100%; + content: ""; + } } diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.spec.ts index 24b8b490d289..3ada5bd626fd 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.spec.ts @@ -46,11 +46,60 @@ import { EditEmaPersonaSelectorComponent } from '../edit-ema-persona-selector/ed const $apiURL = '/api/v1/page/json/123-xyz-567-xxl?host_id=123-xyz-567-xxl&language_id=1'; +const params = HEADLESS_BASE_QUERY_PARAMS; +const url = sanitizeURL(params?.url); + +const pageAPIQueryParams = createPageApiUrlWithQueryParams(url, params); +const pageAPI = `/api/v1/page/${'json'}/${pageAPIQueryParams}`; +const pageAPIResponse = MOCK_RESPONSE_HEADLESS; +const shouldShowInfoDisplay = false || pageAPIResponse?.page.locked; +const bookmarksUrl = createFavoritePagesURL({ + languageId: Number(params?.language_id), + pageURI: url, + siteId: pageAPIResponse?.site.identifier +}); + +const baseUVEToolbarState = { + editor: { + bookmarksUrl, + copyUrl: createFullURL(params, pageAPIResponse?.site.identifier), + apiUrl: `${'http://localhost'}${pageAPI}` + }, + preview: null, + currentLanguage: pageAPIResponse?.viewAs.language, + urlContentMap: null, + runningExperiment: null, + workflowActionsInode: pageAPIResponse?.page.inode, + unlockButton: null, + showInfoDisplay: shouldShowInfoDisplay +}; + +const baseUVEState = { + $uveToolbar: signal(baseUVEToolbarState), + setDevice: jest.fn(), + setSocialMedia: jest.fn(), + pageParams: signal(params), + pageAPIResponse: signal(MOCK_RESPONSE_VTL), + $apiURL: signal($apiURL), + reloadCurrentPage: jest.fn(), + loadPageAsset: jest.fn(), + $isPreviewMode: signal(false), + $personaSelector: signal({ + pageId: pageAPIResponse?.page.identifier, + value: pageAPIResponse?.viewAs.persona ?? DEFAULT_PERSONA + }), + languages: signal([ + { id: 1, translated: true }, + { id: 2, translated: false }, + { id: 3, translated: true } + ]) +}; + describe('DotUveToolbarComponent', () => { let spectator: Spectator; + let store: InstanceType; let messageService: MessageService; let confirmationService: ConfirmationService; - let store: InstanceType; const createComponent = createComponentFactory({ component: DotUveToolbarComponent, @@ -107,66 +156,15 @@ describe('DotUveToolbarComponent', () => { ] }); - const params = HEADLESS_BASE_QUERY_PARAMS; - const url = sanitizeURL(params?.url); - - const pageAPIQueryParams = createPageApiUrlWithQueryParams(url, params); - const pageAPIResponse = MOCK_RESPONSE_HEADLESS; - - const pageAPI = `/api/v1/page/${'json'}/${pageAPIQueryParams}`; - - const shouldShowInfoDisplay = false || pageAPIResponse?.page.locked || false || false; - - const bookmarksUrl = createFavoritePagesURL({ - languageId: Number(params?.language_id), - pageURI: url, - siteId: pageAPIResponse?.site.identifier - }); - - const baseUVEToolbarState = { - editor: { - bookmarksUrl, - copyUrl: createFullURL(params, pageAPIResponse?.site.identifier), - apiUrl: `${'http://localhost'}${pageAPI}` - }, - preview: null, - currentLanguage: pageAPIResponse?.viewAs.language, - urlContentMap: null, - runningExperiment: null, - workflowActionsInode: pageAPIResponse?.page.inode, - unlockButton: null, - showInfoDisplay: shouldShowInfoDisplay - }; - - const baseUVEState = { - $uveToolbar: signal(baseUVEToolbarState), - setDevice: jest.fn(), - setSocialMedia: jest.fn(), - pageParams: signal(params), - pageAPIResponse: signal(MOCK_RESPONSE_VTL), - $apiURL: signal($apiURL), - $personaSelector: signal({ - pageId: pageAPIResponse?.page.identifier, - value: pageAPIResponse?.viewAs.persona ?? DEFAULT_PERSONA - }), - reloadCurrentPage: jest.fn(), - loadPageAsset: jest.fn(), - languages: signal([ - { id: 1, translated: true }, - { id: 2, translated: false }, - { id: 3, translated: true } - ]) - }; - describe('base state', () => { beforeEach(() => { spectator = createComponent({ providers: [mockProvider(UVEStore, { ...baseUVEState })] }); - messageService = spectator.inject(MessageService); + store = spectator.inject(UVEStore, true); + messageService = spectator.inject(MessageService, true); confirmationService = spectator.inject(ConfirmationService); - store = spectator.inject(UVEStore); }); describe('dot-ema-bookmarks', () => { @@ -183,10 +181,6 @@ describe('DotUveToolbarComponent', () => { }); }); - it('should have preview button', () => { - expect(spectator.query(byTestId('uve-toolbar-preview'))).toBeTruthy(); - }); - describe('copy-url', () => { let button: DebugElement; @@ -199,12 +193,14 @@ describe('DotUveToolbarComponent', () => { it('should have attrs', () => { expect(button.attributes).toEqual({ class: 'ng-star-inserted', - 'data-testId': 'uve-toolbar-copy-url', icon: 'pi pi-external-link', + pTooltip: 'Copy URL', + 'data-testId': 'uve-toolbar-copy-url', + 'ng-reflect-style-class': 'p-button-text p-button-sm', + 'ng-reflect-content': 'Copy URL', 'ng-reflect-icon': 'pi pi-external-link', - 'ng-reflect-style-class': 'p-button-text', 'ng-reflect-text': 'http://localhost:3000/test-url', - styleClass: 'p-button-text' + styleClass: 'p-button-text p-button-sm' }); }); @@ -219,40 +215,6 @@ describe('DotUveToolbarComponent', () => { }); }); - it('should have not experiments button if experiment is not running', () => { - expect(spectator.query(byTestId('uve-toolbar-running-experiment'))).toBeFalsy(); - }); - - describe('language selector', () => { - it('should have language selector', () => { - expect(spectator.query(byTestId('uve-toolbar-language-selector'))).toBeTruthy(); - }); - - it('should call loadPageAsset when language is selected and exists that page translated', () => { - const spyLoadPageAsset = jest.spyOn(baseUVEState, 'loadPageAsset'); - - spectator.triggerEventHandler(EditEmaLanguageSelectorComponent, 'selected', 1); - - expect(spyLoadPageAsset).toHaveBeenCalled(); - }); - - it('should call confirmationService.confirm when language is selected and does not exist that page translated', () => { - const spyConfirmationService = jest.spyOn(baseUVEState, 'loadPageAsset'); - - spectator.triggerEventHandler(EditEmaLanguageSelectorComponent, 'selected', 2); - - expect(spyConfirmationService).toHaveBeenCalled(); - }); - }); - - it('should have persona selector', () => { - expect(spectator.query(byTestId('uve-toolbar-persona-selector'))).toBeTruthy(); - }); - - it('should have workflows button', () => { - expect(spectator.query(byTestId('uve-toolbar-workflow-actions'))).toBeTruthy(); - }); - describe('API URL', () => { it('should have api link button', () => { expect(spectator.query(byTestId('uve-toolbar-api-link'))).toBeTruthy(); @@ -264,6 +226,20 @@ describe('DotUveToolbarComponent', () => { }); }); + describe('Preview', () => { + it('should have preview button', () => { + expect(spectator.query(byTestId('uve-toolbar-preview'))).toBeTruthy(); + }); + + it('should call store.loadPageAsset with preview true', () => { + const spy = jest.spyOn(store, 'loadPageAsset'); + + spectator.click(byTestId('uve-toolbar-preview')); + + expect(spy).toHaveBeenCalledWith({ preview: 'true' }); + }); + }); + describe('dot-edit-ema-persona-selector', () => { it('should have attr', () => { const personaSelector = spectator.query(EditEmaPersonaSelectorComponent); @@ -353,24 +329,109 @@ describe('DotUveToolbarComponent', () => { }); }); }); + + describe('language selector', () => { + it('should have language selector', () => { + expect(spectator.query(byTestId('uve-toolbar-language-selector'))).toBeTruthy(); + }); + + it('should call loadPageAsset when language is selected and exists that page translated', () => { + const spyLoadPageAsset = jest.spyOn(baseUVEState, 'loadPageAsset'); + + spectator.triggerEventHandler(EditEmaLanguageSelectorComponent, 'selected', 1); + + expect(spyLoadPageAsset).toHaveBeenCalled(); + }); + + it('should call confirmationService.confirm when language is selected and does not exist that page translated', () => { + const spyConfirmationService = jest.spyOn(baseUVEState, 'loadPageAsset'); + + spectator.triggerEventHandler(EditEmaLanguageSelectorComponent, 'selected', 2); + + expect(spyConfirmationService).toHaveBeenCalled(); + }); + }); + + it('should have not experiments button if experiment is not running', () => { + expect(spectator.query(byTestId('uve-toolbar-running-experiment'))).toBeFalsy(); + }); + + it('should have persona selector', () => { + expect(spectator.query(byTestId('uve-toolbar-persona-selector'))).toBeTruthy(); + }); + + it('should have workflows button', () => { + expect(spectator.query(byTestId('uve-toolbar-workflow-actions'))).toBeTruthy(); + }); + }); + + describe('preview', () => { + beforeEach(() => { + spectator = createComponent({ + providers: [ + mockProvider(UVEStore, { ...baseUVEState, $isPreviewMode: signal(true) }) + ] + }); + + store = spectator.inject(UVEStore, true); + }); + + describe('Close Preview Mode', () => { + it('should have api link button', () => { + expect(spectator.query(byTestId('close-preview-mode'))).toBeTruthy(); + }); + + it('should call store.loadPageAsset with preview null', () => { + const spy = jest.spyOn(store, 'loadPageAsset'); + + spectator.click(byTestId('close-preview-mode')); + + spectator.detectChanges(); + expect(spy).toHaveBeenCalledWith({ preview: null }); + }); + }); + + it('should have desktop button', () => { + expect(spectator.query(byTestId('desktop-preview'))).toBeTruthy(); + }); + + it('should have mobile button', () => { + expect(spectator.query(byTestId('mobile-preview'))).toBeTruthy(); + }); + + it('should have tablet button', () => { + expect(spectator.query(byTestId('tablet-preview'))).toBeTruthy(); + }); + + it('should have more devices button', () => { + expect(spectator.query(byTestId('more-devices-preview'))).toBeTruthy(); + }); + + it('should not have experiments', () => { + expect(spectator.query(byTestId('uve-toolbar-running-experiment'))).toBeFalsy(); + }); + + it('should not have workflow actions', () => { + expect(spectator.query(byTestId('uve-toolbar-workflow-actions'))).toBeFalsy(); + }); }); describe('State changes', () => { - describe('Experiment is running', () => { - beforeEach(() => { - const state = { - ...baseUVEState, - $uveToolbar: signal({ - ...baseUVEToolbarState, - runningExperiment: getRunningExperimentMock() - }) - }; - - spectator = createComponent({ - providers: [mockProvider(UVEStore, { ...state })] - }); + beforeEach(() => { + const state = { + ...baseUVEState, + $uveToolbar: signal({ + ...baseUVEToolbarState, + runningExperiment: getRunningExperimentMock() + }) + }; + + spectator = createComponent({ + providers: [mockProvider(UVEStore, { ...state })] }); + }); + describe('Experiment is running', () => { it('should have experiment running component', () => { expect(spectator.query(byTestId('uve-toolbar-running-experiment'))).toBeTruthy(); }); diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts index c60adb1e5a09..bfc47d0fe26a 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/components/dot-uve-toolbar/dot-uve-toolbar.component.ts @@ -1,15 +1,21 @@ import { ClipboardModule } from '@angular/cdk/clipboard'; +import { NgClass, NgTemplateOutlet } from '@angular/common'; import { ChangeDetectionStrategy, Component, + computed, EventEmitter, inject, Output, viewChild } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { ConfirmationService, MessageService } from 'primeng/api'; import { ButtonModule } from 'primeng/button'; +import { CalendarModule } from 'primeng/calendar'; +import { ChipModule } from 'primeng/chip'; +import { SplitButtonModule } from 'primeng/splitbutton'; import { ToolbarModule } from 'primeng/toolbar'; import { DotMessageService, DotPersonalizeService } from '@dotcms/data-access'; @@ -28,11 +34,19 @@ import { EditEmaPersonaSelectorComponent } from '../edit-ema-persona-selector/ed selector: 'dot-uve-toolbar', standalone: true, imports: [ + NgClass, + NgTemplateOutlet, ButtonModule, ToolbarModule, DotEmaBookmarksComponent, DotEmaInfoDisplayComponent, DotEmaRunningExperimentComponent, + ClipboardModule, + CalendarModule, + SplitButtonModule, + FormsModule, + ReactiveFormsModule, + ChipModule, EditEmaPersonaSelectorComponent, EditEmaLanguageSelectorComponent, ClipboardModule @@ -44,7 +58,6 @@ import { EditEmaPersonaSelectorComponent } from '../edit-ema-persona-selector/ed }) export class DotUveToolbarComponent { $personaSelector = viewChild('personaSelector'); - $languageSelector = viewChild('languageSelector'); #store = inject(UVEStore); @@ -54,13 +67,38 @@ export class DotUveToolbarComponent { readonly #personalizeService = inject(DotPersonalizeService); readonly $toolbar = this.#store.$uveToolbar; + readonly $isPreviewMode = this.#store.$isPreviewMode; readonly $apiURL = this.#store.$apiURL; readonly $personaSelectorProps = this.#store.$personaSelector; @Output() translatePage = new EventEmitter<{ page: DotPage; newLanguage: number }>(); - togglePreviewMode(preview: boolean) { - this.#store.togglePreviewMode(preview); + readonly $styleToolbarClass = computed(() => { + if (!this.$isPreviewMode()) { + return 'uve-toolbar'; + } + + return 'uve-toolbar uve-toolbar-preview'; + }); + + protected readonly date = new Date(); + + /** + * Set the preview mode + * + * @memberof DotUveToolbarComponent + */ + protected setPreviewMode() { + this.#store.loadPageAsset({ preview: 'true' }); + } + + /** + * Set the edit mode + * + * @memberof DotUveToolbarComponent + */ + protected setEditMode() { + this.#store.loadPageAsset({ preview: null }); } /** diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.html b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.html index 17caed1844f9..fa44544ad286 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.html +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/edit-ema-editor/edit-ema-editor.component.html @@ -14,7 +14,10 @@ data-testId="results-seo-tool"> } @if ($editorProps().showEditorContent) { -
+
{ + spectator.service + .get({ + url: 'test-url', + language_id: 'en', + 'com.dotmarketing.persona.id': 'modes.persona.no.persona', + preview: 'true' + }) + .subscribe(); + + spectator.expectOne( + '/api/v1/page/render/test-url?language_id=en&com.dotmarketing.persona.id=modes.persona.no.persona&variantName=DEFAULT&depth=0&mode=PREVIEW_MODE', + HttpMethod.GET + ); + }); + }); + describe('getClientPage', () => { const baseParams = { url: '///test-url///', diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api.service.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api.service.ts index 40f867dab0e5..634f22953566 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api.service.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/services/dot-page-api.service.ts @@ -43,6 +43,7 @@ export interface DotPageApiParams { url: string; language_id: string; 'com.dotmarketing.persona.id': string; + preview?: string; variantName?: string; experimentId?: string; mode?: string; @@ -58,7 +59,8 @@ export enum DotPageApiKeys { VARIANT_NAME = 'variantName', LANGUAGE_ID = 'language_id', EXPERIMENT_ID = 'experimentId', - PERSONA_ID = 'com.dotmarketing.persona.id' + PERSONA_ID = 'com.dotmarketing.persona.id', + PREVIEW = 'preview' } export interface GetPersonasParams { @@ -92,17 +94,19 @@ export class DotPageApiService { */ get(params: DotPageApiParams): Observable { // Remove trailing and leading slashes + const { clientHost, preview, depth = '0', language_id, variantName, experimentId } = params; const url = params.url.replace(/^\/+|\/+$/g, ''); - const pageType = params.clientHost ? 'json' : 'render'; - const mode = PAGE_MODE.EDIT; + const isPreview = preview === 'true'; + const pageType = clientHost ? 'json' : 'render'; + const mode = isPreview ? PAGE_MODE.PREVIEW : PAGE_MODE.EDIT; const pageApiUrl = createPageApiUrlWithQueryParams(url, { - language_id: params.language_id, - 'com.dotmarketing.persona.id': params['com.dotmarketing.persona.id'], - variantName: params.variantName, - experimentId: params.experimentId, - depth: params['depth'] || '0', + language_id, + 'com.dotmarketing.persona.id': params?.['com.dotmarketing.persona.id'], + variantName, + experimentId, + depth, mode }); diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.spec.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.spec.ts index 03fba5ce2b8c..45de140e732c 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.spec.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.spec.ts @@ -352,6 +352,20 @@ describe('UVEStore', () => { }); }); }); + + describe('$isPreviewMode', () => { + it("should return true when the preview is 'true'", () => { + store.loadPageAsset({ preview: 'true' }); + + expect(store.$isPreviewMode()).toBe(true); + }); + + it("should return false when the preview is not 'true'", () => { + store.loadPageAsset({ preview: null }); + + expect(store.$isPreviewMode()).toBe(false); + }); + }); }); describe('withMethods', () => { diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.ts index b835d3604aea..429b1a36b45f 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/dot-uve.store.ts @@ -123,6 +123,9 @@ export const UVEStore = signalStore( }), $languageId: computed(() => { return pageAPIResponse()?.viewAs.language?.id || 1; + }), + $isPreviewMode: computed(() => { + return pageParams()?.preview === 'true'; }) }; } diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/toolbar/withUVEToolbar.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/toolbar/withUVEToolbar.ts index 297b39476be4..d43e88e037d2 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/toolbar/withUVEToolbar.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/toolbar/withUVEToolbar.ts @@ -87,22 +87,23 @@ export function withUVEToolbar() { const siteId = pageAPIResponse?.site?.identifier; const clientHost = `${params?.clientHost ?? window.location.origin}`; - return { - editor: store.isPreviewModeActive() - ? null - : { - bookmarksUrl, - copyUrl: createFullURL(params, siteId), - apiUrl: pageAPI - }, - preview: store.isPreviewModeActive() - ? { - deviceSelector: { - apiLink: `${clientHost}${pageAPI}`, - hideSocialMedia: !store.isTraditionalPage() - } + const isPreview = params?.preview === 'true'; + const prevewItem = isPreview + ? { + deviceSelector: { + apiLink: `${clientHost}${pageAPI}`, + hideSocialMedia: !store.isTraditionalPage() } - : null, + } + : null; + + return { + editor: { + bookmarksUrl, + copyUrl: createFullURL(params, siteId), + apiUrl: pageAPI + }, + preview: prevewItem, currentLanguage: pageAPIResponse?.viewAs.language, urlContentMap: store.isEditState() ? (pageAPIResponse?.urlContentMap ?? null) diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.ts index 66be972ae8e2..e905d5953c8d 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/editor/withEditor.ts @@ -132,7 +132,8 @@ export function withEditor() { const showDropzone = canEditPage && state === EDITOR_STATE.DRAGGING; - const showPalette = isEnterprise && canEditPage && isEditState; + const isPreview = params?.preview === 'true'; + const showPalette = isEnterprise && canEditPage && isEditState && !isPreview; const shouldShowSeoResults = socialMedia && ogTags; diff --git a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts index ee3944fcec67..0c1b479186c8 100644 --- a/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts +++ b/core-web/libs/portlets/edit-ema/portlet/src/lib/store/features/load/withLoad.ts @@ -49,16 +49,25 @@ export function withLoad() { */ loadPageAsset: rxMethod>( pipe( - tap(() => store.resetClientConfiguration()), - tap(() => { - patchState(store, { status: UVE_STATUS.LOADING, isClientReady: false }); - }), - switchMap((params) => { - const pageParams = { - ...(store.pageParams() ?? {}), - ...params - } as DotPageApiParams; + map((params) => { + if (!store.pageParams()) { + return params as DotPageApiParams; + } + return { + ...store.pageParams(), + ...params + }; + }), + tap((pageParams) => { + store.resetClientConfiguration(); + patchState(store, { + status: UVE_STATUS.LOADING, + isClientReady: false, + pageParams + }); + }), + switchMap((pageParams) => { return forkJoin({ pageAsset: dotPageApiService.get(pageParams).pipe( // This logic should be handled in the Shell component using an effect @@ -128,7 +137,6 @@ export function withLoad() { const isTraditionalPage = !pageParams.clientHost; // If we don't send the clientHost we are using as VTL page patchState(store, { - pageParams, pageAPIResponse: pageAsset, isEnterprise, currentUser,