Fix masonry/thumbnail bug?

This commit is contained in:
Caramel
2024-11-01 15:50:02 +01:00
parent 0c063e444e
commit d24e4a7723
11 changed files with 121 additions and 104 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "picsur-backend",
"version": "0.5.3",
"version": "0.5.5",
"description": "Backend for Picsur",
"license": "GPL-3.0",
"repository": "https://github.com/caramelfur/Picsur",

View File

@@ -1,6 +1,6 @@
{
"name": "picsur-frontend",
"version": "0.5.3",
"version": "0.5.5",
"description": "Frontend for Picsur",
"license": "GPL-3.0",
"repository": "https://github.com/caramelfur/Picsur",
@@ -57,10 +57,8 @@
"typescript": "~5.5.4",
"webpack": "^5.95.0",
"webpack-bundle-analyzer": "^4.10.2",
"@ngx-dropzone/cdk": "^18.1.1",
"zod": "^3.23.8",
"zone.js": "~0.14.10"
},
"dependencies": {
"@ngx-dropzone/cdk": "^18.1.1"
}
}

View File

@@ -1,52 +1,53 @@
import { Directive, ElementRef, Inject } from '@angular/core';
import {
ResizeObserverService,
WA_RESIZE_OPTION_BOX
} from '@ng-web-apis/resize-observer';
import { AutoUnsubscribe } from 'ngx-auto-unsubscribe-decorator';
import { Observable, map } from 'rxjs';
import { Directive, TemplateRef, ViewRef } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Directive({
selector: '[masonry-item]',
providers: [
ResizeObserverService,
{
provide: WA_RESIZE_OPTION_BOX,
deps: [ElementRef],
useValue: "border-box",
},
],
selector: 'ng-template[masonry-item]',
})
export class MasonryItemDirective {
private lastEntry: ResizeObserverEntry | null = null;
private viewRef: ViewRef | null = null;
private resizeObserver: ResizeObserver | null = null;
private sizeSubject: BehaviorSubject<{ width: number; height: number }> =
new BehaviorSubject({ width: 0, height: 0 });
private resizeObserver: Observable<ResizeObserverEntry>;
constructor(private template: TemplateRef<HTMLElement>) {}
constructor(
private element: ElementRef<HTMLElement>,
@Inject(ResizeObserverService)
resize: Observable<ResizeObserverEntry[]>,
) {
this.resizeObserver = resize.pipe(map((entries) => entries[0]));
this.subscribeResize();
public getTemplate(): TemplateRef<HTMLElement> {
return this.template;
}
@AutoUnsubscribe()
private subscribeResize() {
return this.resizeObserver.subscribe((value) => {
this.lastEntry = value;
});
public getViewRef(): ViewRef {
if (!this.viewRef || this.viewRef.destroyed) {
this.viewRef = this.template.createEmbeddedView(null as any);
this.resubscribeResizeObserver();
}
return this.viewRef;
}
public getElement() {
return this.element.nativeElement;
public getElement(): HTMLElement | null {
const anyRef = this.getViewRef() as any;
return anyRef.rootNodes.length > 0 ? anyRef.rootNodes[0] : null;
}
public getSize() {
return this.resizeObserver;
public getSizeObservable() {
return this.sizeSubject.asObservable();
}
public getLastSize() {
return this.lastEntry;
public getCurrentSize() {
return this.sizeSubject.value;
}
public resubscribeResizeObserver() {
if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
const element = this.getElement();
if (element) {
this.resizeObserver = new ResizeObserver((items) => {
const { width, height } = items[0].contentRect;
this.sizeSubject.next({ width, height });
});
this.resizeObserver.observe(element);
}
}
}

View File

@@ -1,6 +1,6 @@
<!-- <ng-content></ng-content> -->
<div
#column
class="column"
*ngFor="let item of [].constructor(_column_count); let i = index"
></div>
>
<ng-container #column></ng-container>
</div>

View File

@@ -4,17 +4,16 @@ import {
ChangeDetectorRef,
Component,
ContentChildren,
ElementRef,
Input,
OnDestroy,
QueryList,
ViewChildren,
ViewContainerRef
} 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';
import { MasonryItemDirective } from './masonry-item.directive';
@Component({
selector: 'masonry',
@@ -27,31 +26,43 @@ export class MasonryComponent implements AfterViewInit, OnDestroy {
@Input('columns') public set column_count(value: number) {
this._column_count = value;
this.cleanAllColumns();
this.changeDetector.markForCheck();
}
public _column_count = 1;
@Input('update-speed') update_speed = 200;
@ContentChildren(MasonryItemDirective)
private content: QueryList<MasonryItemDirective>;
private items: QueryList<MasonryItemDirective>;
@ViewChildren('column')
private columns: QueryList<ElementRef<HTMLDivElement>>;
@ViewChildren('column', { read: ViewContainerRef })
private columns: QueryList<ViewContainerRef>;
private sizesSubscription: Subscription | null = null;
ngAfterViewInit(): void {
this.subscribeContent();
this.subscribeColumns();
}
@AutoUnsubscribe()
private subscribeColumns() {
return this.columns.changes.subscribe(this.handleColumnsChange.bind(this));
}
private handleColumnsChange() {
this.resortItems();
this.changeDetector.markForCheck();
}
@AutoUnsubscribe()
private subscribeContent() {
this.handleContentChange(this.content);
return this.content.changes.subscribe(this.handleContentChange.bind(this));
this.handleContentChange();
return this.items.changes.subscribe(this.handleContentChange.bind(this));
}
private handleContentChange(items: QueryList<MasonryItemDirective>) {
const sizes = items.map((i) => i.getSize());
private handleContentChange() {
const sizes = this.items.map((i) => i.getSizeObservable());
if (this.sizesSubscription) {
this.sizesSubscription.unsubscribe();
@@ -60,22 +71,20 @@ export class MasonryComponent implements AfterViewInit, OnDestroy {
this.sizesSubscription = combineLatest(sizes)
.pipe(Throttle(this.update_speed))
.subscribe(() => {
this.resortItems(items);
this.resortItems();
});
this.resortItems(items);
this.resortItems();
this.changeDetector.markForCheck();
}
private resortItems(items: QueryList<MasonryItemDirective>) {
const itemsArray = items.toArray();
const columnsArray = this.columns.map((c) => c.nativeElement);
private resortItems() {
const itemsArray = this.items.toArray();
for (let i = 0; i < columnsArray.length; i++) {
RemoveChildren(columnsArray[i]);
}
this.cleanAllColumns();
const columnsArray = this.columns.map((c) => c);
const columnSizes = columnsArray.map(() => 0);
for (let i = 0; i < itemsArray.length; i++) {
@@ -92,9 +101,21 @@ export class MasonryComponent implements AfterViewInit, OnDestroy {
}
}
columnsArray[smallestColumn].appendChild(item.getElement());
columnsArray[smallestColumn].insert(item.getViewRef())
columnSizes[smallestColumn] +=
item.getLastSize()?.contentRect.height ?? 0;
item.getCurrentSize()?.height ?? 0;
}
}
private cleanAllColumns() {
this.columns?.forEach((column) => {
this.removeChildren(column);
});
}
private removeChildren(parent: ViewContainerRef) {
while (parent.length) {
parent.detach(0);
}
}

View File

@@ -5,12 +5,14 @@
width="0"
#targetcanvas
></canvas>
<img
*ngIf="state === 'image' || state === 'loading'"
[style.display]="state === 'loading' ? 'none' : 'block'"
loading="lazy"
#targetimg
/>
<mat-icon *ngIf="state === 'error'">broken_image</mat-icon>
<mat-spinner

View File

@@ -6,35 +6,36 @@
<ng-container *ngIf="images !== null && images.length > 0">
<h1>Your Images</h1>
<masonry [columns]="columns">
<div *ngFor="let image of images" class="m-2" masonry-item>
<mat-card>
<mat-card-header>
<mat-card-title>{{ image.file_name | truncate }}</mat-card-title>
<mat-card-subtitle>
Uploaded {{ image.created | amTimeAgo }}
{{
image.expires_at === null
? ''
: '| Expires ' + (image.expires_at | amTimeAgo)
}}
</mat-card-subtitle>
</mat-card-header>
<picsur-img
mat-card-image
[src]="getThumbnailUrl(image)"
alt="Image uploaded by you"
>
</picsur-img>
<mat-card-actions>
<button mat-stroked-button (click)="viewImage(image)">VIEW</button>
<button mat-button color="warn" (click)="deleteImage(image)">
DELETE
</button>
</mat-card-actions>
</mat-card>
</div>
<ng-template *ngFor="let image of images" masonry-item>
<div class="m-2">
<mat-card>
<mat-card-header>
<mat-card-title>{{ image.file_name | truncate }}</mat-card-title>
<mat-card-subtitle>
Uploaded {{ image.created | amTimeAgo }}
{{
image.expires_at === null
? ''
: '| Expires ' + (image.expires_at | amTimeAgo)
}}
</mat-card-subtitle>
</mat-card-header>
<picsur-img
mat-card-image
[src]="getThumbnailUrl(image)"
alt="Image uploaded by you"
>
</picsur-img>
<mat-card-actions>
<button mat-stroked-button (click)="viewImage(image)">VIEW</button>
<button mat-button color="warn" (click)="deleteImage(image)">
DELETE
</button>
</mat-card-actions>
</mat-card>
</div>
</ng-template>
</masonry>
<paginator

View File

@@ -1,5 +0,0 @@
export const RemoveChildren = (parent: HTMLElement) => {
while (parent.lastChild) {
parent.removeChild(parent.lastChild);
}
};

9
pnpm-lock.yaml generated
View File

@@ -212,10 +212,6 @@ importers:
version: 5.5.4
frontend:
dependencies:
'@ngx-dropzone/cdk':
specifier: ^18.1.1
version: 18.1.1(@angular/common@18.2.10(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10))(@angular/forms@18.2.10(@angular/common@18.2.10(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.10(@angular/animations@18.2.10(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@18.2.10(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10)))(rxjs@7.8.1))(rxjs@7.8.1)
devDependencies:
'@angular-builders/custom-webpack':
specifier: ^18.0.0
@@ -274,6 +270,9 @@ importers:
'@ngui/common':
specifier: ^1.0.0
version: 1.0.0(@angular/common@18.2.10(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10))
'@ngx-dropzone/cdk':
specifier: ^18.1.1
version: 18.1.1(@angular/common@18.2.10(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10))(@angular/forms@18.2.10(@angular/common@18.2.10(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@18.2.10(@angular/animations@18.2.10(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@18.2.10(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@18.2.10(rxjs@7.8.1)(zone.js@0.14.10)))(rxjs@7.8.1))(rxjs@7.8.1)
'@popperjs/core':
specifier: ^2.11.8
version: 2.11.8
@@ -2046,7 +2045,7 @@ packages:
peerDependencies:
'@angular/compiler-cli': ^18.0.0
typescript: ~5.5.4
webpack: '>=5.76.0'
webpack: ^5.54.0
'@ngui/common@1.0.0':
resolution: {integrity: sha512-T0vX6jFLR+19iUVqM0J6lQkcDo6Iaq8pptzMJDEjLG3HkpgeM9SYxiTFV3+yHuP4QzQQ6/VP8gJ+1f4M7iZv5Q==}

View File

@@ -1,6 +1,6 @@
{
"name": "picsur-shared",
"version": "0.5.3",
"version": "0.5.5",
"description": "Shared libraries for Picsur",
"license": "GPL-3.0",
"repository": "https://github.com/caramelfur/Picsur",

View File

@@ -11,7 +11,7 @@ fi
SCRIPT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
UPDATE_VERSION="yarn version"
UPDATE_VERSION="pnpm version --f"
cd $SCRIPT_PATH/..
$UPDATE_VERSION $VERSION