dashboardjavacloudcoapiotiot-analyticsiot-platformiot-solutionskafkalwm2mmicroservicesmiddlewaremqttnettyplatformsnmpthingsboardvisualizationwebsocketswidgets
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
167 lines
4.5 KiB
167 lines
4.5 KiB
///
|
|
/// Copyright © 2016-2023 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 {
|
|
AfterViewInit, ChangeDetectorRef,
|
|
Component,
|
|
Input,
|
|
OnChanges, OnDestroy,
|
|
OnInit,
|
|
Renderer2,
|
|
SimpleChanges,
|
|
TemplateRef,
|
|
ViewChild,
|
|
ViewEncapsulation
|
|
} from '@angular/core';
|
|
import {
|
|
GridEntitiesFetchFunction,
|
|
ScrollGridColumns,
|
|
ScrollGridDatasource
|
|
} from '@shared/components/grid/scroll-grid-datasource';
|
|
import { BreakpointObserver } from '@angular/cdk/layout';
|
|
import { isObject } from '@app/core/utils';
|
|
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
|
|
import { ResizeObserver } from '@juggle/resize-observer';
|
|
|
|
export type ItemSizeFunction = (itemWidth: number) => number;
|
|
|
|
export interface ItemSizeStrategy {
|
|
defaultItemSize: number;
|
|
itemSizeFunction: ItemSizeFunction;
|
|
}
|
|
|
|
@Component({
|
|
selector: 'tb-scroll-grid',
|
|
templateUrl: './scroll-grid.component.html',
|
|
styleUrls: ['./scroll-grid.component.scss'],
|
|
encapsulation: ViewEncapsulation.None
|
|
})
|
|
export class ScrollGridComponent<T, F> implements OnInit, AfterViewInit, OnChanges, OnDestroy {
|
|
|
|
@ViewChild('viewport')
|
|
viewport: CdkVirtualScrollViewport;
|
|
|
|
@Input()
|
|
columns: ScrollGridColumns | number = 1;
|
|
|
|
@Input()
|
|
fetchFunction: GridEntitiesFetchFunction<T, F>;
|
|
|
|
@Input()
|
|
filter: F;
|
|
|
|
@Input()
|
|
itemSize: number | ItemSizeStrategy = 200;
|
|
|
|
@Input()
|
|
gap = 12;
|
|
|
|
@Input()
|
|
itemCard: TemplateRef<{item: T}>;
|
|
|
|
@Input()
|
|
loadingCell: TemplateRef<any>;
|
|
|
|
@Input()
|
|
dataLoading: TemplateRef<any>;
|
|
|
|
@Input()
|
|
noData: TemplateRef<any>;
|
|
|
|
dataSource: ScrollGridDatasource<T, F>;
|
|
|
|
calculatedItemSize: number;
|
|
minBuffer: number;
|
|
maxBuffer: number;
|
|
|
|
private contentResize$: ResizeObserver;
|
|
|
|
constructor(private breakpointObserver: BreakpointObserver,
|
|
private cd: ChangeDetectorRef,
|
|
private renderer: Renderer2) {
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
if (typeof this.itemSize === 'number') {
|
|
this.calculatedItemSize = this.itemSize;
|
|
} else {
|
|
this.calculatedItemSize = this.itemSize.defaultItemSize;
|
|
}
|
|
this.minBuffer = this.calculatedItemSize;
|
|
this.maxBuffer = this.calculatedItemSize * 2;
|
|
this.dataSource = new ScrollGridDatasource<T, F>(this.breakpointObserver, this.columns, this.fetchFunction, this.filter);
|
|
}
|
|
|
|
ngAfterViewInit() {
|
|
this.renderer.setStyle(this.viewport._contentWrapper.nativeElement, 'gap', this.gap + 'px');
|
|
this.renderer.setStyle(this.viewport._contentWrapper.nativeElement, 'padding', this.gap + 'px');
|
|
if (!(typeof this.itemSize === 'number')) {
|
|
this.contentResize$ = new ResizeObserver(() => {
|
|
this.onContentResize();
|
|
});
|
|
this.contentResize$.observe(this.viewport._contentWrapper.nativeElement);
|
|
}
|
|
}
|
|
|
|
ngOnChanges(changes: SimpleChanges): void {
|
|
for (const propName of Object.keys(changes)) {
|
|
const change = changes[propName];
|
|
if (!change.firstChange && change.currentValue !== change.previousValue && propName === 'filter') {
|
|
this.dataSource.updateFilter(this.filter);
|
|
}
|
|
}
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
if (this.contentResize$) {
|
|
this.contentResize$.disconnect();
|
|
}
|
|
}
|
|
|
|
isObject(value: any): boolean {
|
|
return isObject(value);
|
|
}
|
|
|
|
trackByItemsRow(index: number, itemsRow: T[]): number {
|
|
return index;
|
|
}
|
|
|
|
trackByItem(index: number, item: T): T {
|
|
return item;
|
|
}
|
|
|
|
public update() {
|
|
this.dataSource.update();
|
|
}
|
|
|
|
public updateItem(index: number, item: T) {
|
|
this.dataSource.updateItem(index, item);
|
|
}
|
|
|
|
public deleteItem(index: number) {
|
|
this.dataSource.deleteItem(index);
|
|
}
|
|
|
|
private onContentResize() {
|
|
const contentWidth = this.viewport._contentWrapper.nativeElement.getBoundingClientRect().width;
|
|
const columns = this.dataSource.currentColumns;
|
|
const itemWidth = (contentWidth - this.gap * (columns + 1)) / columns;
|
|
this.calculatedItemSize = (this.itemSize as ItemSizeStrategy).itemSizeFunction(itemWidth);
|
|
this.minBuffer = this.calculatedItemSize;
|
|
this.maxBuffer = this.calculatedItemSize * 2;
|
|
this.cd.markForCheck();
|
|
}
|
|
}
|
|
|