Start implementing direct messaging and continued developing the chatnav

This commit is contained in:
2026-04-09 08:47:56 +02:00
parent 3fb1145c6b
commit c5bc817efe
27 changed files with 428 additions and 27 deletions

BIN
public/Onest-SemiBold.ttf Normal file

Binary file not shown.

View File

@@ -16,5 +16,17 @@
"passwordRequired": "Password is required", "passwordRequired": "Password is required",
"passwordIncorrect": "Password is incorrect" "passwordIncorrect": "Password is incorrect"
} }
},
"chat": {
"chatnav": {
"dmList": {
"newChat": "Start new chat",
"messageBox": {
"latestMessage": {
"you": "You: "
}
}
}
}
} }
} }

View File

@@ -1,8 +1,13 @@
import { Routes } from '@angular/router'; import {Routes} from '@angular/router';
import {SignIn} from './signin/signin'; import {SignIn} from './signin/signin';
import {Chat} from './chat/chat'; import {Chat} from './chat/chat';
import {Dm} from './chat/dm/dm';
export const routes: Routes = [ export const routes: Routes = [
{ path: 'signin', component: SignIn }, {path: 'signin', component: SignIn},
{ path: 'chat', component: Chat }, {
path: 'chat', component: Chat, children: [
{path: 'dm/:chatid', component: Dm},
]
},
]; ];

View File

@@ -3,7 +3,7 @@
<tui-loader size="xl"/> <tui-loader size="xl"/>
</main> </main>
} @else { } @else {
<main id="layout"> <main id="layout" tuiGroup [collapsed]="true">
<aside id="chatnav"> <aside id="chatnav">
<aside> <aside>
<tui-segmented id="mode_switcher"> <tui-segmented id="mode_switcher">
@@ -30,8 +30,9 @@
<main id="content"> <main id="content">
<div id="content_tint"> <div id="content_tint">
{{serviceManager.currentSession()|json}} @defer (when serviceManager.chatsStatus() != LoadStatus.loading) {
<router-outlet/> <router-outlet/>
}
</div> </div>
</main> </main>
</main> </main>

View File

@@ -1,11 +1,11 @@
#layout { #layout {
display: grid; display: grid;
grid-template-columns: 20% 80%; grid-template-columns: 350px minmax(0, 1fr);
height: 100svh; height: 100svh;
#chatnav { #chatnav {
display: grid; display: grid;
grid-template-columns: 25% 75%; grid-template-columns: 70px minmax(0, 1fr);
aside { aside {
padding: 15px; padding: 15px;
@@ -36,18 +36,22 @@
} }
main { main {
padding: 25px; padding-top: 65px;
background: var(--tui-background-base-alt);
border-radius: 20px 0 0 20px;
margin: 10px 0 10px 10px;
padding: 15px;
} }
} }
#content { #content {
width: 100%; width: 100%;
padding: 10px; padding: 10px 10px 10px 0;
#content_tint { #content_tint {
border-radius: 0 20px 20px 0;
height: 100%; height: 100%;
border-radius: 20px; background: var(--tui-background-neutral-2);
background: var(--tui-background-base-alt);
padding: 15px; padding: 15px;
} }
} }

View File

