diff --git a/backend/package.json b/backend/package.json index b84bc37..257a3f6 100644 --- a/backend/package.json +++ b/backend/package.json @@ -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", diff --git a/frontend/package.json b/frontend/package.json index b4c742b..5345a34 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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" } } diff --git a/frontend/src/app/components/masonry/masonry-item.directive.ts b/frontend/src/app/components/masonry/masonry-item.directive.ts index c34616c..7902fae 100644 --- a/frontend/src/app/components/masonry/masonry-item.directive.ts +++ b/frontend/src/app/components/masonry/masonry-item.directive.ts @@ -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; + constructor(private template: TemplateRef) {} - constructor( - private element: ElementRef, - @Inject(ResizeObserverService) - resize: Observable, - ) { - this.resizeObserver = resize.pipe(map((entries) => entries[0])); - this.subscribeResize(); + public getTemplate(): TemplateRef { + 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); + } } } diff --git a/frontend/src/app/components/masonry/masonry.component.html b/frontend/src/app/components/masonry/masonry.component.html index d50794e..1cf45ce 100644 --- a/frontend/src/app/components/masonry/masonry.component.html +++ b/frontend/src/app/components/masonry/masonry.component.html @@ -1,6 +1,6 @@ -
+> + + diff --git a/frontend/src/app/components/masonry/masonry.component.ts b/frontend/src/app/components/masonry/masonry.component.ts index abd42d3..eda61cb 100644 --- a/frontend/src/app/components/masonry/masonry.component.ts +++ b/frontend/src/app/components/masonry/masonry.component.ts @@ -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; + private items: QueryList; - @ViewChildren('column') - private columns: QueryList>; + @ViewChildren('column', { read: ViewContainerRef }) + private columns: QueryList; 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) { - 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) { - 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); } } diff --git a/frontend/src/app/components/picsur-img/picsur-img.component.html b/frontend/src/app/components/picsur-img/picsur-img.component.html index 3e771f4..a656c56 100644 --- a/frontend/src/app/components/picsur-img/picsur-img.component.html +++ b/frontend/src/app/components/picsur-img/picsur-img.component.html @@ -5,12 +5,14 @@ width="0" #targetcanvas > + + broken_image

Your Images

- -
- - - {{ image.file_name | truncate }} - - Uploaded {{ image.created | amTimeAgo }} - {{ - image.expires_at === null - ? '' - : '| Expires ' + (image.expires_at | amTimeAgo) - }} - - - - - - - - - -
+ +
+ + + {{ image.file_name | truncate }} + + Uploaded {{ image.created | amTimeAgo }} + {{ + image.expires_at === null + ? '' + : '| Expires ' + (image.expires_at | amTimeAgo) + }} + + + + + + + + + +
+
{ - while (parent.lastChild) { - parent.removeChild(parent.lastChild); - } -}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc59eee..556888a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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==} diff --git a/shared/package.json b/shared/package.json index 3626422..3d1a0f3 100644 --- a/shared/package.json +++ b/shared/package.json @@ -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", diff --git a/support/setversion.sh b/support/setversion.sh index 3a18179..fd3e20d 100755 --- a/support/setversion.sh +++ b/support/setversion.sh @@ -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