mirror of
https://github.com/CaramelFur/Picsur.git
synced 2025-10-25 23:46:06 +02:00
Fix masonry/thumbnail bug?
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
export const RemoveChildren = (parent: HTMLElement) => {
|
||||
while (parent.lastChild) {
|
||||
parent.removeChild(parent.lastChild);
|
||||
}
|
||||
};
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -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==}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user