[ACS-4721] tags are not displayed properly when resizing browser (#8424)

* ACS-4721 Partial fix issue with resizing window

* ACS-4721 Fixed issue for overlapping view more button with tags, skip calculation if anyone clicked on view more button

* ACS-4721 Unit tests

* ACS-4721 Fixed lint issues

* ACS-4721 Small correction in tests

* ACS-4721 Fixed unit tests

* ACS-4721 Fixed unit tests

* ACS-4721 Resolved issues after opening viewer

* ACS-4721 Formatting

* ACS-4721 Fixed lint issue
This commit is contained in:
AleksanderSklorz 2023-03-30 14:16:04 +02:00 committed by GitHub
parent 3e2683e06b
commit 431b3db08a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 176 additions and 42 deletions

View File

@ -239,6 +239,29 @@ describe('ContentMetadataComponent', () => {
expect(tagService.assignTagsToNode).toHaveBeenCalledWith(node.id, [tag1, tag2]);
}));
it('should call getTagsByNodeId on TagService on save click', fakeAsync( () => {
component.editable = true;
component.displayTags = true;
const property = { key: 'properties.property-key', value: 'original-value' } as CardViewBaseItemModel;
const expectedNode = { ...node, name: 'some-modified-value' };
spyOn(nodesApiService, 'updateNode').and.returnValue(of(expectedNode));
const tagPaging = mockTagPaging();
const getTagsByNodeIdSpy = spyOn(tagService, 'getTagsByNodeId').and.returnValue(of(tagPaging));
component.ngOnInit();
spyOn(tagService, 'removeTag').and.returnValue(of(undefined));
spyOn(tagService, 'assignTagsToNode').and.returnValue(of({}));
updateService.update(property, 'updated-value');
tick(600);
fixture.detectChanges();
findTagsCreator().tagsChange.emit([tagPaging.list.entries[0].entry.tag, 'New tag 3']);
getTagsByNodeIdSpy.calls.reset();
clickOnSave();
expect(tagService.getTagsByNodeId).toHaveBeenCalledWith(node.id);
}));
it('should throw error on unsuccessful save', fakeAsync((done) => {
const logService: LogService = TestBed.inject(LogService);
component.editable = true;

View File

@ -270,6 +270,9 @@ export class ContentMetadataComponent implements OnChanges, OnInit, OnDestroy {
this.revertChanges();
Object.assign(this.node, result.updatedNode);
this.nodesApiService.nodeUpdated.next(this.node);
if (Object.keys(result).length > 1 && this.displayTags) {
this.loadTagsForNode(this.node.id);
}
}
this._saving = false;
});

View File

@ -10,7 +10,8 @@
</mat-chip>
</mat-chip-list>
<button mat-button
*ngIf="limitTagsDisplayed"
[hidden]="!limitTagsDisplayed"
[style.left.px]="viewMoreButtonLeftOffset"
class="adf-view-more-button"
[class.adf-hidden-btn]="!calculationsDone"
(click)="displayAllTags($event)"

View File

@ -4,13 +4,22 @@
flex-direction: row;
width: inherit;
&.adf-flex-column {
flex-direction: column;
}
.adf-view-more-button {
margin: 4px;
color: var(--adf-theme-foreground-text-color-054);
position: absolute;
&[hidden] {
visibility: hidden;
}
}
&.adf-flex-column {
flex-direction: column;
.adf-view-more-button {
position: relative;
}
}
.adf-full-width {

View File

@ -44,15 +44,19 @@ describe('TagNodeList', () => {
},
{
entry: {tag: 'test3', id: 'fb4213c0-729d-466c-9a6c-ee2e937273bf'}
},
{
entry: {tag: 'test4', id: 'as4213c0-729d-466c-9a6c-ee2e937273as'}
}
]
}
};
let component: any;
let component: TagNodeListComponent;
let fixture: ComponentFixture<TagNodeListComponent>;
let element: HTMLElement;
let tagService: TagService;
let resizeCallback: ResizeObserverCallback;
function findViewMoreButton(): HTMLButtonElement {
return element.querySelector('.adf-view-more-button');
@ -70,6 +74,7 @@ describe('TagNodeList', () => {
});
beforeEach(() => {
const resizeObserverSpy = spyOn(window, 'ResizeObserver').and.callThrough();
fixture = TestBed.createComponent(TagNodeListComponent);
tagService = TestBed.inject(TagService);
@ -79,6 +84,7 @@ describe('TagNodeList', () => {
component = fixture.componentInstance;
component.nodeId = 'fake-node-id';
fixture.detectChanges();
resizeCallback = resizeObserverSpy.calls.mostRecent().args[0];
});
describe('Rendering tests', () => {
@ -137,24 +143,29 @@ describe('TagNodeList', () => {
fixture.detectChanges();
await fixture.whenStable();
expect(findViewMoreButton()).toBeNull();
expect(findTagChips().length).toBe(3);
expect(findViewMoreButton().hidden).toBeTrue();
expect(findTagChips()).toHaveSize(4);
});
});
describe('Limit tags display', () => {
let initialEntries: TagEntry[];
async function renderTags(entries?: TagEntry[]): Promise<any> {
if (entries) {
dataTag.list.entries = entries;
}
dataTag.list.entries = entries || initialEntries;
component.tagsEntries = dataTag.list.entries;
fixture.detectChanges();
await fixture.whenStable();
}
beforeAll(() => {
initialEntries = dataTag.list.entries;
});
beforeEach(() => {
component.limitTagsDisplayed = true;
element.style.maxWidth = '200px';
component.ngOnInit();
element.style.maxWidth = '309px';
});
it('should render view more button when limiting is enabled', async () => {
@ -162,8 +173,8 @@ describe('TagNodeList', () => {
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
expect(findViewMoreButton()).not.toBeNull();
expect(findTagChips().length).toBe(component.tagsEntries.length);
expect(findViewMoreButton().hidden).toBeFalse();
expect(findTagChips()).toHaveSize(component.tagsEntries.length);
});
it('should not render view more button when limiting is enabled and all tags fits into container', async () => {
@ -174,8 +185,8 @@ describe('TagNodeList', () => {
fixture.detectChanges();
await fixture.whenStable();
expect(findViewMoreButton()).toBeNull();
expect(findTagChips().length).toBe(3);
expect(findViewMoreButton().hidden).toBeTrue();
expect(findTagChips()).toHaveSize(4);
});
it('should display all tags when view more button is clicked', async () => {
@ -190,8 +201,8 @@ describe('TagNodeList', () => {
await fixture.whenStable();
viewMoreButton = findViewMoreButton();
expect(viewMoreButton).toBeNull();
expect(findTagChips().length).toBe(3);
expect(viewMoreButton.hidden).toBeTrue();
expect(findTagChips()).toHaveSize(4);
});
it('should not render view more button when tag takes more than one line and there are no more tags', async () => {
@ -204,8 +215,8 @@ describe('TagNodeList', () => {
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
expect(findViewMoreButton()).toBeNull();
expect(findTagChips().length).toBe(component.tagsEntries.length);
expect(findViewMoreButton().hidden).toBeTrue();
expect(findTagChips()).toHaveSize(component.tagsEntries.length);
});
it('should render view more button when tag takes more than one line and there are more tags', async () => {
@ -223,8 +234,53 @@ describe('TagNodeList', () => {
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
expect(findViewMoreButton()).not.toBeNull();
expect(findTagChips().length).toBe(component.tagsEntries.length);
const viewMoreButton = findViewMoreButton();
expect(viewMoreButton.hidden).toBeFalse();
expect(viewMoreButton.style.left).toBe('0px');
expect(findTagChips()).toHaveSize(component.tagsEntries.length);
});
it('should not render view more button when there is enough space after resizing', async () => {
await renderTags();
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
element.style.maxWidth = '800px';
resizeCallback([], null);
fixture.detectChanges();
const viewMoreButton = findViewMoreButton();
expect(viewMoreButton.hidden).toBeTrue();
expect(findTagChips()).toHaveSize(4);
});
it('should render view more button when there is not enough space after resizing', async () => {
await renderTags();
element.style.maxWidth = '800px';
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
element.style.maxWidth = '309px';
resizeCallback([], null);
fixture.detectChanges();
expect(findViewMoreButton().hidden).toBeFalse();
expect(findTagChips()).toHaveSize(component.tagsEntries.length);
});
it('should not render view more button again resizing when there is not enough space if user requested to see all tags', async () => {
await renderTags();
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
const viewMoreButton = findViewMoreButton();
viewMoreButton.click();
fixture.detectChanges();
element.style.maxWidth = '309px';
resizeCallback([], null);
fixture.detectChanges();
expect(viewMoreButton.hidden).toBeTrue();
expect(findTagChips()).toHaveSize(4);
});
});
});

View File

@ -15,7 +15,22 @@
* limitations under the License.
*/
import { Component, EventEmitter, Input, OnChanges, Output, ViewEncapsulation, OnDestroy, OnInit, ViewChild, ElementRef, ViewChildren, QueryList } from '@angular/core';
import {
Component,
EventEmitter,
Input,
OnChanges,
Output,
ViewEncapsulation,
OnDestroy,
OnInit,
ViewChild,
ElementRef,
ViewChildren,
QueryList,
ChangeDetectorRef,
AfterViewInit
} from '@angular/core';
import { TagService } from './services/tag.service';
import { TagEntry } from '@alfresco/js-api';
import { Subject } from 'rxjs';
@ -33,7 +48,7 @@ import { MatChip } from '@angular/material/chips';
styleUrls: ['./tag-node-list.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit {
export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit, AfterViewInit {
/* eslint no-underscore-dangle: ["error", { "allow": ["_elementRef"] }]*/
/** The identifier of a node. */
@Input()
@ -57,19 +72,29 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit {
calculationsDone = false;
columnFlexDirection = false;
undisplayedTagsCount = 0;
viewMoreButtonLeftOffset: number;
/** Emitted when a tag is selected. */
@Output()
results = new EventEmitter();
private onDestroy$ = new Subject<boolean>();
private initialLimitTagsDisplayed: boolean;
private initialTagsEntries: TagEntry[] = [];
private viewMoreButtonLeftOffsetBeforeFlexDirection: number;
private requestedDisplayingAllTags = false;
private resizeObserver = new ResizeObserver(() => {
this.calculateTagsToDisplay();
this.changeDetectorRef.detectChanges();
});
/**
* Constructor
*
* @param tagService
* @param changeDetectorRef
*/
constructor(private tagService: TagService) {
constructor(private tagService: TagService, private changeDetectorRef: ChangeDetectorRef) {
}
ngOnChanges() {
@ -77,6 +102,7 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit {
}
ngOnInit() {
this.initialLimitTagsDisplayed = this.limitTagsDisplayed;
this.tagService.refresh
.pipe(takeUntil(this.onDestroy$))
.subscribe(() => this.refreshTag());
@ -90,15 +116,21 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit {
});
}
ngAfterViewInit() {
this.resizeObserver.observe(this.containerView.nativeElement);
}
ngOnDestroy() {
this.onDestroy$.next(true);
this.onDestroy$.complete();
this.resizeObserver.unobserve(this.containerView.nativeElement);
}
refreshTag() {
if (this.nodeId) {
this.tagService.getTagsByNodeId(this.nodeId).subscribe((tagPaging) => {
this.tagsEntries = tagPaging.list.entries;
this.initialTagsEntries = tagPaging.list.entries;
this.results.emit(this.tagsEntries);
});
}
@ -114,29 +146,39 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit {
event.preventDefault();
event.stopPropagation();
this.limitTagsDisplayed = false;
this.requestedDisplayingAllTags = true;
this.resizeObserver.unobserve(this.containerView.nativeElement);
this.refreshTag();
}
private calculateTagsToDisplay() {
let tagsToDisplay = 1;
const containerWidth: number = this.containerView.nativeElement.clientWidth;
const viewMoreBtnWidth: number = this.containerView.nativeElement.children[1].offsetWidth;
const tagChipMargin = this.getTagChipMargin(this.tagChips.get(0));
const tagChipsWidth: number = this.tagChips.reduce((acc, val, index) => {
if (containerWidth - viewMoreBtnWidth > acc + val._elementRef.nativeElement.offsetWidth) {
tagsToDisplay = index + 1;
if (!this.requestedDisplayingAllTags) {
this.tagsEntries = this.initialTagsEntries;
this.changeDetectorRef.detectChanges();
this.undisplayedTagsCount = 0;
let tagsToDisplay = 1;
const containerWidth: number = this.containerView.nativeElement.clientWidth;
const viewMoreBtnWidth: number = this.containerView.nativeElement.children[1].offsetWidth;
const firstTag = this.tagChips.get(0);
const tagChipMargin = firstTag ? this.getTagChipMargin(this.tagChips.get(0)) : 0;
const tagChipsWidth: number = this.tagChips.reduce((width, val, index) => {
width += val._elementRef.nativeElement.offsetWidth + tagChipMargin;
if (containerWidth - viewMoreBtnWidth > width) {
tagsToDisplay = index + 1;
this.viewMoreButtonLeftOffset = width;
this.viewMoreButtonLeftOffsetBeforeFlexDirection = width;
}
return width;
}, 0);
if ((containerWidth - tagChipsWidth) <= 0) {
this.columnFlexDirection = tagsToDisplay === 1 && (containerWidth < (this.tagChips.get(0)._elementRef.nativeElement.offsetWidth + viewMoreBtnWidth));
this.undisplayedTagsCount = this.tagsEntries.length - tagsToDisplay;
this.tagsEntries = this.tagsEntries.slice(0, tagsToDisplay);
}
return acc + val._elementRef.nativeElement.offsetWidth + tagChipMargin;
}, 0);
if ((containerWidth - tagChipsWidth) <= 0) {
this.columnFlexDirection = tagsToDisplay === 1 && (containerWidth < (this.tagChips.get(0)._elementRef.nativeElement.offsetWidth + viewMoreBtnWidth));
this.undisplayedTagsCount = this.tagsEntries.length - tagsToDisplay;
this.tagsEntries = this.tagsEntries.slice(0, tagsToDisplay);
this.limitTagsDisplayed = this.undisplayedTagsCount ? this.initialLimitTagsDisplayed : false;
this.viewMoreButtonLeftOffset = this.columnFlexDirection ? 0 : this.viewMoreButtonLeftOffsetBeforeFlexDirection;
this.calculationsDone = true;
}
if (!this.undisplayedTagsCount) {
this.limitTagsDisplayed = false;
}
this.calculationsDone = true;
}
private getTagChipMargin(chip: MatChip): number {