@@ -1,9 +1,9 @@
import {Component, inject, OnInit} from '@angular/core'; import {Component, inject, OnInit} from '@angular/core';
import {RouterOutlet} from '@angular/router'; import {RouterOutlet} from '@angular/router';
import {TuiSegmented} from '@taiga-ui/kit'; import {TuiSegmented} from '@taiga-ui/kit';
import {TuiButton, TuiIcon, TuiLoader} from '@taiga-ui/core'; import {TuiAppearance, TuiButton, TuiGroup, TuiIcon, TuiLoader} from '@taiga-ui/core';
import {SessionManager} from '@chatenium/chatenium-sdk/services/sessionManager'; import {SessionManager} from '@chatenium/chatenium-sdk/services/sessionManager';
import {ServiceManager} from '../service-manager'; import {LoadStatus, ServiceManager} from '../service-manager';
import {IndexedDB} from '../storage/indexed-db'; import {IndexedDB} from '../storage/indexed-db';
import {DmList} from './dm-list/dm-list'; import {DmList} from './dm-list/dm-list';
import {JsonPipe} from '@angular/common'; import {JsonPipe} from '@angular/common';
@@ -17,7 +17,9 @@ import {JsonPipe} from '@angular/common';
TuiButton, TuiButton,
TuiLoader, TuiLoader,
DmList, DmList,
JsonPipe JsonPipe,
TuiAppearance,
TuiGroup
], ],
templateUrl: './chat.html', templateUrl: './chat.html',
styleUrl: './chat.scss', styleUrl: './chat.scss',
@@ -31,4 +33,6 @@ export class Chat implements OnInit {
this.serviceManager.currentSession.set(await this.serviceManager.sessionManager.loadPreferredSession()) this.serviceManager.currentSession.set(await this.serviceManager.sessionManager.loadPreferredSession())
}) })
} }
protected readonly LoadStatus = LoadStatus;
} }

View File

@@ -1,3 +1,24 @@
@for (chat of chats(); track chat.chatid) { <button tuiButton appearance="secondary" iconStart="@tui.mail-plus">
{{chat.chatid}} {{"chat.chatnav.dmList.newChat"|translate}}
</button>
@for (chat of serviceManager.chats(); track chat.chatid) {
<button tuiButton [appearance]="router.url == '/chat/dm/' + chat.chatid ? 'primary' : 'flat'" [routerLink]="'/chat/dm/' + chat.chatid">
<oimg [src]="chat.pfp" height="35px" width="35px" [radius]="10"></oimg>
<div class="info">
@if (chat.displayName == "") {
<span>{{'@'+chat.username}}</span>
} @else {
<span>{{chat.displayName}}</span>
}
@if (chat.latestMessage) {
<span class="latest_message">
@if (chat.latestMessage.isAuthor) {
{{"chat.chatnav.dmList.messageBox.latestMessage.you"|translate}}
}
{{chat.latestMessage.message}}
</span>
}
</div>
</button>
} }

View File

@@ -0,0 +1,24 @@
:host {
display: flex;
flex-direction: column;
gap: 5px;
button {
width: 100%;
display: flex;
justify-content: start;
font-weight: 600;
.info {
display: flex;
flex-direction: column;
text-align: start;
.latest_message {
margin-top: -5px;
font-size: 12px;
opacity: 50%;
}
}
}
}

View File

@@ -2,12 +2,19 @@ import {Component, inject, input, OnInit, signal} from '@angular/core';
import {ChatService} from '@chatenium/chatenium-sdk/services/chatService'; import {ChatService} from '@chatenium/chatenium-sdk/services/chatService';
import {IndexedDB} from '../../storage/indexed-db'; import {IndexedDB} from '../../storage/indexed-db';
import {Chat} from '@chatenium/chatenium-sdk/domain/chatService.schema'; import {Chat} from '@chatenium/chatenium-sdk/domain/chatService.schema';
import {JsonPipe} from '@angular/common'; import {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({ @Component({
selector: 'app-dm-list', selector: 'app-dm-list',
imports: [ imports: [
JsonPipe TuiButton,
Oimg,
RouterLink,
TranslatePipe
], ],
templateUrl: './dm-list.html', templateUrl: './dm-list.html',
styleUrl: './dm-list.scss', styleUrl: './dm-list.scss',
@@ -17,19 +24,17 @@ export class DmList implements OnInit {
token = input<string>("") token = input<string>("")
indexedDb = inject(IndexedDB) indexedDb = inject(IndexedDB)
router = inject(Router)
service: ChatService | null = null serviceManager = inject(ServiceManager)
chats = signal<Chat[]>([])
chatsStatus = 0
async ngOnInit() { async ngOnInit() {
this.service = new ChatService(this.userid(), this.token(), this.indexedDb.getApi(), () => {}) this.serviceManager.chatService = new ChatService(this.userid(), this.token(), this.indexedDb.getApi(), () => {})
try { try {
this.chats.set(await this.service.get()) this.serviceManager.chats.set(await this.serviceManager.chatService.get())
this.chatsStatus = 1 this.serviceManager.chatsStatus.set(LoadStatus.loaded)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
this.chatsStatus = 2 this.serviceManager.chatsStatus.set(LoadStatus.error)
} }
} }
} }

27
src/app/chat/dm/dm.html Normal file
View File

@@ -0,0 +1,27 @@
@defer (when store) {
<navbar>
<div class="items-left">
<oimg [src]="store.chatData.pfp" height="50px" width="50px" [radius]="15"></oimg>
<div class="chat-data">
@if (store.chatData.displayName == "") {
<span class="main-name">{{'@'+store.chatData.username}}</span>
} @else {
<span class="main-name">{{store.chatData.displayName}}</span>
<span class="alt-name">{{'@'+store.chatData.username}}</span>
}
</div>
</div>
<div class="items-right">
<button tuiButton appearance="flat">
<tui-icon icon="@tui.phone"/>
</button>
</div>
</navbar>
<main>
</main>
<message-box/>
}

33
src/app/chat/dm/dm.scss Normal file
View File

@@ -0,0 +1,33 @@
:host {
height: 100%;
display: grid;
grid-template-rows: 70px minmax(0, 1fr) auto;
navbar {
.chat-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;
}
}
}
}

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Dm } from './dm';
describe('Dm', () => {
let component: Dm;
let fixture: ComponentFixture<Dm>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Dm],
}).compileComponents();
fixture = TestBed.createComponent(Dm);
component = fixture.componentInstance;
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

