From cbff51df3bd5889888cdc9209510f5b649fe6340 Mon Sep 17 00:00:00 2001 From: Alfredo Li Date: Mon, 25 Apr 2022 09:01:29 -0600 Subject: [PATCH] dotCMS/core#21915 Add inline edit to content type name and Icon --- ...t-type-fields-properties-form.component.ts | 2 +- .../content-types-layout.component.html | 27 ++++++++++- .../content-types-layout.component.scss | 27 +++++++++++ .../content-types-layout.component.spec.ts | 45 ++++++++++++++++--- .../layout/content-types-layout.component.ts | 39 +++++++++++++++- .../dot-content-types-edit.component.html | 1 + .../dot-content-types-edit.component.spec.ts | 27 +++++++++++ .../dot-content-types-edit.component.ts | 11 +++++ .../dot-content-types-edit.module.ts | 2 + 9 files changed, 172 insertions(+), 9 deletions(-) diff --git a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/content-type-fields-properties-form/content-type-fields-properties-form.component.ts b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/content-type-fields-properties-form/content-type-fields-properties-form.component.ts index 7dc88f8476..7ae4f4bdf5 100644 --- a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/content-type-fields-properties-form/content-type-fields-properties-form.component.ts +++ b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/fields/content-type-fields-properties-form/content-type-fields-properties-form.component.ts @@ -42,7 +42,7 @@ export class ContentTypeFieldsPropertiesFormComponent implements OnChanges, OnIn constructor(private fb: FormBuilder, private fieldPropertyService: FieldPropertyService) {} ngOnChanges(changes: SimpleChanges): void { - if (changes.formFieldData.currentValue && this.formFieldData) { + if (changes.formFieldData?.currentValue && this.formFieldData) { this.destroy(); setTimeout(this.init.bind(this), 0); diff --git a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.html b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.html index 96866c8026..594a705ed3 100644 --- a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.html +++ b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.html @@ -2,7 +2,24 @@
-

{{ contentType.name }}

+ + + +

{{ contentType.name }}

+
+ + + +
@@ -10,11 +27,17 @@

{{ contentType.name }}

