diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.scss index 2b99e3217102..a206cc3a8faf 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.scss +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.scss @@ -33,7 +33,7 @@ } .category-list__category-column { - flex: 0 0 30%; + flex: 0 0 38%; border-right: 1px solid $color-palette-gray-400; overflow-y: auto; height: 100%; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.ts index 66e44429b111..d7de911dd131 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-category-list/dot-category-field-category-list.component.ts @@ -33,7 +33,7 @@ import { } from '../../models/dot-category-field.models'; import { DotCategoryFieldListSkeletonComponent } from '../dot-category-field-list-skeleton/dot-category-field-list-skeleton.component'; -export const MINIMUM_CATEGORY_COLUMNS = 4; +export const MINIMUM_CATEGORY_COLUMNS = 3; const MINIMUM_CATEGORY_WITHOUT_SCROLLING = 3; diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-dialog/dot-category-field-dialog.component.html similarity index 69% rename from core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html rename to core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-dialog/dot-category-field-dialog.component.html index 25f90123198d..fba5bfb6268c 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-dialog/dot-category-field-dialog.component.html @@ -1,49 +1,33 @@ - - -
- -
- {{ 'edit.content.category-field.sidebar.header.select-categories' | dm }} -
-
-
- +
@if (store.mode() === 'list') { } @else { - {{ 'edit.content.category-field.sidebar.button.clear-all' | dm }} + {{ 'edit.content.category-field.dialog.button.clear-all' | dm }}
} @else {
-

{{ 'edit.content.category-field.sidebar.empty-state' | dm }}

+

{{ 'edit.content.category-field.dialog.empty-state' | dm }}

}
-
+ + + + + diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.scss b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-dialog/dot-category-field-dialog.component.scss similarity index 88% rename from core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.scss rename to core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-dialog/dot-category-field-dialog.component.scss index bd2516e0db43..30056f0db608 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.scss +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-dialog/dot-category-field-dialog.component.scss @@ -71,10 +71,17 @@ scrollbar-gutter: auto; } -:host ::ng-deep .p-sidebar-content { - padding: 0; - height: 100%; - overflow: hidden; +:host ::ng-deep { + .category-field__dialog.p-dialog { + min-height: 90%; + height: 100%; + width: 75vw; + + .p-dialog-content { + padding: 0; + height: 100%; + } + } } .category-field__empty-state { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-dialog/dot-category-field-dialog.component.spec.ts similarity index 54% rename from core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.spec.ts rename to core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-dialog/dot-category-field-dialog.component.spec.ts index f63b5d36ce19..c12f28f00bfa 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-dialog/dot-category-field-dialog.component.spec.ts @@ -2,11 +2,11 @@ import { expect, it } from '@jest/globals'; import { byTestId, createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest'; import { of } from 'rxjs'; -import { Sidebar } from 'primeng/sidebar'; +import { Dialog } from 'primeng/dialog'; import { DotHttpErrorManagerService, DotMessageService } from '@dotcms/data-access'; -import { DotCategoryFieldSidebarComponent } from './dot-category-field-sidebar.component'; +import { DotCategoryFieldDialogComponent } from './dot-category-field-dialog.component'; import { CATEGORY_LIST_MOCK } from '../../mocks/category-field.mocks'; import { CategoriesService } from '../../services/categories.service'; @@ -14,20 +14,17 @@ import { CategoryFieldStore } from '../../store/content-category-field.store'; import { DotCategoryFieldCategoryListComponent } from '../dot-category-field-category-list/dot-category-field-category-list.component'; import { DotCategoryFieldSelectedComponent } from '../dot-category-field-selected/dot-category-field-selected.component'; -describe('DotEditContentCategoryFieldSidebarComponent', () => { - let spectator: Spectator; +describe('DotCategoryFieldDialogComponent', () => { + let spectator: Spectator; let store: InstanceType; const createComponent = createComponentFactory({ - component: DotCategoryFieldSidebarComponent, + component: DotCategoryFieldDialogComponent, providers: [mockProvider(DotMessageService), CategoryFieldStore] }); beforeEach(() => { spectator = createComponent({ - props: { - visible: true - }, providers: [ mockProvider(CategoriesService, { getChildren: jest.fn().mockReturnValue(of(CATEGORY_LIST_MOCK)) @@ -35,6 +32,7 @@ describe('DotEditContentCategoryFieldSidebarComponent', () => { mockProvider(DotHttpErrorManagerService) ] }); + spectator.setInput('isVisible', true); store = spectator.inject(CategoryFieldStore, true); @@ -46,8 +44,8 @@ describe('DotEditContentCategoryFieldSidebarComponent', () => { }); it('should have `visible` property set to `true` by default', () => { - expect(spectator.component.visible).toBe(true); - expect(spectator.query(Sidebar)).not.toBeNull(); + expect(spectator.component.$isVisible()).toBe(true); + expect(spectator.query(Dialog)).not.toBeNull(); }); it('should render the selected categories list when there are selected categories', () => { @@ -64,15 +62,42 @@ describe('DotEditContentCategoryFieldSidebarComponent', () => { expect(spectator.query(byTestId('category-field__empty-state'))).not.toBeNull(); }); - it('should emit event to close sidebar when "back" button is clicked', () => { - const closedSidebarSpy = jest.spyOn(spectator.component.closedSidebar, 'emit'); - const cancelBtn = spectator.query(byTestId('back-btn')); - expect(cancelBtn).not.toBeNull(); + it('should have the correct configuration for the dialog.', () => { + const closedDialogSpy = jest.spyOn(spectator.component.closedDialog, 'emit'); + const dialog = spectator.query(Dialog); - expect(closedSidebarSpy).not.toHaveBeenCalled(); + expect(dialog.draggable).toBe(false); + expect(dialog.resizable).toBe(false); + expect(dialog.modal).toBe(true); + expect(dialog.modal).toBe(true); - spectator.click(cancelBtn); - expect(closedSidebarSpy).toHaveBeenCalled(); + dialog.onHide.emit(); + + expect(closedDialogSpy).toHaveBeenCalled(); + }); + + it('should close the dialog when the close button is clicked', () => { + const closedDialogSpy = jest.spyOn(spectator.component.closedDialog, 'emit'); + spectator.click(byTestId('dialog-cancel')); + + expect(closedDialogSpy).toHaveBeenCalled(); + }); + + it('should save the changes and apply the categories when the apply button is clicked', () => { + const closedDialogSpy = jest.spyOn(spectator.component.closedDialog, 'emit'); + const addConfirmedCategoriesSky = jest.spyOn(store, 'addConfirmedCategories'); + const categories = { key: '1234', value: 'test' }; + store.addSelected({ key: '1234', value: 'test' }); + spectator.detectChanges(); + + expect(store.selected()).toEqual([categories]); + expect(store.confirmedCategories()).toEqual([]); + + spectator.click(byTestId('dialog-apply')); + + expect(store.confirmedCategories()).toEqual([categories]); + expect(closedDialogSpy).toHaveBeenCalled(); + expect(addConfirmedCategoriesSky).toHaveBeenCalled(); }); it('should render the CategoryFieldCategoryList component', () => { diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-dialog/dot-category-field-dialog.component.ts similarity index 60% rename from core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.ts rename to core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-dialog/dot-category-field-dialog.component.ts index b396c0b3ce18..eb15e950f4dc 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-sidebar/dot-category-field-sidebar.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/components/dot-category-field-dialog/dot-category-field-dialog.component.ts @@ -1,21 +1,18 @@ -import { animate, state, style, transition, trigger } from '@angular/animations'; import { NgClass } from '@angular/common'; import { ChangeDetectionStrategy, Component, computed, - EventEmitter, inject, - Input, + model, OnDestroy, OnInit, - Output + output } from '@angular/core'; import { ButtonModule } from 'primeng/button'; import { DialogModule } from 'primeng/dialog'; import { InputTextModule } from 'primeng/inputtext'; -import { SidebarModule } from 'primeng/sidebar'; import { DotMessagePipe } from '@dotcms/ui'; @@ -26,21 +23,20 @@ import { DotCategoryFieldSearchListComponent } from '../dot-category-field-searc import { DotCategoryFieldSelectedComponent } from '../dot-category-field-selected/dot-category-field-selected.component'; /** - * The DotCategoryFieldSidebarComponent is a sidebar panel that allows editing of content category field. + * The DotCategoryFieldDialogComponent is a dialog panel that allows editing of content category field. * It provides interfaces for item selection and click handling, and communicates with a store * to fetch and update the categories' data. * - * @property {boolean} visible - Indicates the visibility of the sidebar. Default is `true`. - * @property {EventEmitter} closedSidebar - Event emitted when the sidebar is closed. + * @property {boolean} visible - Indicates the visibility of the dialog. Default is `true`. + * @property {output} closedDialog - Output emitted when the dialog is closed. */ @Component({ - selector: 'dot-category-field-sidebar', + selector: 'dot-category-field-dialog', standalone: true, imports: [ DialogModule, ButtonModule, DotMessagePipe, - SidebarModule, DotCategoryFieldCategoryListComponent, InputTextModule, DotCategoryFieldSearchComponent, @@ -48,38 +44,27 @@ import { DotCategoryFieldSelectedComponent } from '../dot-category-field-selecte DotCategoryFieldSelectedComponent, NgClass ], - templateUrl: './dot-category-field-sidebar.component.html', - styleUrl: './dot-category-field-sidebar.component.scss', - changeDetection: ChangeDetectionStrategy.OnPush, - animations: [ - trigger('fadeAnimation', [ - state( - 'void', - style({ - opacity: 0 - }) - ), - transition(':enter, :leave', [animate('50ms ease-in-out')]) - ]) - ] + templateUrl: './dot-category-field-dialog.component.html', + styleUrl: './dot-category-field-dialog.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush }) -export class DotCategoryFieldSidebarComponent implements OnInit, OnDestroy { +export class DotCategoryFieldDialogComponent implements OnInit, OnDestroy { /** - * Indicates the visibility of the sidebar. + * Indicates the visibility of the dialog. * - * @memberof DotCategoryFieldSidebarComponent + * @memberof DotCategoryFieldDialogComponent */ - @Input() visible = false; + $isVisible = model(false, { alias: 'isVisible' }); /** - * Output that emit if the sidebar is closed + * Output that emit if the Dialog is closed */ - @Output() closedSidebar = new EventEmitter(); + closedDialog = output(); /** * Store based on the `CategoryFieldStore`. * - * @memberof DotCategoryFieldSidebarComponent + * @memberof DotCategoryFieldDialogComponent */ readonly store = inject(CategoryFieldStore); @@ -95,4 +80,9 @@ export class DotCategoryFieldSidebarComponent implements OnInit, OnDestroy { ngOnDestroy(): void { this.store.clean(); } + + confirmCategories(): void { + this.store.addConfirmedCategories(); + this.closedDialog.emit(); + } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.html b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.html index 94bf4ae7712e..f485bd225323 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.html +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.html @@ -1,26 +1,26 @@ -@if (store.selected().length) { +@if (store.confirmedCategories().length) { + [categories]="store.confirmedCategories()" + (remove)="store.removeConfirmedCategories($event)" /> }
-@if ($showCategoriesSidebar()) { - @defer (when $showCategoriesSidebar()) { - +@if ($showCategoriesDialog()) { + @defer (when $showCategoriesDialog()) { + } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.spec.ts index 529e8a7e869b..8783707f3d48 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.spec.ts @@ -9,7 +9,7 @@ import { ControlContainer, FormControl, FormGroup } from '@angular/forms'; import { DotHttpErrorManagerService, DotMessageService } from '@dotcms/data-access'; import { DotCMSContentlet } from '@dotcms/dotcms-models'; -import { DotCategoryFieldSidebarComponent } from './components/dot-category-field-sidebar/dot-category-field-sidebar.component'; +import { DotCategoryFieldDialogComponent } from './components/dot-category-field-dialog/dot-category-field-dialog.component'; import { DotEditContentCategoryFieldComponent } from './dot-edit-content-category-field.component'; import { CATEGORY_FIELD_CONTENTLET_MOCK, @@ -33,7 +33,7 @@ describe('DotEditContentCategoryFieldComponent', () => { const createComponent = createComponentFactory({ component: DotEditContentCategoryFieldComponent, - imports: [MockComponent(DotCategoryFieldSidebarComponent)], + imports: [MockComponent(DotCategoryFieldDialogComponent)], componentViewProviders: [ { provide: ControlContainer, @@ -71,11 +71,11 @@ describe('DotEditContentCategoryFieldComponent', () => { }); it('should render a button for selecting categories', () => { - expect(spectator.query(byTestId('show-sidebar-btn'))).not.toBeNull(); + expect(spectator.query(byTestId('show-dialog-btn'))).not.toBeNull(); }); it('should the button be type=button', () => { - const selectBtn = spectator.query(byTestId('show-sidebar-btn')); + const selectBtn = spectator.query(byTestId('show-dialog-btn')); expect(selectBtn.type).toBe('button'); }); @@ -130,21 +130,18 @@ describe('DotEditContentCategoryFieldComponent', () => { spectator.detectChanges(); }); - it('should invoke `showCategoriesSidebar` method when the select button is clicked', () => { - const selectBtn = spectator.query(byTestId('show-sidebar-btn')); - const showCategoriesSidebarSpy = jest.spyOn( - spectator.component, - 'openCategoriesSidebar' - ); + it('should invoke `showCategoriesDialog` method when the select button is clicked', () => { + const selectBtn = spectator.query(byTestId('show-dialog-btn')); + const showCategoriesDialogSpy = jest.spyOn(spectator.component, 'openCategoriesDialog'); expect(selectBtn).not.toBeNull(); spectator.click(selectBtn); - expect(showCategoriesSidebarSpy).toHaveBeenCalled(); + expect(showCategoriesDialogSpy).toHaveBeenCalled(); }); - it('should disable the `Select` button after `openCategoriesSidebar` method is invoked', () => { - const selectBtn = spectator.query(byTestId('show-sidebar-btn')) as HTMLButtonElement; + it('should disable the `Select` button after `openCategoriesDialog` method is invoked', () => { + const selectBtn = spectator.query(byTestId('show-dialog-btn')) as HTMLButtonElement; expect(selectBtn).not.toBeNull(); spectator.click(selectBtn); @@ -154,34 +151,34 @@ describe('DotEditContentCategoryFieldComponent', () => { expect(selectBtn.disabled).toBe(true); }); - it('should create a DotEditContentCategoryFieldSidebarComponent instance when the `Select` button is clicked', async () => { - const selectBtn = spectator.query(byTestId('show-sidebar-btn')); + it('should create a DotCategoryFieldDialogComponent instance when the `Select` button is clicked', async () => { + const selectBtn = spectator.query(byTestId('show-dialog-btn')); expect(selectBtn).not.toBeNull(); - expect(spectator.query(DotCategoryFieldSidebarComponent)).toBeNull(); + expect(spectator.query(DotCategoryFieldDialogComponent)).toBeNull(); spectator.click(selectBtn); await spectator.fixture.whenStable(); - expect(spectator.query(DotCategoryFieldSidebarComponent)).not.toBeNull(); + expect(spectator.query(DotCategoryFieldDialogComponent)).not.toBeNull(); }); - it('should remove DotEditContentCategoryFieldSidebarComponent when `closedSidebar` emit', fakeAsync(async () => { - const selectBtn = spectator.query(byTestId('show-sidebar-btn')) as HTMLButtonElement; + it('should remove DotCategoryFieldDialogComponent when `closedDialog` emit', fakeAsync(async () => { + const selectBtn = spectator.query(byTestId('show-dialog-btn')) as HTMLButtonElement; expect(selectBtn).not.toBeNull(); spectator.click(selectBtn); await spectator.fixture.whenStable(); - const sidebarComponentRef = spectator.query(DotCategoryFieldSidebarComponent); - expect(sidebarComponentRef).not.toBeNull(); + const dialogComponentRef = spectator.query(DotCategoryFieldDialogComponent); + expect(dialogComponentRef).not.toBeNull(); - sidebarComponentRef.closedSidebar.emit(); + dialogComponentRef.closedDialog.emit(); spectator.detectComponentChanges(); - // Check if the sidebar component is removed - expect(spectator.query(DotCategoryFieldSidebarComponent)).toBeNull(); + // Check if the dialog component is removed + expect(spectator.query(DotCategoryFieldDialogComponent)).toBeNull(); // Check if the button is enabled again expect(selectBtn.disabled).toBe(false); @@ -197,6 +194,7 @@ describe('DotEditContentCategoryFieldComponent', () => { key: '1234', value: 'test' }); + store.addConfirmedCategories(); spectator.flushEffects(); const categoryValue = spectator.component.categoryFieldControl.value; @@ -205,7 +203,7 @@ describe('DotEditContentCategoryFieldComponent', () => { }); it('should set categoryFieldControl value when removing a category', () => { - store.removeSelected(SELECTED_LIST_MOCK[0]); + store.removeConfirmedCategories(SELECTED_LIST_MOCK[0]); spectator.flushEffects(); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.ts index 96df742a848e..4059ee4e1ce8 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/dot-edit-content-category-field.component.ts @@ -18,7 +18,7 @@ import { DotCMSContentlet, DotCMSContentTypeField } from '@dotcms/dotcms-models' import { DotMessagePipe } from '@dotcms/ui'; import { DotCategoryFieldChipsComponent } from './components/dot-category-field-chips/dot-category-field-chips.component'; -import { DotCategoryFieldSidebarComponent } from './components/dot-category-field-sidebar/dot-category-field-sidebar.component'; +import { DotCategoryFieldDialogComponent } from './components/dot-category-field-dialog/dot-category-field-dialog.component'; import { CategoriesService } from './services/categories.service'; import { CategoryFieldStore } from './store/content-category-field.store'; @@ -39,14 +39,14 @@ import { CategoryFieldStore } from './store/content-category-field.store'; NgClass, DotMessagePipe, DotCategoryFieldChipsComponent, - DotCategoryFieldSidebarComponent + DotCategoryFieldDialogComponent ], templateUrl: './dot-edit-content-category-field.component.html', styleUrl: './dot-edit-content-category-field.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, host: { - '[class.dot-category-field__container--has-categories]': '$hasSelectedCategories()', - '[class.dot-category-field__container]': '!$hasSelectedCategories()' + '[class.dot-category-field__container--has-categories]': '$hasConfirmedCategories()', + '[class.dot-category-field__container]': '!$hasConfirmedCategories()' }, viewProviders: [ { @@ -61,9 +61,9 @@ export class DotEditContentCategoryFieldComponent implements OnInit { readonly #form = inject(ControlContainer).control as FormGroup; readonly #injector = inject(Injector); /** - * Disable the button to open the sidebar + * Disable the button to open the dialog */ - $showCategoriesSidebar = signal(false); + $showCategoriesDialog = signal(false); /** * The `field` variable is of type `DotCMSContentTypeField` and is a required input. * @description The variable represents a field of a DotCMS content type and is a required input. @@ -75,11 +75,11 @@ export class DotEditContentCategoryFieldComponent implements OnInit { */ contentlet = input.required(); /** - * The `$hasSelectedCategories` variable is a computed property that returns a boolean value. + * The `$hasConfirmedCategories` variable is a computed property that returns a boolean value. * * @returns {Boolean} - True if there are selected categories, false otherwise. */ - $hasSelectedCategories = computed(() => !!this.store.hasSelectedCategories()); + $hasConfirmedCategories = computed(() => !!this.store.hasConfirmedCategories()); /** * Getter to retrieve the category field control. * @@ -100,7 +100,7 @@ export class DotEditContentCategoryFieldComponent implements OnInit { }); effect( () => { - const categoryValues = this.store.selectedCategoriesValues(); + const categoryValues = this.store.confirmedCategoriesValues(); if (this.categoryFieldControl) { this.categoryFieldControl.setValue(categoryValues); @@ -112,19 +112,20 @@ export class DotEditContentCategoryFieldComponent implements OnInit { ); } /** - * Open the categories sidebar. + * Open the categories dialog. * * @memberof DotEditContentCategoryFieldComponent */ - openCategoriesSidebar(): void { - this.$showCategoriesSidebar.set(true); + openCategoriesDialog(): void { + this.store.setSelectedCategories(); + this.$showCategoriesDialog.set(true); } /** - * Close the categories' sidebar. + * Close the categories dialog. * * @memberof DotEditContentCategoryFieldComponent */ - closeCategoriesSidebar() { - this.$showCategoriesSidebar.set(false); + closeCategoriesDialog() { + this.$showCategoriesDialog.set(false); } } diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.spec.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.spec.ts index e50fca87e255..d5bf4bb23388 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.spec.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.spec.ts @@ -52,6 +52,7 @@ describe('CategoryFieldStore', () => { expect(store.keyParentPath()).toEqual(EMPTY_ARRAY); expect(store.state()).toEqual(ComponentStatus.INIT); expect(store.selected()).toEqual(EMPTY_ARRAY); + expect(store.confirmedCategories()).toEqual(EMPTY_ARRAY); expect(store.mode()).toEqual('list'); }); @@ -123,13 +124,41 @@ describe('CategoryFieldStore', () => { expect(store.categories().length).toBe(2); }); }); + + it('should remove confirmed categories with given key', () => { + store.addSelected([{ key: '1234', value: 'test' }]); + store.addConfirmedCategories(); + + store.removeConfirmedCategories('1234'); + expect(store.confirmedCategories().length).toEqual(0); + }); + + it('should add selected categories to confirmed categories', () => { + store.addSelected([{ key: '1234', value: 'test' }]); + store.addConfirmedCategories(); + + expect(store.confirmedCategories()).toEqual(store.selected()); + }); + + it('should set selected categories based on confirmed categories', () => { + store.addSelected([{ key: '1234', value: 'test' }]); + store.addConfirmedCategories(); + + store.removeSelected('1234'); + + expect(store.selected()).toEqual(EMPTY_ARRAY); + + store.setSelectedCategories(); + + expect(store.selected()).toEqual(store.confirmedCategories()); + }); }); describe('withComputed', () => { it('should show item after load the values', () => { const expectedSelectedValues = SELECTED_LIST_MOCK; store.load({ field: CATEGORY_FIELD_MOCK, contentlet: CATEGORY_FIELD_CONTENTLET_MOCK }); - expect(store.selectedCategoriesValues().sort()).toEqual(expectedSelectedValues.sort()); + expect(store.confirmedCategoriesValues().sort()).toEqual(expectedSelectedValues.sort()); expect(store.categoryList()).toEqual(EMPTY_ARRAY); }); diff --git a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts index cb41e3ef7705..2d2e7b1bf3d8 100644 --- a/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts +++ b/core-web/libs/edit-content/src/lib/fields/dot-edit-content-category-field/store/content-category-field.store.ts @@ -36,6 +36,7 @@ import { export type CategoryFieldState = { field: DotCMSContentTypeField; selected: DotCategoryFieldKeyValueObj[]; // <- source of selected + confirmedCategories: DotCategoryFieldKeyValueObj[]; // <- source of confirmed categories in the modal. categories: DotCategory[][]; keyParentPath: string[]; // Delete when we have the endpoint for this state: ComponentStatus; @@ -48,6 +49,7 @@ export type CategoryFieldState = { export const initialState: CategoryFieldState = { field: {} as DotCMSContentTypeField, selected: [], + confirmedCategories: [], categories: [], keyParentPath: [], state: ComponentStatus.INIT, @@ -65,9 +67,11 @@ export const CategoryFieldStore = signalStore( withState(initialState), withComputed((store) => ({ /** - * Current selected items (key) from the contentlet + * Current confirmed Categories items (key) from the contentlet */ - selectedCategoriesValues: computed(() => store.selected().map((item) => item.key)), + confirmedCategoriesValues: computed(() => + store.confirmedCategories().map((item) => item.key) + ), /** * Categories for render with added properties @@ -77,9 +81,9 @@ export const CategoryFieldStore = signalStore( ), /** - * Indicates whether any categories are selected. + * Indicates whether any categories are confirmed. */ - hasSelectedCategories: computed(() => !!store.selected().length), + hasConfirmedCategories: computed(() => !!store.confirmedCategories().length), /** * Get the root category inode. @@ -159,12 +163,13 @@ export const CategoryFieldStore = signalStore( return categoryService.getSelectedHierarchy(selectedKeys).pipe( tapResponse({ next: (categoryWithParentPath) => { - const selected = + const confirmedCategories = transformToSelectedObject(categoryWithParentPath); patchState(store, { field, - selected, + selected: confirmedCategories, + confirmedCategories, state: ComponentStatus.LOADED }); }, @@ -239,6 +244,44 @@ export const CategoryFieldStore = signalStore( }); }, + /** + * Removes the confirmed categories with the given key(s). + * + * @param {string | string[]} key - The key(s) of the item(s) to be removed. + * @return {void} + */ + removeConfirmedCategories(key: string | string[]): void { + const newConfirmed = removeItemByKey(store.confirmedCategories(), key); + + patchState(store, { + confirmedCategories: newConfirmed + }); + }, + + /** + * Adds the selected categories to the confirmed categories in the store. + * This method is used when the user confirms the selection of categories in the Dialog. + * + * @return {void} + */ + addConfirmedCategories(): void { + patchState(store, { + confirmedCategories: store.selected() + }); + }, + + /** + * Sets the selected categories in the store to the confirmed categories. + * This method is used when the user open the Dialog of categories. + * + * @return {void} + */ + setSelectedCategories(): void { + patchState(store, { + selected: store.confirmedCategories() + }); + }, + /** * Clears all categories from the store, effectively resetting state related to categories and their parent paths. */ diff --git a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties index 98cae197cf4b..1ed724079866 100644 --- a/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties +++ b/dotCMS/src/main/webapp/WEB-INF/messages/Language.properties @@ -5751,8 +5751,8 @@ edit.content.layout.no.content.to.show = No content to show. content.type.form.banner.message= Enable Edit Content Beta for a fresh editing experience (roll back anytime). edit.content.category-field.show-categories-dialog=Select -edit.content.category-field.sidebar.header.select-categories=Select categories -edit.content.category-field.sidebar.button.clear-all=Clear all +edit.content.category-field.dialog.header.select-categories=Select categories +edit.content.category-field.dialog.button.clear-all=Clear all edit.content.category-field.list.show.more=+ {0} More edit.content.category-field.list.show.less=Less @@ -5765,6 +5765,6 @@ edit.content.category-field.search.not-found.legend=Your search does not match a edit.content.category-field.search.error.title=Something went wrong edit.content.category-field.search.error.legend=Oops! Something went wrong. Please try again later. edit.content.category-field.category.root-name=Root -edit.content.category-field.sidebar.empty-state=Click on the checkbox at the left to add categories. +edit.content.category-field.dialog.empty-state=Click on the checkbox at the left to add categories. edit.content.category-field.search.empty.title=There are no categories yet edit.content.category-field.search.empty.legend=To create a new category, navigate to: Content model > Categories > Click the "+" Plus Button > Add.