56
src/app/chat/dm/dm.ts Normal file
View File

@@ -0,0 +1,56 @@
import {Component, inject, OnInit} from '@angular/core';
import {DmStorage, ServiceManager} from '../../service-manager';
import {ActivatedRoute} from '@angular/router';
import {DMService} from '@chatenium/chatenium-sdk/services/dmService';
import {IndexedDB} from '../../storage/indexed-db';
import {Navbar} from '../elements/navbar/navbar';
import {Oimg} from '../elements/oimg/oimg';
import {TuiButton, TuiIcon} from '@taiga-ui/core';
import {MessageBox} from '../elements/message-box/message-box';
@Component({
selector: 'app-dm',
imports: [
Navbar,
Oimg,
TuiButton,
TuiIcon,
MessageBox
],
templateUrl: './dm.html',
styleUrl: './dm.scss',
})
export class Dm implements OnInit {
serviceManager = inject(ServiceManager)
route = inject(ActivatedRoute)
indexedDb = inject(IndexedDB)
chatid = ""
get store(): DmStorage {
return this.serviceManager.dmServices()[this.chatid]
}
ngOnInit() {
this.route.params.subscribe(params => {
const chatid = params['chatid']
this.chatid = chatid
console.log(this.serviceManager.chats())
const session = this.serviceManager.currentSession();
const chatData = this.serviceManager.chats().find(chat => chat.chatid == chatid)
if (!this.serviceManager.dmServices()[chatid] && session != null && chatData != null) {
this.serviceManager.dmServices()[chatid] = {
service: new DMService(
session.userData.userid,
session.token,
chatid,
this.indexedDb.getApi(),
() => {}
),
chatData: chatData
}
}
})
}
}

View File

@@ -0,0 +1,7 @@
<div id="message-box">
<div></div>
<div class="items-middle">
<textarea></textarea>
</div>
<div></div>
</div>

View File

@@ -0,0 +1,29 @@
:host {
width: 100%;
display: flex;
justify-content: center;
#message-box {
width: 60%;
background: var(--tui-background-base-alt);
height: 75px;
border-radius: 200px;
border: 2px solid var(--tui-border-normal);
display: grid;
grid-template-columns: 10% 80% 10%;
align-items: center;
padding: 0 10px;
.items-middle {
textarea {
width: 100%;
height: 100%;
background: transparent;
border: none;
outline: none;
resize: none;
color: var(--tui-text-01);
}
}
}
}

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MessageBox } from './message-box';
describe('MessageBox', () => {
let component: MessageBox;
let fixture: ComponentFixture<MessageBox>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MessageBox],
}).compileComponents();
fixture = TestBed.createComponent(MessageBox);
component = fixture.componentInstance;
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,12 @@
import { Component } from '@angular/core';
import {TuiAppearance} from '@taiga-ui/core';
@Component({
selector: 'message-box',
imports: [
TuiAppearance
],
templateUrl: './message-box.html',
styleUrl: './message-box.scss',
})
export class MessageBox {}

