2024-04-16 00:55:00 +03:00
|
|
|
///
|
|
|
|
|
/// Copyright © 2016-2024 The Thingsboard Authors
|
|
|
|
|
///
|
|
|
|
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
/// you may not use this file except in compliance with the License.
|
|
|
|
|
/// You may obtain a copy of the License at
|
|
|
|
|
///
|
|
|
|
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
///
|
|
|
|
|
/// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
/// See the License for the specific language governing permissions and
|
|
|
|
|
/// limitations under the License.
|
|
|
|
|
///
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
Directive,
|
|
|
|
|
ElementRef,
|
2024-04-19 12:56:11 +03:00
|
|
|
Inject,
|
2024-04-16 00:55:00 +03:00
|
|
|
Input,
|
|
|
|
|
OnDestroy,
|
2024-07-18 18:18:31 +03:00
|
|
|
Renderer2,
|
|
|
|
|
AfterViewInit,
|
2024-04-16 00:55:00 +03:00
|
|
|
} from '@angular/core';
|
|
|
|
|
import { isEqual } from '@core/utils';
|
|
|
|
|
import { TranslateService } from '@ngx-translate/core';
|
2024-04-19 12:56:11 +03:00
|
|
|
import { WINDOW } from '@core/services/window.service';
|
|
|
|
|
import { fromEvent, Subject } from 'rxjs';
|
|
|
|
|
import { takeUntil } from 'rxjs/operators';
|
2024-04-16 00:55:00 +03:00
|
|
|
|
|
|
|
|
@Directive({
|
|
|
|
|
// eslint-disable-next-line @angular-eslint/directive-selector
|
2024-07-18 18:18:31 +03:00
|
|
|
selector: '[tb-ellipsis-chip-list]',
|
|
|
|
|
standalone: true,
|
2024-04-16 00:55:00 +03:00
|
|
|
})
|
2024-07-18 18:18:31 +03:00
|
|
|
export class EllipsisChipListDirective implements OnDestroy, AfterViewInit {
|
2024-04-16 00:55:00 +03:00
|
|
|
|
|
|
|
|
chipsValue: string[];
|
|
|
|
|
|
2024-04-19 12:56:11 +03:00
|
|
|
private destroy$ = new Subject<void>();
|
2024-07-18 18:18:31 +03:00
|
|
|
private intersectionObserver: IntersectionObserver;
|
2024-04-19 12:56:11 +03:00
|
|
|
|
2024-04-16 00:55:00 +03:00
|
|
|
@Input('tb-ellipsis-chip-list')
|
2024-04-19 12:56:11 +03:00
|
|
|
set chips(value: string[]) {
|
2024-04-16 00:55:00 +03:00
|
|
|
if (!isEqual(this.chipsValue, value)) {
|
|
|
|
|
this.chipsValue = value;
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.adjustChips();
|
|
|
|
|
}, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constructor(private el: ElementRef,
|
|
|
|
|
private renderer: Renderer2,
|
2024-04-19 12:56:11 +03:00
|
|
|
private translate: TranslateService,
|
|
|
|
|
@Inject(WINDOW) private window: Window) {
|
|
|
|
|
this.renderer.setStyle(this.el.nativeElement, 'max-height', '48px');
|
|
|
|
|
this.renderer.setStyle(this.el.nativeElement, 'overflow', 'auto');
|
|
|
|
|
fromEvent(window, 'resize').pipe(
|
|
|
|
|
takeUntil(this.destroy$)
|
|
|
|
|
).subscribe(() => {
|
|
|
|
|
this.adjustChips();
|
|
|
|
|
});
|
2024-07-18 18:18:31 +03:00
|
|
|
|
|
|
|
|
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
|
|
|
entries.forEach(entry => {
|
|
|
|
|
if (entry.isIntersecting) {
|
|
|
|
|
this.adjustChips();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ngAfterViewInit(): void {
|
|
|
|
|
this.intersectionObserver.observe(this.el.nativeElement);
|
2024-04-16 00:55:00 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private adjustChips(): void {
|
|
|
|
|
const chipListElement = this.el.nativeElement;
|
|
|
|
|
const ellipsisChip = this.el.nativeElement.querySelector('.ellipsis-chip');
|
2024-04-19 12:56:11 +03:00
|
|
|
const margin = parseFloat(this.window.getComputedStyle(ellipsisChip).marginLeft) || 0;
|
2024-04-29 17:47:13 +03:00
|
|
|
const chipNodes = chipListElement.querySelectorAll('mat-chip:not(.ellipsis-chip)');
|
|
|
|
|
|
|
|
|
|
if (this.chipsValue.length > 1) {
|
|
|
|
|
const ellipsisText = this.el.nativeElement.querySelector('.ellipsis-text');
|
|
|
|
|
this.renderer.setStyle(ellipsisChip, 'display', 'inline-flex');
|
|
|
|
|
ellipsisText.innerHTML = this.translate.instant('gateway.ellipsis-chips-text',
|
|
|
|
|
{count: (this.chipsValue.length)});
|
2024-04-16 00:55:00 +03:00
|
|
|
|
2024-04-29 17:47:13 +03:00
|
|
|
const availableWidth = chipListElement.offsetWidth - (ellipsisChip.offsetWidth + margin);
|
|
|
|
|
let usedWidth = 0;
|
|
|
|
|
let visibleChipsCount = 0;
|
|
|
|
|
|
|
|
|
|
chipNodes.forEach((chip) => {
|
|
|
|
|
this.renderer.setStyle(chip, 'display', 'inline-flex');
|
2024-05-02 16:57:25 +03:00
|
|
|
const textLabelContainer = chip.querySelector('.mdc-evolution-chip__text-label');
|
|
|
|
|
|
|
|
|
|
this.applyMaxChipTextWidth(textLabelContainer, (availableWidth / 3));
|
|
|
|
|
|
2024-04-29 17:47:13 +03:00
|
|
|
if ((usedWidth + (chip.offsetWidth + margin) <= availableWidth) && (visibleChipsCount < this.chipsValue.length)) {
|
|
|
|
|
visibleChipsCount++;
|
|
|
|
|
usedWidth += chip.offsetWidth + margin;
|
|
|
|
|
} else {
|
|
|
|
|
this.renderer.setStyle(chip, 'display', 'none');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ellipsisText.innerHTML = this.translate.instant('gateway.ellipsis-chips-text',
|
|
|
|
|
{count: (this.chipsValue.length - visibleChipsCount)});
|
|
|
|
|
|
|
|
|
|
if (visibleChipsCount === this.chipsValue?.length) {
|
|
|
|
|
this.renderer.setStyle(ellipsisChip, 'display', 'none');
|
2024-04-16 00:55:00 +03:00
|
|
|
}
|
2024-04-29 17:47:13 +03:00
|
|
|
} else if (this.chipsValue.length === 1) {
|
|
|
|
|
const chipLabelContainer = chipNodes[0].querySelector('.mdc-evolution-chip__action');
|
|
|
|
|
const textLabelContainer = chipLabelContainer.querySelector('.mdc-evolution-chip__text-label');
|
|
|
|
|
const leftPadding = parseFloat(this.window.getComputedStyle(chipLabelContainer).paddingLeft) || 0;
|
|
|
|
|
const rightPadding = parseFloat(this.window.getComputedStyle(chipLabelContainer).paddingRight) || 0;
|
|
|
|
|
const computedTextWidth = chipListElement.offsetWidth - margin -
|
|
|
|
|
(leftPadding + rightPadding);
|
2024-04-16 00:55:00 +03:00
|
|
|
|
2024-04-30 18:08:23 +03:00
|
|
|
this.renderer.setStyle(ellipsisChip, 'display', 'none');
|
|
|
|
|
this.renderer.setStyle(chipNodes[0], 'display', 'inline-flex');
|
|
|
|
|
|
2024-05-02 16:57:25 +03:00
|
|
|
this.applyMaxChipTextWidth(textLabelContainer, computedTextWidth);
|
2024-04-29 17:47:13 +03:00
|
|
|
} else {
|
|
|
|
|
this.renderer.setStyle(ellipsisChip, 'display', 'none');
|
2024-04-16 00:55:00 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-02 16:57:25 +03:00
|
|
|
private applyMaxChipTextWidth(element: HTMLElement, widthLimit: number): void {
|
|
|
|
|
this.renderer.setStyle(element, 'max-width', widthLimit + 'px');
|
|
|
|
|
this.renderer.setStyle(element, 'overflow', 'hidden');
|
|
|
|
|
this.renderer.setStyle(element, 'text-overflow', 'ellipsis');
|
|
|
|
|
this.renderer.setStyle(element, 'white-space', 'nowrap');
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-19 12:56:11 +03:00
|
|
|
ngOnDestroy(): void {
|
|
|
|
|
this.destroy$.next();
|
|
|
|
|
this.destroy$.complete();
|
2024-07-18 18:18:31 +03:00
|
|
|
this.intersectionObserver.disconnect();
|
2024-04-16 00:55:00 +03:00
|
|
|
}
|
|
|
|
|
}
|