{{ 'contenttypes.content.variable' | dm }}: - {{ 'contenttypes.form.identifier' | dm }}: {{ contentType.id }} + {{ 'contenttypes.form.identifier' | dm }}: +
diff --git a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.scss b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.scss index 1aad17f990..5ef19bfd2d 100644 --- a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.scss +++ b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.scss @@ -102,6 +102,33 @@ $top-height: ($toolbar-height + $tabview-nav-height + $dot-secondary-toolbar-mai header { display: flex; align-items: center; + + dot-icon { + align-items: center; + margin-right: $spacing-2; + } + + ::ng-deep { + p-inplace { + h4, + input { + min-width: 50px; + } + + .p-inplace-display { + display: flex; + padding: 0; + } + + .p-inplace-content input { + height: 36px; + } + + button { + margin-left: $spacing-2; + } + } + } } h4 { diff --git a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.spec.ts b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.spec.ts index be2460cdf0..5507faf4ad 100644 --- a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.spec.ts +++ b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.spec.ts @@ -27,6 +27,7 @@ import { TabViewModule } from 'primeng/tabview'; import { SplitButtonModule } from 'primeng/splitbutton'; import { MenuItem } from 'primeng/api'; import { DotPortletBoxModule } from '@components/dot-portlet-base/components/dot-portlet-box/dot-portlet-box.module'; +import { InplaceModule } from 'primeng/inplace'; @Component({ selector: 'dot-content-types-fields-list', @@ -78,6 +79,7 @@ class FieldDragDropServiceMock { const fakeContentType: DotCMSContentType = { ...dotcmsContentTypeBasicMock, + icon: 'testIcon', id: '1234567890', name: 'name', variable: 'helloVariable', @@ -121,6 +123,7 @@ describe('ContentTypesLayoutComponent', () => { DotCopyLinkModule, DotPipesModule, SplitButtonModule, + InplaceModule, HttpClientTestingModule, DotPortletBoxModule ], @@ -155,9 +158,8 @@ describe('ContentTypesLayoutComponent', () => { }); it('should set the field and row bag options', () => { - const fieldDragDropService: FieldDragDropService = fixture.debugElement.injector.get( - FieldDragDropService - ); + const fieldDragDropService: FieldDragDropService = + fixture.debugElement.injector.get(FieldDragDropService); fixture.componentInstance.contentType = fakeContentType; spyOn(fieldDragDropService, 'setBagOptions'); fixture.detectChanges(); @@ -214,11 +216,38 @@ describe('ContentTypesLayoutComponent', () => { }); it('should have elements in the correct place', () => { + expect( + de.query(By.css('.main-toolbar-left header dot-icon')).componentInstance.name + ).toBe(fakeContentType.icon); + expect(de.query(By.css('.main-toolbar-left header p-inplace'))).toBeDefined(); + expect( + de.query(By.css('.main-toolbar-left header p-inplace h4')).nativeElement.innerHTML + ).toBe(fakeContentType.name); expect(de.query(By.css('.main-toolbar-left .content-type__title'))).toBeDefined(); expect(de.query(By.css('.main-toolbar-left .content-type__info'))).toBeDefined(); expect(de.query(By.css('.main-toolbar-right #form-edit-button'))).toBeDefined(); }); + it('should set and emit change name of Content Type', () => { + de.query(By.css('.main-toolbar-left header p-inplace h4')).nativeElement.click(); + fixture.detectChanges(); + + spyOn(de.componentInstance.changeContentTypeName, 'emit'); + + expect(de.query(By.css('.main-toolbar-left header p-inplace input'))).toBeDefined(); + de.query(By.css('.main-toolbar-left header p-inplace input')).nativeElement.value = + 'changedName'; + de.query(By.css('.main-toolbar-left header p-inplace input')).triggerEventHandler( + 'keydown.enter', + { + stopPropagation: jasmine.createSpy('stopPropagation') + } + ); + expect(de.componentInstance.changeContentTypeName.emit).toHaveBeenCalledWith( + 'changedName' + ); + }); + it('should have api link component', () => { expect(de.query(By.css('dot-api-link')).componentInstance.link).toBe( '/api/v1/contenttype/id/1234567890' @@ -226,8 +255,14 @@ describe('ContentTypesLayoutComponent', () => { }); it('should have copy variable link', () => { - expect(de.query(By.css('dot-copy-link')).componentInstance.copy).toBe( - 'helloVariable' + expect( + de.query(By.css('[data-testId="copyVariableName"]')).componentInstance.copy + ).toBe('helloVariable'); + }); + + it('should have copy identifier link', () => { + expect(de.query(By.css('[data-testId="copyIdentifier"]')).componentInstance.copy).toBe( + '1234567890' ); }); diff --git a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.ts b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.ts index 44494e082c..0d6480fbdd 100644 --- a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.ts +++ b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/components/layout/content-types-layout.component.ts @@ -1,4 +1,13 @@ -import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; +import { + Component, + ElementRef, + EventEmitter, + Input, + OnChanges, + OnInit, + Output, + ViewChild +} from '@angular/core'; import { DotMessageService } from '@services/dot-message/dot-messages.service'; import { DotMenuService } from '@services/dot-menu.service'; import { FieldDragDropService } from '../fields/service'; @@ -8,6 +17,7 @@ import { DotEventsService } from '@services/dot-events/dot-events.service'; import { DotCMSContentType } from '@dotcms/dotcms-models'; import { DotCurrentUserService } from '@services/dot-current-user/dot-current-user.service'; import { Observable } from 'rxjs'; +import { Inplace } from 'primeng/inplace'; @Component({ selector: 'dot-content-type-layout', @@ -17,9 +27,14 @@ import { Observable } from 'rxjs'; export class ContentTypesLayoutComponent implements OnChanges, OnInit { @Input() contentType: DotCMSContentType; @Output() openEditDialog: EventEmitter = new EventEmitter(); + @Output() changeContentTypeName: EventEmitter = new EventEmitter(); + @ViewChild('contentTypeNameInput') contentTypeNameInput: ElementRef; + @ViewChild('contentTypeInlineEdit') contentTypeInlineEdit: Inplace; + permissionURL: string; pushHistoryURL: string; relationshipURL: string; + contentTypeNameInputSize: string; showPermissionsTab: Observable; actions: MenuItem[]; @@ -52,10 +67,32 @@ export class ContentTypesLayoutComponent implements OnChanges, OnInit { } } + /** + * Emits add-row event to add new row + * + * @memberof ContentTypesLayoutComponent + */ + fireAddRowEvent(): void { this.dotEventsService.notify('add-row'); } + /** + * Emits new name to parent component and close Edit Inline mode + * + * @memberof ContentTypesLayoutComponent + */ + fireChangeName(): void { + const contentTypeName = this.contentTypeNameInput.nativeElement.value.trim(); + this.changeContentTypeName.emit(contentTypeName); + this.contentType.name = contentTypeName; + this.contentTypeInlineEdit.deactivate(); + } + + editInlineActivate(event): void { + this.contentTypeNameInputSize = event.target.offsetWidth; + } + private loadActions(): void { this.actions = [ { diff --git a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/dot-content-types-edit.component.html b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/dot-content-types-edit.component.html index 4e590de642..e1cabee0ba 100644 --- a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/dot-content-types-edit.component.html +++ b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/dot-content-types-edit.component.html @@ -1,6 +1,7 @@ = new EventEmitter(); + @Output() changeContentTypeName: EventEmitter = new EventEmitter(); } @Component({ @@ -752,6 +753,32 @@ describe('DotContentTypesEditComponent', () => { expect(dotHttpErrorManagerService.handle).toHaveBeenCalledTimes(1); }); + it('should update Content Type name on dot-content-type-layout event', () => { + const responseContentType = Object.assign({}, fakeContentType, { + name: 'CT changed' + }); + + spyOn(crudService, 'putData').and.returnValue(of(responseContentType)); + + const contentTypeLayout = de.query(By.css('dot-content-type-layout')); + contentTypeLayout.triggerEventHandler('changeContentTypeName', 'CT changed'); + + const replacedWorkflowsPropContentType = { + ...responseContentType + }; + + replacedWorkflowsPropContentType['workflow'] = fakeContentType.workflows.map( + (workflow) => workflow.id + ); + delete replacedWorkflowsPropContentType.workflows; + + expect(crudService.putData).toHaveBeenCalledWith( + 'v1/contenttype/id/1234567890', + replacedWorkflowsPropContentType + ); + expect(comp.data).toEqual(responseContentType, 'set data with response'); + }); + describe('update', () => { let contentTypeForm: DebugElement; diff --git a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/dot-content-types-edit.component.ts b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/dot-content-types-edit.component.ts index 57641e5bda..fef2566f2f 100644 --- a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/dot-content-types-edit.component.ts +++ b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/dot-content-types-edit.component.ts @@ -124,6 +124,17 @@ export class DotContentTypesEditComponent implements OnInit, OnDestroy { this.setEditContentletDialogOptions(); } + /** + * Set updated name on Content Type and send a request to save it + * @param {string} name + * + * @memberof DotContentTypesEditComponent + */ + editContentTypeName(name: string): void { + const updatedContentType = { ...this.data, name }; + this.updateContentType(updatedContentType); + } + /** * Set the icon, labels and placeholder in the template * @memberof DotContentTypesEditComponent diff --git a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/dot-content-types-edit.module.ts b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/dot-content-types-edit.module.ts index ee154b1ff7..6ae39fbe07 100644 --- a/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/dot-content-types-edit.module.ts +++ b/apps/dotcms-ui/src/app/portlets/shared/dot-content-types-edit/dot-content-types-edit.module.ts @@ -69,6 +69,7 @@ import { OverlayPanelModule } from 'primeng/overlaypanel'; import { RadioButtonModule } from 'primeng/radiobutton'; import { SplitButtonModule } from 'primeng/splitbutton'; import { TabViewModule } from 'primeng/tabview'; +import { InplaceModule } from 'primeng/inplace'; import { DotRelationshipTreeModule } from '@components/dot-relationship-tree/dot-relationship-tree.module'; import { DotPortletBoxModule } from '@components/dot-portlet-base/components/dot-portlet-box/dot-portlet-box.module'; import { DotMdIconSelectorModule } from '@dotcms/app/view/components/_common/dot-md-icon-selector/dot-md-icon-selector.module'; @@ -129,6 +130,7 @@ import { DotMdIconSelectorModule } from '@dotcms/app/view/components/_common/dot DropdownModule, FormsModule, IFrameModule, + InplaceModule, InputTextModule, MultiSelectModule, OverlayPanelModule,