From c56b0aab8adea20ea6c4a51dd69eb3505257b462 Mon Sep 17 00:00:00 2001 From: chatenium Date: Sun, 12 Apr 2026 17:40:51 +0200 Subject: [PATCH] Started implementing Chatenium Pictures --- package-lock.json | 8 +-- package.json | 2 +- src/app/app.routes.ts | 2 + src/app/chat/chat.html | 8 ++- src/app/chat/chat.ts | 6 +- src/app/chat/dm-list/dm-list.spec.ts | 2 +- src/app/chat/elements/masonry/masonry.scss | 3 +- src/app/chat/picture-list/picture-list.html | 16 +++++ src/app/chat/picture-list/picture-list.scss | 28 ++++++++ .../chat/picture-list/picture-list.spec.ts | 22 ++++++ src/app/chat/picture-list/picture-list.ts | 41 +++++++++++ src/app/chat/picture/see/see.html | 35 ++++++++++ src/app/chat/picture/see/see.scss | 69 +++++++++++++++++++ src/app/chat/picture/see/see.spec.ts | 22 ++++++ src/app/chat/picture/see/see.ts | 66 ++++++++++++++++++ src/app/service-manager.ts | 12 +++- src/app/storage/indexed-db.ts | 3 +- 17 files changed, 332 insertions(+), 13 deletions(-) create mode 100644 src/app/chat/picture-list/picture-list.html create mode 100644 src/app/chat/picture-list/picture-list.scss create mode 100644 src/app/chat/picture-list/picture-list.spec.ts create mode 100644 src/app/chat/picture-list/picture-list.ts create mode 100644 src/app/chat/picture/see/see.html create mode 100644 src/app/chat/picture/see/see.scss create mode 100644 src/app/chat/picture/see/see.spec.ts create mode 100644 src/app/chat/picture/see/see.ts diff --git a/package-lock.json b/package-lock.json index b50f60b..e535e6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@angular/platform-browser": "^21.2.0", "@angular/router": "^21.2.0", "@angular/service-worker": "^21.2.0", - "@chatenium/chatenium-sdk": "^1.1.7", + "@chatenium/chatenium-sdk": "^1.1.8", "@fortawesome/angular-fontawesome": "^4.0.0", "@fortawesome/free-brands-svg-icons": "^7.1.0", "@fortawesome/free-solid-svg-icons": "^7.1.0", @@ -1011,9 +1011,9 @@ } }, "node_modules/@chatenium/chatenium-sdk": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@chatenium/chatenium-sdk/-/chatenium-sdk-1.1.7.tgz", - "integrity": "sha512-8stVVjKwwWZvUfVeWzSK1WGIiSt95Yxr80nfltZSDmqMKQaEA1sL9/2V6pobM4mCs9nzjUQQiniEPVQ0uxPXAg==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@chatenium/chatenium-sdk/-/chatenium-sdk-1.1.8.tgz", + "integrity": "sha512-UuPCr/NqZhLrT/lt8I1AM+UNFAdI00adtvvlu3BQgqCYPASoUpTJJ4HdMzJ+E0cb0pUS6DmN0Kv+w3vFQfRYwg==", "dependencies": { "@faker-js/faker": "^10.4.0", "axios": "^1.14.0", diff --git a/package.json b/package.json index 932c96c..c00ec4a 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@angular/platform-browser": "^21.2.0", "@angular/router": "^21.2.0", "@angular/service-worker": "^21.2.0", - "@chatenium/chatenium-sdk": "^1.1.7", + "@chatenium/chatenium-sdk": "^1.1.8", "@fortawesome/angular-fontawesome": "^4.0.0", "@fortawesome/free-brands-svg-icons": "^7.1.0", "@fortawesome/free-solid-svg-icons": "^7.1.0", diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 4a9813b..4644fca 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -9,6 +9,7 @@ import {Privacy} from './privacy/privacy'; import {TOS} from './tos/tos'; import {Network} from './chat/network/network'; import {Text} from './chat/network/channel/text/text'; +import {See} from './chat/picture/see/see'; export const routes: Routes = [ {path: '', component: Homepage}, @@ -18,6 +19,7 @@ export const routes: Routes = [ { path: 'chat', component: Chat, canActivate: [authNeededGuard], children: [ {path: 'dm/:chatid', component: Dm}, + {path: 'picture/:uploaderId', component: See}, { path: 'network/:networkId', component: Network, children: [ {path: ":categoryId/:channelId", component: Text} diff --git a/src/app/chat/chat.html b/src/app/chat/chat.html index 29065ee..7561cbf 100644 --- a/src/app/chat/chat.html +++ b/src/app/chat/chat.html @@ -27,7 +27,7 @@ - @@ -47,13 +47,17 @@ } + @case (2) { + + } }
- @if (router.url.startsWith("/chat/dm")) { + @if (router.url.startsWith("/chat/dm") && router.url.startsWith("/chat/picture")) { @defer (when serviceManager.chatsStatus() != LoadStatus.loading) { diff --git a/src/app/chat/chat.ts b/src/app/chat/chat.ts index 0e3a89b..5ff8f59 100644 --- a/src/app/chat/chat.ts +++ b/src/app/chat/chat.ts @@ -12,6 +12,7 @@ import {TranslatePipe, TranslateService} from '@ngx-translate/core'; import {environment} from '../../environments/environment'; import {TuiTabBarComponent, TuiTabBarItem} from '@taiga-ui/addon-mobile'; import {NetworkList} from './network-list/network-list'; +import {PictureList} from './picture-list/picture-list'; @Component({ selector: 'app-chat', @@ -29,7 +30,8 @@ import {NetworkList} from './network-list/network-list'; TranslatePipe, TuiTabBarComponent, TuiTabBarItem, - NetworkList + NetworkList, + PictureList ], templateUrl: './chat.html', styleUrl: './chat.scss', @@ -72,6 +74,8 @@ export class Chat implements OnInit { async ngOnInit() { if (this.router.url.startsWith("/chat/network")) { this.navigationActiveIndex = 1 + } else if (this.router.url.startsWith("/chat/picture")) { + this.navigationActiveIndex = 2 } this.indexedDb.openDatabase().then(async () => { diff --git a/src/app/chat/dm-list/dm-list.spec.ts b/src/app/chat/dm-list/dm-list.spec.ts index 17bc90b..5aedba4 100644 --- a/src/app/chat/dm-list/dm-list.spec.ts +++ b/src/app/chat/dm-list/dm-list.spec.ts @@ -2,7 +2,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DmList } from './dm-list'; -describe('DmList', () => { +describe('PictureList', () => { let component: DmList; let fixture: ComponentFixture; diff --git a/src/app/chat/elements/masonry/masonry.scss b/src/app/chat/elements/masonry/masonry.scss index 748c46f..88d7096 100644 --- a/src/app/chat/elements/masonry/masonry.scss +++ b/src/app/chat/elements/masonry/masonry.scss @@ -1,9 +1,8 @@ :host { display: grid; - gap: 4px; height: 100%; - img { + ::ng-deep img { display: block; height: 100%; width: 100%; diff --git a/src/app/chat/picture-list/picture-list.html b/src/app/chat/picture-list/picture-list.html new file mode 100644 index 0000000..fa8619d --- /dev/null +++ b/src/app/chat/picture-list/picture-list.html @@ -0,0 +1,16 @@ + + +@for (chat of serviceManager.chats(); track chat.chatid) { + +} diff --git a/src/app/chat/picture-list/picture-list.scss b/src/app/chat/picture-list/picture-list.scss new file mode 100644 index 0000000..22667f0 --- /dev/null +++ b/src/app/chat/picture-list/picture-list.scss @@ -0,0 +1,28 @@ +:host { + display: flex; + flex-direction: column; + gap: 5px; + + button { + width: 100%; + display: flex; + justify-content: start; + font-weight: 600; + + &.enlarge { + height: 75px; + } + + .info { + display: flex; + flex-direction: column; + text-align: start; + + .latest_message { + margin-top: -5px; + font-size: 12px; + opacity: 50%; + } + } + } +} diff --git a/src/app/chat/picture-list/picture-list.spec.ts b/src/app/chat/picture-list/picture-list.spec.ts new file mode 100644 index 0000000..7ef697e --- /dev/null +++ b/src/app/chat/picture-list/picture-list.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PictureList } from './picture-list'; + +describe('PictureList', () => { + let component: PictureList; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PictureList], + }).compileComponents(); + + fixture = TestBed.createComponent(PictureList); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/chat/picture-list/picture-list.ts b/src/app/chat/picture-list/picture-list.ts new file mode 100644 index 0000000..871f246 --- /dev/null +++ b/src/app/chat/picture-list/picture-list.ts @@ -0,0 +1,41 @@ +import {Component, inject, input, OnInit, signal} from '@angular/core'; +import {ChatService} from '@chatenium/chatenium-sdk/services/chatService'; +import {IndexedDB} from '../../storage/indexed-db'; +import {Chat} from '@chatenium/chatenium-sdk/domain/chatService.schema'; +import {TUI_BREAKPOINT, TuiButton} from '@taiga-ui/core'; +import {Oimg} from '../elements/oimg/oimg'; +import {Router, RouterLink} from '@angular/router'; +import {TranslatePipe} from '@ngx-translate/core'; +import {LoadStatus, ServiceManager} from '../../service-manager'; + +@Component({ + selector: 'app-picture-list', + imports: [ + TuiButton, + Oimg, + RouterLink, + TranslatePipe + ], + templateUrl: './picture-list.html', + styleUrl: './picture-list.scss', +}) +export class PictureList implements OnInit { + userid = input("") + token = input("") + + indexedDb = inject(IndexedDB) + router = inject(Router) + serviceManager = inject(ServiceManager) + breakpoint = inject(TUI_BREAKPOINT) + + async ngOnInit() { + this.serviceManager.chatService = new ChatService(this.userid(), this.token(), this.indexedDb.getApi(), () => {}) + try { + this.serviceManager.chats.set(await this.serviceManager.chatService.get()) + this.serviceManager.chatsStatus.set(LoadStatus.loaded) + } catch (e) { + console.error(e) + this.serviceManager.chatsStatus.set(LoadStatus.error) + } + } +} diff --git a/src/app/chat/picture/see/see.html b/src/app/chat/picture/see/see.html new file mode 100644 index 0000000..14346a4 --- /dev/null +++ b/src/app/chat/picture/see/see.html @@ -0,0 +1,35 @@ +
+ @defer (when store) { + +
+ +
+ @if (store.uploaderData().displayName == "") { + {{'@'+store.uploaderData().username}} + } @else { + {{store.uploaderData().displayName}} + {{'@'+store.uploaderData().username}} + } +
+
+ +
+
+
+ +
+ @for (album of store.albums(); track album) { +
+ + @for (file of album.images; track file) { + + } + +
+

{{album.name}}

+
+
+ } +
+ } +
diff --git a/src/app/chat/picture/see/see.scss b/src/app/chat/picture/see/see.scss new file mode 100644 index 0000000..2fe083d --- /dev/null +++ b/src/app/chat/picture/see/see.scss @@ -0,0 +1,69 @@ +main { + height: 98svh; + display: grid; + grid-template-rows: 70px minmax(0, 1fr) auto; + padding: 15px; + + navbar { + .uploader-data { + display: flex; + flex-direction: column; + + .main-name { + font-size: 18px; + font-weight: bold; + } + + .alt-name { + margin-top: -5px; + color: gray; + font-size: 12px; + } + } + + .items-right { + margin-top: -10px; + + button { + width: 35px; + height: 35px; + } + } + } + + main { + display: flex; + gap: 10px; + flex-wrap: wrap; + align-content: flex-start; + + .album { + width: 300px; + height: 300px; + background: var(--tui-background-base-alt); + border: 2px solid var(--tui-border-normal); + border-radius: 30px; + overflow: hidden; + position: relative; + + .album-name { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + background: color-mix(in srgb, var(--tui-background-base) 50%, transparent); + padding: 10px; + font-size: 18px; + font-weight: bold; + display: flex; + justify-content: center; + align-items: center; + + h2 { + padding: 0; + margin: 0; + } + } + } + } +} diff --git a/src/app/chat/picture/see/see.spec.ts b/src/app/chat/picture/see/see.spec.ts new file mode 100644 index 0000000..a5c18a1 --- /dev/null +++ b/src/app/chat/picture/see/see.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { See } from './see'; + +describe('See', () => { + let component: See; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [See], + }).compileComponents(); + + fixture = TestBed.createComponent(See); + component = fixture.componentInstance; + await fixture.whenStable(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/chat/picture/see/see.ts b/src/app/chat/picture/see/see.ts new file mode 100644 index 0000000..2dd663c --- /dev/null +++ b/src/app/chat/picture/see/see.ts @@ -0,0 +1,66 @@ +import {Component, inject, signal} from '@angular/core'; +import {DmStorage, PictureStorage, ServiceManager} from '../../../service-manager'; +import {ActivatedRoute} from '@angular/router'; +import {IndexedDB} from '../../../storage/indexed-db'; +import {TUI_BREAKPOINT, TuiButton, TuiIcon} from '@taiga-ui/core'; +import {PictureService} from '@chatenium/chatenium-sdk/services/pictureService'; +import {Navbar} from '../../elements/navbar/navbar'; +import {Oimg} from '../../elements/oimg/oimg'; +import {Masonry} from '../../elements/masonry/masonry'; + +@Component({ + selector: 'app-see', + imports: [ + Navbar, + Oimg, + TuiButton, + TuiIcon, + Masonry + ], + templateUrl: './see.html', + styleUrl: './see.scss', +}) +export class See { + serviceManager = inject(ServiceManager) + route = inject(ActivatedRoute) + indexedDb = inject(IndexedDB) + breakpoint = inject(TUI_BREAKPOINT) + + uploaderId = "" + + get store(): PictureStorage { + return this.serviceManager.pictureServices()[this.uploaderId] + } + + ngOnInit() { + this.route.params.subscribe(async params => { + const uploaderId = params['uploaderId']; + this.uploaderId = uploaderId; + + const session = this.serviceManager.currentSession(); + if (!session) { + return + } + + if (!this.serviceManager.pictureServices()[uploaderId]) { + const newService = new PictureService( + session.token, + uploaderId, + session.userData.userid, + this.indexedDb.getApi(), + ); + + const uploaderInfo = await newService.get() + + this.serviceManager.pictureServices.update(services => ({ + ...services, + [uploaderId]: { + albums: signal(uploaderInfo.pictures), + uploaderData: signal(uploaderInfo.userData), + service: newService, + } as PictureStorage + })); + } + }); + } +} diff --git a/src/app/service-manager.ts b/src/app/service-manager.ts index 95bb32b..8956f65 100644 --- a/src/app/service-manager.ts +++ b/src/app/service-manager.ts @@ -13,6 +13,9 @@ import {NetworkService} from '@chatenium/chatenium-sdk/services/networkService'; import {Network, NetworkCategory, NetworkChannel} from '@chatenium/chatenium-sdk/domain/networkService.schema'; import {TextChannelServiceService} from '@chatenium/chatenium-sdk/services/textChannelService'; import {Message as NetworkMessage} from '@chatenium/chatenium-sdk/domain/textChannelService.schema'; +import {PictureService} from '@chatenium/chatenium-sdk/services/pictureService'; +import {Album} from '@chatenium/chatenium-sdk/domain/pictureService.schema'; +import {PublicUserData} from '@chatenium/chatenium-sdk/domain/common.schema'; @Injectable({ providedIn: 'root', @@ -25,7 +28,7 @@ export class ServiceManager { sessionManager = new SessionManager(this.database.getApi(), this.keyring.getApi(), this.keyValue.getApi()) currentSession = signal(null) - chatService: ChatService | null = null // Initialized in dm-list.ts + chatService: ChatService | null = null // Initialized in picture-list.ts chatsStatus = signal(LoadStatus.loading) chats = signal([]) @@ -36,6 +39,7 @@ export class ServiceManager { networkServices = signal>({}) dmServices = signal>({}) + pictureServices = signal>({}) } export enum LoadStatus { @@ -53,6 +57,12 @@ export interface DmStorage { wsListener: (action: string, message: string) => void } +export interface PictureStorage { + service: PictureService + albums: WritableSignal + uploaderData: WritableSignal +} + export interface NetworkStorage { service: NetworkService networkData: WritableSignal diff --git a/src/app/storage/indexed-db.ts b/src/app/storage/indexed-db.ts index adc0e55..1e6f9de 100644 --- a/src/app/storage/indexed-db.ts +++ b/src/app/storage/indexed-db.ts @@ -6,7 +6,7 @@ import {DatabaseAPI} from '@chatenium/chatenium-sdk/storage/database'; providedIn: 'root' }) export class IndexedDB { - private dbVersion = 2 + private dbVersion = 3 private db: IDBDatabase | null = null getApi(): DatabaseAPI { @@ -36,6 +36,7 @@ export class IndexedDB { db.createObjectStore('files', { keyPath: 'id' }) db.createObjectStore('messages', { keyPath: 'id' }) db.createObjectStore('networkmessages', { keyPath: 'id' }) + db.createObjectStore('pictures', { keyPath: 'id' }) } request.onsuccess = (event: Event) => {