[ACS-4124] Display only as much tags as fits container in tag node list (#8247)

* [ACS-4124] Display only as much tags as fits container in tag node list

* [ACS-4124] CR fixes

* [ACS-4124] Add hidden, lint fixes, event propagation stopped
This commit is contained in:
MichalKinas
2023-02-14 11:04:07 +01:00
committed by GitHub
parent 6e99dd663a
commit b1311c6966
7 changed files with 193 additions and 24 deletions

View File

@@ -544,5 +544,8 @@
"NODE_COUNTER": {
"SELECTED_COUNT": "{{ count }} selected"
},
"TAG_NODE_LIST": {
"VIEW_MORE": "View {{ count }} more"
},
"swsdp": "Sample: Web Site Design Project"
}

View File

@@ -1,9 +1,19 @@
<mat-chip-list>
<mat-chip class="adf-tag-chips"
*ngFor="let currentEntry of tagsEntries; let idx = index" (removed)="removeTag(currentEntry.entry.id)">
<span id="tag_name_{{idx}}">{{currentEntry.entry.tag}}</span>
<mat-icon *ngIf="showDelete" id="tag_chips_delete_{{currentEntry.entry.tag}}"
class="adf-tag-chips-delete-icon" matChipRemove>cancel
</mat-icon>
</mat-chip>
</mat-chip-list>
<div class="adf-tag-node-list" [class.adf-flex-column]="limitTagsDisplayed && (!calculationsDone || columnFlexDirection)" #nodeListContainer>
<mat-chip-list [class.adf-full-width]="limitTagsDisplayed && !calculationsDone">
<mat-chip class="adf-tag-chips"
*ngFor="let currentEntry of tagsEntries; let idx = index"
(removed)="removeTag(currentEntry.entry.id)">
<span id="tag_name_{{idx}}">{{currentEntry.entry.tag}}</span>
<mat-icon *ngIf="showDelete" id="tag_chips_delete_{{currentEntry.entry.tag}}"
class="adf-tag-chips-delete-icon" matChipRemove>cancel
</mat-icon>
</mat-chip>
</mat-chip-list>
<button mat-button
*ngIf="limitTagsDisplayed"
class="adf-view-more-button"
[class.adf-hidden-btn]="!calculationsDone"
(click)="displayAllTags($event)"
>{{ 'TAG_NODE_LIST.VIEW_MORE' | translate: { count: undisplayedTagsCount} }}
</button>
</div>

View File

