107 lines
2.9 KiB
TypeScript
107 lines
2.9 KiB
TypeScript
import {
|
|
AfterViewInit,
|
|
ChangeDetectionStrategy,
|
|
ChangeDetectorRef,
|
|
Component,
|
|
ContentChildren,
|
|
ElementRef,
|
|
Input,
|
|
OnDestroy,
|
|
QueryList,
|
|
ViewChildren,
|
|
} from '@angular/core';
|
|
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
|
|
import { combineLatest, Subscription } from 'rxjs';
|
|
import { MasonryItemDirective } from './masonry-item.directive';
|
|
import { RemoveChildren } from '../../util/remove-children';
|
|
import { Throttle } from '../../util/throttle';
|
|
|
|
@Component({
|
|
selector: 'masonry',
|
|
templateUrl: './masonry.component.html',
|
|
styleUrls: ['./masonry.component.scss'],
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
})
|
|
export class MasonryComponent implements AfterViewInit, OnDestroy {
|
|
constructor(private readonly changeDetector: ChangeDetectorRef) {}
|
|
|
|
@Input('columns') public set column_count(value: number) {
|
|
this._column_count = value;
|
|
this.changeDetector.markForCheck();
|
|
}
|
|
public _column_count = 1;
|
|
@Input('update-speed') update_speed = 200;
|
|
|
|
@ContentChildren(MasonryItemDirective)
|
|
private content: QueryList<MasonryItemDirective>;
|
|
|
|
@ViewChildren('column')
|
|
private columns: QueryList<ElementRef<HTMLDivElement>>;
|
|
|
|
private sizesSubscription: Subscription | null = null;
|
|
|
|
ngAfterViewInit(): void {
|
|
this.subscribeContent();
|
|
}
|
|
|
|
@AutoUnsubscribe()
|
|
private subscribeContent() {
|
|
this.handleContentChange(this.content);
|
|
return this.content.changes.subscribe(this.handleContentChange.bind(this));
|
|
}
|
|
|
|
private handleContentChange(items: QueryList<MasonryItemDirective>) {
|
|
const sizes = items.map((i) => i.getSize());
|
|
|
|
if (this.sizesSubscription) {
|
|
this.sizesSubscription.unsubscribe();
|
|
}
|
|
|
|
this.sizesSubscription = combineLatest(sizes)
|
|
.pipe(Throttle(this.update_speed))
|
|
.subscribe(() => {
|
|
this.resortItems(items);
|
|
});
|
|
|
|
this.resortItems(items);
|
|
|
|
this.changeDetector.markForCheck();
|
|
}
|
|
|
|
private resortItems(items: QueryList<MasonryItemDirective>) {
|
|
const itemsArray = items.toArray();
|
|
const columnsArray = this.columns.map((c) => c.nativeElement);
|
|
|
|
for (let i = 0; i < columnsArray.length; i++) {
|
|
RemoveChildren(columnsArray[i]);
|
|
}
|
|
|
|
const columnSizes = columnsArray.map(() => 0);
|
|
|
|
for (let i = 0; i < itemsArray.length; i++) {
|
|
const item = itemsArray[i];
|
|
|
|
let smallestColumn = 0;
|
|
let smallestColumnSize = columnSizes[0];
|
|
for (let j = columnSizes.length - 1; j >= 0; j--) {
|
|
const better_j = (j + i) % columnSizes.length;
|
|
|
|
if (columnSizes[better_j] <= smallestColumnSize) {
|
|
smallestColumn = better_j;
|
|
smallestColumnSize = columnSizes[better_j];
|
|
}
|
|
}
|
|
|
|
columnsArray[smallestColumn].appendChild(item.getElement());
|
|
columnSizes[smallestColumn] +=
|
|
item.getLastSize()?.contentRect.height ?? 0;
|
|
}
|
|
}
|
|
|
|
ngOnDestroy() {
|
|
if (this.sizesSubscription) {
|
|
this.sizesSubscription.unsubscribe();
|
|
}
|
|
}
|
|
}
|