View File

@@ -0,0 +1,3 @@
<nav>
<ng-content></ng-content>
</nav>

View File

@@ -0,0 +1,15 @@
nav {
display: grid;
grid-template-columns: 1fr 1fr;
::ng-deep .items-right, ::ng-deep .items-left {
display: flex;
gap: 10px;
align-items: center;
}
::ng-deep .items-right {
display: flex;
justify-content: end;
}
}

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Navbar } from './navbar';
describe('Navbar', () => {
let component: Navbar;
let fixture: ComponentFixture<Navbar>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Navbar],
}).compileComponents();
fixture = TestBed.createComponent(Navbar);
component = fixture.componentInstance;
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,9 @@
import { Component } from '@angular/core';
@Component({
selector: 'navbar',
imports: [],
templateUrl: './navbar.html',
styleUrl: './navbar.scss',
})
export class Navbar {}

View File

@@ -0,0 +1 @@
<img [src]="src()" [style]="'width:'+width()+';height:'+height()+'; border-radius: '+radius()+'px;'" class="">

View File

View File

@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Oimg } from './oimg';
describe('Oimg', () => {
let component: Oimg;
let fixture: ComponentFixture<Oimg>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [Oimg],
}).compileComponents();
fixture = TestBed.createComponent(Oimg);
component = fixture.componentInstance;
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,18 @@
import {Component, input, Input, signal} from '@angular/core';
@Component({
selector: 'oimg',
imports: [],
templateUrl: './oimg.html',
styleUrl: './oimg.scss',
host: {
"[style.height]": "height()",
"[style.width]": "width()",
}
})
export class Oimg {
height = input("")
width = input("")
src = input("")
radius = input(15)
}

View File

@@ -4,6 +4,9 @@ import {Keyring} from './storage/keyring';
import {KeyValue} from './storage/key-value'; import {KeyValue} from './storage/key-value';
import {SessionManager} from '@chatenium/chatenium-sdk/services/sessionManager'; import {SessionManager} from '@chatenium/chatenium-sdk/services/sessionManager';
import {Session} from '@chatenium/chatenium-sdk/domain/sessionManager.schema'; import {Session} from '@chatenium/chatenium-sdk/domain/sessionManager.schema';
import {ChatService} from '@chatenium/chatenium-sdk/services/chatService';
import {Chat} from '@chatenium/chatenium-sdk/domain/chatService.schema';
import {DMService} from '@chatenium/chatenium-sdk/services/dmService';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@@ -15,4 +18,22 @@ export class ServiceManager {
sessionManager = new SessionManager(this.database.getApi(), this.keyring.getApi(), this.keyValue.getApi()) sessionManager = new SessionManager(this.database.getApi(), this.keyring.getApi(), this.keyValue.getApi())
currentSession = signal<Session | null>(null) currentSession = signal<Session | null>(null)
chatService: ChatService | null = null // Initialized in dm-list.ts
chatsStatus = signal<LoadStatus>(LoadStatus.loading)
chats = signal<Chat[]>([])
dmServices = signal<Record<string, DmStorage>>({})
}
export enum LoadStatus {
loading = 0,
loaded = 1,
error = 2,
updating = 3,
}
export interface DmStorage {
service: DMService
chatData: Chat
} }

View File

@@ -20,3 +20,9 @@ body {
src: url("/Onest-ExtraBold.ttf"); src: url("/Onest-ExtraBold.ttf");
font-weight: bold; font-weight: bold;
} }
@font-face {
font-family: "Onest";
src: url("/Onest-SemiBold.ttf");
font-weight: 600;
}