@@ -1,7 +1,31 @@
.adf-tag-node-list {
display: flex;
align-items: center;
flex-direction: row;
width: inherit;
&.adf-flex-column {
flex-direction: column;
}
.adf-view-more-button {
margin: 4px;
color: var(--theme-text-color);
}
.adf-full-width {
width: 100%;
}
.adf-hidden-btn {
visibility: hidden;
}
.adf-tag-chips {
color: var(--theme-primary-color-default-contrast);
background-color: var(--theme-primary-color);
height: auto;
word-break: break-word;
}
.adf-tag-chips-delete {

View File

@@ -34,11 +34,17 @@ describe('TagNodeList', () => {
skipCount: 0,
maxItems: 100
},
entries: [{
entry: {tag: 'test1', id: '0ee933fa-57fc-4587-8a77-b787e814f1d2'}
}, {entry: {tag: 'test2', id: 'fcb92659-1f10-41b4-9b17-851b72a3b597'}}, {
entry: {tag: 'test3', id: 'fb4213c0-729d-466c-9a6c-ee2e937273bf'}
}]
entries: [
{
entry: {tag: 'test1', id: '0ee933fa-57fc-4587-8a77-b787e814f1d2'}
},
{
entry: {tag: 'test2', id: 'fcb92659-1f10-41b4-9b17-851b72a3b597'}
},
{
entry: {tag: 'test3', id: 'fb4213c0-729d-466c-9a6c-ee2e937273bf'}
}
]
}
};
@@ -62,14 +68,13 @@ describe('TagNodeList', () => {
element = fixture.nativeElement;
component = fixture.componentInstance;
component.nodeId = 'fake-node-id';
fixture.detectChanges();
});
describe('Rendering tests', () => {
it('Tag list relative a single node should be rendered', async () => {
component.nodeId = 'fake-node-id';
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
@@ -84,8 +89,6 @@ describe('TagNodeList', () => {
});
it('Tag list click on delete button should delete the tag', async () => {
component.nodeId = 'fake-node-id';
spyOn(tagService, 'removeTag').and.returnValue(of(true));
component.ngOnChanges();
@@ -99,7 +102,6 @@ describe('TagNodeList', () => {
});
it('Should not show the delete tag button if showDelete is false', async () => {
component.nodeId = 'fake-node-id';
component.showDelete = false;
component.ngOnChanges();
@@ -111,7 +113,6 @@ describe('TagNodeList', () => {
});
it('Should show the delete tag button if showDelete is true', async () => {
component.nodeId = 'fake-node-id';
component.showDelete = true;
component.ngOnChanges();
@@ -121,5 +122,66 @@ describe('TagNodeList', () => {
const deleteButton: any = element.querySelector('#tag_chips_delete_test1');
expect(deleteButton).not.toBeNull();
});
it('should not render view more button by default', async () => {
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
const viewMoreButton = element.querySelector('.adf-view-more-button');
const tagChips = element.querySelectorAll('.adf-tag-chips');
expect(viewMoreButton).toBeNull();
expect(tagChips.length).toBe(3);
});
});
describe('Limit tags display', () => {
beforeEach(async () => {
component.limitTagsDisplayed = true;
element.style.maxWidth = '200px';
component.tagsEntries = dataTag.list.entries;
fixture.detectChanges();
await fixture.whenStable();
});
it('should render view more button when limiting is enabled', async () => {
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
const viewMoreButton = element.querySelector('.adf-view-more-button');
const tagChips = element.querySelectorAll('.adf-tag-chips');
expect(viewMoreButton).not.toBeNull();
expect(tagChips.length).toBe(component.tagsEntries.length);
});
it('should not render view more button when limiting is enabled and all tags fits into container', async () => {
element.style.maxWidth = '800px';
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
const viewMoreButton = element.querySelector('.adf-view-more-button');
const tagChips = element.querySelectorAll('.adf-tag-chips');
expect(viewMoreButton).toBeNull();
expect(tagChips.length).toBe(3);
});
it('should display all tags when view more button is clicked', async () => {
component.ngOnChanges();
fixture.detectChanges();
await fixture.whenStable();
let viewMoreButton: HTMLButtonElement = element.querySelector('.adf-view-more-button');
let tagChips = element.querySelectorAll('.adf-tag-chips');
viewMoreButton.click();
fixture.detectChanges();
await fixture.whenStable();
viewMoreButton = element.querySelector('.adf-view-more-button');
tagChips = element.querySelectorAll('.adf-tag-chips');
expect(viewMoreButton).toBeNull();
expect(tagChips.length).toBe(3);
});
});
});

View File

@@ -15,11 +15,12 @@
* limitations under the License.
*/
import { Component, EventEmitter, Input, OnChanges, Output, ViewEncapsulation, OnDestroy, OnInit } from '@angular/core';
import { Component, EventEmitter, Input, OnChanges, Output, ViewEncapsulation, OnDestroy, OnInit, ViewChild, ElementRef, ViewChildren, QueryList } from '@angular/core';
import { TagService } from './services/tag.service';
import { TagEntry } from '@alfresco/js-api';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { MatChip } from '@angular/material/chips';
/**
*
@@ -30,10 +31,10 @@ import { takeUntil } from 'rxjs/operators';
selector: 'adf-tag-node-list',
templateUrl: './tag-node-list.component.html',
styleUrls: ['./tag-node-list.component.scss'],
encapsulation: ViewEncapsulation.None,
host: { class: 'adf-tag-node-list' }
encapsulation: ViewEncapsulation.None
})
export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit {
/* eslint no-underscore-dangle: ["error", { "allow": ["_elementRef"] }]*/
/** The identifier of a node. */
@Input()
nodeId: string;
@@ -42,7 +43,20 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit {
@Input()
showDelete = true;
tagsEntries: TagEntry[];
/** Should limit number of tags displayed */
@Input()
limitTagsDisplayed = false;
@ViewChild('nodeListContainer')
containerView: ElementRef;
@ViewChildren(MatChip)
tagChips: QueryList<MatChip>;
tagsEntries: TagEntry[] = [];
calculationsDone = false;
columnFlexDirection = false;
undisplayedTagsCount = 0;
/** Emitted when a tag is selected. */
@Output()
@@ -66,6 +80,14 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit {
this.tagService.refresh
.pipe(takeUntil(this.onDestroy$))
.subscribe(() => this.refreshTag());
this.results
.pipe(takeUntil(this.onDestroy$))
.subscribe(() => {
if (this.limitTagsDisplayed && this.tagsEntries.length > 0) {
this.calculateTagsToDisplay();
}
});
}
ngOnDestroy() {
@@ -87,4 +109,37 @@ export class TagNodeListComponent implements OnChanges, OnDestroy, OnInit {
this.refreshTag();
});
}
displayAllTags(event: Event): void {
event.preventDefault();
event.stopPropagation();
this.limitTagsDisplayed = false;
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;
}
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);
} else {
this.limitTagsDisplayed = false;
}
this.calculationsDone = true;
}
private getTagChipMargin(chip: MatChip): number {
const tagChipStyles = window.getComputedStyle(chip._elementRef.nativeElement);
return parseInt(tagChipStyles.marginLeft, 10) + parseInt(tagChipStyles.marginRight, 10);
}
}

View File

@@ -44,6 +44,7 @@ import { ExtensionService } from '../../services/extension.service';
.adf-dynamic-column {
display: flex;
align-items: center;
width: inherit;
}
`
]