1 Commits

Author SHA1 Message Date
8afd4a81b0 3.0 Beta 8 2026-04-17 17:55:42 +02:00
30 changed files with 834 additions and 172 deletions

8
package-lock.json generated
View File

@@ -16,7 +16,7 @@
"@angular/platform-browser": "^21.2.0", "@angular/platform-browser": "^21.2.0",
"@angular/router": "^21.2.0", "@angular/router": "^21.2.0",
"@angular/service-worker": "^21.2.0", "@angular/service-worker": "^21.2.0",
"@chatenium/chatenium-sdk": "^1.1.11", "@chatenium/chatenium-sdk": "^1.2.0",
"@fortawesome/angular-fontawesome": "^4.0.0", "@fortawesome/angular-fontawesome": "^4.0.0",
"@fortawesome/free-brands-svg-icons": "^7.1.0", "@fortawesome/free-brands-svg-icons": "^7.1.0",
"@fortawesome/free-solid-svg-icons": "^7.1.0", "@fortawesome/free-solid-svg-icons": "^7.1.0",
@@ -1011,9 +1011,9 @@
} }
}, },
"node_modules/@chatenium/chatenium-sdk": { "node_modules/@chatenium/chatenium-sdk": {
"version": "1.1.11", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@chatenium/chatenium-sdk/-/chatenium-sdk-1.1.11.tgz", "resolved": "https://registry.npmjs.org/@chatenium/chatenium-sdk/-/chatenium-sdk-1.2.0.tgz",
"integrity": "sha512-iwYHyED1AnGcWtyeVo+R1JfxVauuIC5FCX8Rk6RwF+ls/oUIg1aExFgvif2wc0qUyT9mla8ztBt+wBB/cTy4hA==", "integrity": "sha512-myNvjsMbxRji6MEHufhgEbCmltLL+Azb2UUA+ovUDuf5+LcdYiLYcIF4A4/NAxRVYx3IcPB4uOVF2f5SqW2sLA==",
"dependencies": { "dependencies": {
"@faker-js/faker": "^10.4.0", "@faker-js/faker": "^10.4.0",
"axios": "^1.14.0", "axios": "^1.14.0",

View File

@@ -19,7 +19,7 @@
"@angular/platform-browser": "^21.2.0", "@angular/platform-browser": "^21.2.0",
"@angular/router": "^21.2.0", "@angular/router": "^21.2.0",
"@angular/service-worker": "^21.2.0", "@angular/service-worker": "^21.2.0",
"@chatenium/chatenium-sdk": "^1.1.11", "@chatenium/chatenium-sdk": "^1.2.0",
"@fortawesome/angular-fontawesome": "^4.0.0", "@fortawesome/angular-fontawesome": "^4.0.0",
"@fortawesome/free-brands-svg-icons": "^7.1.0", "@fortawesome/free-brands-svg-icons": "^7.1.0",
"@fortawesome/free-solid-svg-icons": "^7.1.0", "@fortawesome/free-solid-svg-icons": "^7.1.0",

View File

@@ -1,5 +1,5 @@
{ {
"version": "Chatenium Nexum 3.0 Beta 7 (April 15, 2026)", "version": "Chatenium Nexum 3.0 Beta 8 (April 17, 2026)",
"ok": "Ok", "ok": "Ok",
"back": "Back", "back": "Back",
"aChatProgram": "A messaging platform that you can trust.", "aChatProgram": "A messaging platform that you can trust.",
@@ -103,12 +103,71 @@
"tab3": "Pictures", "tab3": "Pictures",
"tab4": "Settings" "tab4": "Settings"
}, },
"network": {
"tabs": {
"channels": "Channels",
"members": "Members",
"settings": "Settings"
},
"settings": {
"options": {
"categories": {
"apps": "Apps",
"moderation": "Moderation",
"community": "Community"
},
"overview": "Overview",
"rank": "Ranks",
"emoji": "Emojis",
"embed": "Embed",
"invite": "Invites",
"webhook": "Webhooks",
"bots": "Bots",
"activityHistory": "Activity history",
"bans": "Bans",
"communityChannels": "Community channels",
"networkIntroducer": "Network introducer",
"members": "Members"
},
"overviewPage": {
"networkPicture": "Network picture",
"networkName": "Network name",
"networkVisibility": "Network visibility",
"uploadNewPicture": "Upload new picture",
"setNewName": "Set new name",
"changeToPrivate": "Change to private",
"changeToPrivateDialog": {
"label": "You are about to make your network private",
"warn": {
"1": "Your network will be delisted from Network Discovery.",
"2": "Users will no longer be able to freely join your network without an invite.",
"3": "Your broadcasts may be unavailable to users outside your network."
}
},
"changeToPublic": "Change to public",
"changeToPublicDialog": {
"label": "You are about to make your network public",
"warn": {
"1": "Your network will be listed in Network Discovery.",
"2": "Users will be able to freely join your network without an invite.",
"3": "Your broadcasts will be available to users outside your network."
}
},
"setNewNameDialog": {
"label": "New name"
}
}
}
},
"changeLogDialog": { "changeLogDialog": {
"label": "Chatenium has been updated", "label": "Chatenium has been updated",
"changeLog": { "changeLog": {
"1": "Added progress bar to files when uploading attachments", "1": "Optimized the loading of chats and networks",
"2": "Bug fixes related to sending messages", "2": "Added skeleton loaders for messages inside DMs and channels",
"3": "Fixed scrolling issues when switching chats. Also now the scroll position is now saved" "3": "Added tabs in networks",
"4": "Added option to edit network name",
"5": "Added option to change network visibility",
"6": "Fixed overflow issues in the mobile UI"
} }
}, },
"chatnav": { "chatnav": {

View File

@@ -5,6 +5,9 @@
<li>{{ "chat.changeLogDialog.changeLog.1"|translate }}</li> <li>{{ "chat.changeLogDialog.changeLog.1"|translate }}</li>
<li>{{ "chat.changeLogDialog.changeLog.2"|translate }}</li> <li>{{ "chat.changeLogDialog.changeLog.2"|translate }}</li>
<li>{{ "chat.changeLogDialog.changeLog.3"|translate }}</li> <li>{{ "chat.changeLogDialog.changeLog.3"|translate }}</li>
<li>{{ "chat.changeLogDialog.changeLog.4"|translate }}</li>
<li>{{ "chat.changeLogDialog.changeLog.5"|translate }}</li>
<li>{{ "chat.changeLogDialog.changeLog.6"|translate }}</li>
</ul> </ul>
<button tuiButton iconStart="@tui.check" <button tuiButton iconStart="@tui.check"

View File

@@ -79,6 +79,7 @@
} }
#navigation_content { #navigation_content {
overflow-y: scroll;
height: 100%; height: 100%;
padding: 20px; padding: 20px;
} }

View File

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

View File

@@ -1,7 +1,6 @@
import {Component, inject, input, OnInit, signal} from '@angular/core'; import {Component, inject, input, OnInit} 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 {TUI_BREAKPOINT, TuiButton, TuiLoader} from '@taiga-ui/core'; import {TUI_BREAKPOINT, TuiButton, TuiLoader} from '@taiga-ui/core';
import {Oimg} from '../elements/oimg/oimg'; import {Oimg} from '../elements/oimg/oimg';
import {Router, RouterLink} from '@angular/router'; import {Router, RouterLink} from '@angular/router';
@@ -31,18 +30,20 @@ export class DmList implements OnInit {
async ngOnInit() { async ngOnInit() {
this.serviceManager.chatService = new ChatService(this.userid(), this.token(), this.indexedDb.getApi(), () => {}) this.serviceManager.chatService = new ChatService(this.userid(), this.token(), this.indexedDb.getApi(), () => {})
try { if (this.serviceManager.chatsStatus() != LoadStatus.loaded) {
this.serviceManager.chats.set(await this.serviceManager.chatService.getQuick()) try {
this.serviceManager.chatsStatus.set(LoadStatus.updating) this.serviceManager.chats.set(await this.serviceManager.chatService.getQuick())
} catch (e) { this.serviceManager.chatsStatus.set(LoadStatus.updating)
console.warn(`Cache load failed: ${e}. Skipping cache load...`) } catch (e) {
} console.warn(`Cache load failed: ${e}. Skipping cache load...`)
try { }
this.serviceManager.chats.set(await this.serviceManager.chatService.get()) try {
this.serviceManager.chatsStatus.set(LoadStatus.loaded) this.serviceManager.chats.set(await this.serviceManager.chatService.get())
} catch (e) { this.serviceManager.chatsStatus.set(LoadStatus.loaded)
console.error(e) } catch (e) {
this.serviceManager.chatsStatus.set(LoadStatus.error) console.error(e)
this.serviceManager.chatsStatus.set(LoadStatus.error)
}
} }
} }

View File

@@ -20,7 +20,7 @@
</div> </div>
</navbar> </navbar>
<messages (scrollend)="handleMessagesScroll($event)" [messageBoxViewModel]="store.messageBox" [messages]="store.messages()" id="scrollContainer" (onDelete)="deleteMessage($event)"/> <messages (scrollend)="handleMessagesScroll($event)" [loading]="store.messagesStatus() == LoadStatus.loading" [messageBoxViewModel]="store.messageBox" [messages]="store.messages()" id="scrollContainer" (onDelete)="deleteMessage($event)"/>
<message-box [viewModel]="store.messageBox"/> <message-box [viewModel]="store.messageBox"/>
} }

View File

@@ -1,5 +1,5 @@
import {Component, inject, OnInit, signal} from '@angular/core'; import {Component, inject, OnInit, signal} from '@angular/core';
import {DmStorage, ServiceManager} from '../../service-manager'; import {DmStorage, LoadStatus, ServiceManager} from '../../service-manager';
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {DMService} from '@chatenium/chatenium-sdk/services/dmService'; import {DMService} from '@chatenium/chatenium-sdk/services/dmService';
import {IndexedDB} from '../../storage/indexed-db'; import {IndexedDB} from '../../storage/indexed-db';
@@ -11,8 +11,7 @@ import {Messages} from '../elements/messages/messages';
import {Chat} from '@chatenium/chatenium-sdk/domain/chatService.schema'; import {Chat} from '@chatenium/chatenium-sdk/domain/chatService.schema';
import {Message} from '@chatenium/chatenium-sdk/domain/dmService.schema'; import {Message} from '@chatenium/chatenium-sdk/domain/dmService.schema';
import {MessageBoxViewModel} from '../elements/message-box/message-box-viewmodel'; import {MessageBoxViewModel} from '../elements/message-box/message-box-viewmodel';
import {WebSocketHandler} from '@chatenium/chatenium-sdk/core/webSocketHandler'; import {FileUploadProgressListener} from '@chatenium/chatenium-sdk/domain/fileUploadService.schema';
import {FileData, FileUploadProgressListener} from '@chatenium/chatenium-sdk/domain/fileUploadService.schema';
import {Attachment} from '@chatenium/chatenium-sdk/domain/common.schema'; import {Attachment} from '@chatenium/chatenium-sdk/domain/common.schema';
import {MessagesViewModel} from '../elements/messages/messages-viewmodel'; import {MessagesViewModel} from '../elements/messages/messages-viewmodel';
import {v4 as uuidv4} from 'uuid'; import {v4 as uuidv4} from 'uuid';
@@ -221,6 +220,7 @@ export class Dm implements OnInit {
const newStore = { const newStore = {
chatData: signal<Chat>(chatData), chatData: signal<Chat>(chatData),
messages: signal<Message[]>([]), messages: signal<Message[]>([]),
messagesStatus: signal<LoadStatus>(LoadStatus.loading),
messageBox: new MessageBoxViewModel((msg, files) => this.sendMessage(msg, files)), messageBox: new MessageBoxViewModel((msg, files) => this.sendMessage(msg, files)),
messagesVm: new MessagesViewModel(), messagesVm: new MessagesViewModel(),
wsListener: (action, data) => this.onWsListen(action, data, chatid), wsListener: (action, data) => this.onWsListen(action, data, chatid),
@@ -240,12 +240,14 @@ export class Dm implements OnInit {
try { try {
const messagesCache = await currentStore.service.getQuick(); const messagesCache = await currentStore.service.getQuick();
currentStore.messages.set(messagesCache); currentStore.messages.set(messagesCache);
this.store.messagesStatus.set(LoadStatus.updating)
this.scrollToBottom("instant") this.scrollToBottom("instant")
} catch (e) { } catch (e) {
console.warn(`Cache load failed: ${e}. Skipping cache load...`) console.warn(`Cache load failed: ${e}. Skipping cache load...`)
} }
const messages = await currentStore.service.get(); const messages = await currentStore.service.get();
currentStore.messages.set(messages); currentStore.messages.set(messages);
this.store.messagesStatus.set(LoadStatus.loaded)
this.scrollToBottom("instant") this.scrollToBottom("instant")
await currentStore.service.joinWebSocketRoom(); await currentStore.service.joinWebSocketRoom();
@@ -254,4 +256,6 @@ export class Dm implements OnInit {
} }
}); });
} }
protected readonly LoadStatus = LoadStatus;
} }

View File

@@ -1,92 +1,108 @@
@if (serviceManager.currentSession != null) { @if (loading()) {
@for (message of messages(); track message.msgid; let i = $index) { @for (_ of [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}]; track _; let i = $index) {
<div <div class="message"
class="message" [class.chained_start]="i == 0 || i == 3 || i == 6"
[class.author]=" [class.chained_end]="i == 2 || i == 5 || i == 9"
[class.chained_middle]="i != 0 && i != 3 && i != 6 && i != 2 && i != 5 && i != 9"
[class.author]="i == 3 || i == 4 || i == 5"
>
<div class="bubble" [tuiSkeleton]="true">
{{i}}
</div>
</div>
}
} @else {
@if (serviceManager.currentSession != null) {
@for (message of messages(); track message.msgid; let i = $index) {
<div
class="message"
[class.author]="
messageAsDm(message).author == serviceManager.currentSession()!.userData.userid || messageAsDm(message).author == serviceManager.currentSession()!.userData.userid ||
messageAsNetwork(message).author.userid == serviceManager.currentSession()!.userData.userid messageAsNetwork(message).author.userid == serviceManager.currentSession()!.userData.userid
" "
[class.chained_start]="isMessageStartOfChain(i)" [class.chained_start]="isMessageStartOfChain(i)"
[class.chained_middle]="isMessageMiddleInChain(i)" [class.chained_middle]="isMessageMiddleInChain(i)"
[class.chained_end]="isMessageEndOfChain(i)" [class.chained_end]="isMessageEndOfChain(i)"
> >
<div class="above"> <div class="above">
<span>{{ message.sent_at.T * 1000 | date: 'HH:mm' }}</span> <span>{{ message.sent_at.T * 1000 | date: 'HH:mm' }}</span>
</div> </div>
<div class="bubble" tuiDropdownContext [tuiDropdown]="messageContextMenu"> <div class="bubble" tuiDropdownContext [tuiDropdown]="messageContextMenu">
<span class="message-text">{{ message.message }}</span> <span class="message-text">{{ message.message }}</span>
<masonry style="max-height: 300px"> <masonry style="max-height: 300px">
@for (file of filterExpressedMedia(message.files); track file) { @for (file of filterExpressedMedia(message.files); track file) {
<div style="position:relative;"> <div style="position:relative;">
@if (file.extraMetaData && Object.keys(file.extraMetaData).length > 0) {
@if (file.extraMetaData['progressShown']) {
<tui-progress-circle
style="position: absolute; top: 0; right: 0; z-index: 10; transform: translate(-50%, -50%)"
[max]="file.extraMetaData['totalChunks']"
[value]="file.extraMetaData['uploadedChunks']"
/>
}
}
@if (file.type == "image") {
<img [src]="file.path"
style="width: 100%; height: 100%; max-height: 300px; object-fit: cover; border-radius: 25px"/>
} @else if (file.type == "video") {
@if (file.extraMetaData && Object.keys(file.extraMetaData).length > 0) { @if (file.extraMetaData && Object.keys(file.extraMetaData).length > 0) {
<video-player maxHeight="300px" maxWidth="300px" [src]="file.path" @if (file.extraMetaData['progressShown']) {
[thumbnailOverwrite]="file.extraMetaData['thumbnailMetaData']"></video-player> <tui-progress-circle
} @else { style="position: absolute; top: 0; right: 0; z-index: 10; transform: translate(-50%, -50%)"
<video-player maxHeight="300px" maxWidth="300px" [src]="file.path"></video-player> [max]="file.extraMetaData['totalChunks']"
[value]="file.extraMetaData['uploadedChunks']"
/>
}
} }
} @if (file.type == "image") {
</div> <img [src]="file.path"
} style="width: 100%; height: 100%; max-height: 300px; object-fit: cover; border-radius: 25px"/>
</masonry> } @else if (file.type == "video") {
@if (file.extraMetaData && Object.keys(file.extraMetaData).length > 0) {
<video-player maxHeight="300px" maxWidth="300px" [src]="file.path"
[thumbnailOverwrite]="file.extraMetaData['thumbnailMetaData']"></video-player>
} @else {
<video-player maxHeight="300px" maxWidth="300px" [src]="file.path"></video-player>
}
}
</div>
}
</masonry>
<div tuiGroup orientation="vertical" style="width: 100%"> <div tuiGroup orientation="vertical" style="width: 100%">
@for (file of filterNonExpressedMedia(message.files); track file) { @for (file of filterNonExpressedMedia(message.files); track file) {
<div style="width: 100%; height: 50px; display: flex; align-items: center; padding: 0 10px; background: var(--tui-background-base-alt); gap: 5px"> <div style="width: 100%; height: 50px; display: flex; align-items: center; padding: 0 10px; background: var(--tui-background-base-alt); gap: 5px">
@if (file.extraMetaData && Object.keys(file.extraMetaData).length > 0) { @if (file.extraMetaData && Object.keys(file.extraMetaData).length > 0) {
@if (file.extraMetaData['progressShown']) { @if (file.extraMetaData['progressShown']) {
<tui-progress-circle <tui-progress-circle
size="xs" size="xs"
[max]="file.extraMetaData['totalChunks']" [max]="file.extraMetaData['totalChunks']"
[value]="file.extraMetaData['uploadedChunks']" [value]="file.extraMetaData['uploadedChunks']"
/> />
} @else {
<tui-icon icon="@tui.file"/>
}
} @else { } @else {
<tui-icon icon="@tui.file"/> <tui-icon icon="@tui.file"/>
} }
} @else { <span>{{ file.fileName }}</span>
<tui-icon icon="@tui.file"/> </div>
} }
<span>{{ file.fileName }}</span> </div>
</div>
}
</div> </div>
<div class="below"></div>
<ng-template #messageContextMenu>
<tui-data-list>
<button
tuiOption
type="button"
iconEnd="@tui.pencil"
(click)="messageBoxViewModel().editingMessage.set({message: message.message, messageId: message.msgid}); messageBoxViewModel().message.set(message.message);"
>
{{ "chat.elements.messages.contextMenu.edit" | translate }}
</button>
<button
style="color: var(--tui-text-negative)"
tuiOption
type="button"
iconEnd="@tui.trash"
(click)="onDelete.emit(message.msgid)"
>
{{ "chat.elements.messages.contextMenu.delete" | translate }}
</button>
</tui-data-list>
</ng-template>
</div> </div>
<div class="below"></div> }
<ng-template #messageContextMenu>
<tui-data-list>
<button
tuiOption
type="button"
iconEnd="@tui.pencil"
(click)="messageBoxViewModel().editingMessage.set({message: message.message, messageId: message.msgid}); messageBoxViewModel().message.set(message.message);"
>
{{ "chat.elements.messages.contextMenu.edit" | translate }}
</button>
<button
style="color: var(--tui-text-negative)"
tuiOption
type="button"
iconEnd="@tui.trash"
(click)="onDelete.emit(message.msgid)"
>
{{ "chat.elements.messages.contextMenu.delete" | translate }}
</button>
</tui-data-list>
</ng-template>
</div>
} }
} }

View File

@@ -17,6 +17,24 @@
.bubble { .bubble {
background: var(--tui-background-accent-1-hover); background: var(--tui-background-accent-1-hover);
} }
&.chained_start {
.bubble {
border-radius: 10px 25px 10px 10px !important;
}
}
&.chained_middle {
.bubble {
border-radius: 10px !important;
}
}
&.chained_end {
.bubble {
border-radius: 10px 10px 25px 10px !important;
}
}
} }
&.chained_start { &.chained_start {
@@ -31,7 +49,7 @@
} }
.bubble { .bubble {
border-radius: 10px 25px 10px 10px !important; border-radius: 25px 10px 10px 10px !important;
} }
} }
@@ -43,7 +61,7 @@
&.chained_end { &.chained_end {
.bubble { .bubble {
border-radius: 10px 10px 25px 10px !important; border-radius: 10px 10px 10px 25px !important;
} }
} }
@@ -65,7 +83,7 @@
padding: 10px; padding: 10px;
.message-text { .message-text {
white-space: none; white-space: break-spaces;
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
} }

View File

@@ -18,7 +18,7 @@ import {TranslatePipe} from '@ngx-translate/core';
import {MessageBoxViewModel} from '../message-box/message-box-viewmodel'; import {MessageBoxViewModel} from '../message-box/message-box-viewmodel';
import {FileDataWithPreview} from '../message-box/message-box'; import {FileDataWithPreview} from '../message-box/message-box';
import {Attachment} from '@chatenium/chatenium-sdk/domain/common.schema'; import {Attachment} from '@chatenium/chatenium-sdk/domain/common.schema';
import {TuiProgressCircle} from '@taiga-ui/kit'; import {TuiProgressCircle, TuiSkeleton} from '@taiga-ui/kit';
@Component({ @Component({
selector: 'messages', selector: 'messages',
@@ -34,7 +34,8 @@ import {TuiProgressCircle} from '@taiga-ui/kit';
TuiDataListComponent, TuiDataListComponent,
TuiGroup, TuiGroup,
TuiIcon, TuiIcon,
TuiProgressCircle TuiProgressCircle,
TuiSkeleton
], ],
templateUrl: './messages.html', templateUrl: './messages.html',
styleUrl: './messages.scss', styleUrl: './messages.scss',
@@ -44,6 +45,7 @@ export class Messages {
messages = input.required<Message[] | NetworkMessage[]>() messages = input.required<Message[] | NetworkMessage[]>()
messageBoxViewModel = input.required<MessageBoxViewModel>() messageBoxViewModel = input.required<MessageBoxViewModel>()
loading = input(false)
@Output() onDelete = new EventEmitter<string>() @Output() onDelete = new EventEmitter<string>()

View File

@@ -3,7 +3,6 @@ import {IndexedDB} from '../../storage/indexed-db';
import {Router, RouterLink} from '@angular/router'; import {Router, RouterLink} from '@angular/router';
import {LoadStatus, ServiceManager} from '../../service-manager'; import {LoadStatus, ServiceManager} from '../../service-manager';
import {TUI_BREAKPOINT, TuiButton, TuiLoader} from '@taiga-ui/core'; import {TUI_BREAKPOINT, TuiButton, TuiLoader} from '@taiga-ui/core';
import {ChatService} from '@chatenium/chatenium-sdk/services/chatService';
import {NetworkService} from '@chatenium/chatenium-sdk/services/networkService'; import {NetworkService} from '@chatenium/chatenium-sdk/services/networkService';
import {Oimg} from '../elements/oimg/oimg'; import {Oimg} from '../elements/oimg/oimg';
import {TranslatePipe} from '@ngx-translate/core'; import {TranslatePipe} from '@ngx-translate/core';
@@ -31,18 +30,20 @@ export class NetworkList implements OnInit {
async ngOnInit() { async ngOnInit() {
this.serviceManager.networkService = new NetworkService(this.userid(), this.token(), "", this.indexedDb.getApi(), () => {}) this.serviceManager.networkService = new NetworkService(this.userid(), this.token(), "", this.indexedDb.getApi(), () => {})
try { if (this.serviceManager.networksStatus() != LoadStatus.loaded) {
this.serviceManager.networks.set(await this.serviceManager.networkService.getQuick()) try {
this.serviceManager.networksStatus.set(LoadStatus.updating) this.serviceManager.networks.set(await this.serviceManager.networkService.getQuick())
} catch (e) { this.serviceManager.networksStatus.set(LoadStatus.updating)
console.warn(`Cache load failed: ${e}. Skipping cache load...`) } catch (e) {
} console.warn(`Cache load failed: ${e}. Skipping cache load...`)
try { }
this.serviceManager.networks.set(await this.serviceManager.networkService.get()) try {
this.serviceManager.networksStatus.set(LoadStatus.loaded) this.serviceManager.networks.set(await this.serviceManager.networkService.get())
} catch (e) { this.serviceManager.networksStatus.set(LoadStatus.loaded)
console.error(e) } catch (e) {
this.serviceManager.networksStatus.set(LoadStatus.error) console.error(e)
this.serviceManager.networksStatus.set(LoadStatus.error)
}
} }
} }

View File

@@ -25,7 +25,7 @@
</div> </div>
</navbar> </navbar>
<messages (scrollend)="handleMessagesScroll($event)" [messageBoxViewModel]="store!.messageBox" [messages]="store!.messages()" id="scrollContainer" <messages [loading]="store!.messagesStatus() == LoadStatus.loading" (scrollend)="handleMessagesScroll($event)" [messageBoxViewModel]="store!.messageBox" [messages]="store!.messages()" id="scrollContainer"
(onDelete)="deleteMessage($event)"/> (onDelete)="deleteMessage($event)"/>
<message-box [viewModel]="store!.messageBox"/> <message-box [viewModel]="store!.messageBox"/>

View File

@@ -1,5 +1,5 @@
import {Component, inject, signal} from '@angular/core'; import {Component, inject, signal} from '@angular/core';
import {DmStorage, ServiceManager, TextChannelStorage} from '../../../../service-manager'; import {DmStorage, LoadStatus, ServiceManager, TextChannelStorage} from '../../../../service-manager';
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {IndexedDB} from '../../../../storage/indexed-db'; import {IndexedDB} from '../../../../storage/indexed-db';
import {TUI_BREAKPOINT, TuiButton, TuiIcon} from '@taiga-ui/core'; import {TUI_BREAKPOINT, TuiButton, TuiIcon} from '@taiga-ui/core';
@@ -261,6 +261,7 @@ export class Text {
channelData: signal(channelData), channelData: signal(channelData),
messages: signal<Message[]>([]), messages: signal<Message[]>([]),
messagesVm: new MessagesViewModel(), messagesVm: new MessagesViewModel(),
messagesStatus: signal<LoadStatus>(LoadStatus.loading),
messageBox: new MessageBoxViewModel((msg, files) => this.sendMessage(msg, files)), messageBox: new MessageBoxViewModel((msg, files) => this.sendMessage(msg, files)),
wsListener: (action, data) => this.onWsListen(action, data, networkId, categoryId), wsListener: (action, data) => this.onWsListen(action, data, networkId, categoryId),
} as TextChannelStorage; } as TextChannelStorage;
@@ -282,11 +283,13 @@ export class Text {
const messagesCache = await currentStore.service.getQuick(); const messagesCache = await currentStore.service.getQuick();
currentStore.messages.set(messagesCache); currentStore.messages.set(messagesCache);
this.scrollToBottom("instant") this.scrollToBottom("instant")
currentStore.messagesStatus.set(LoadStatus.updating)
} catch (e) { } catch (e) {
console.warn(`Cache load failed: ${e}. Skipping cache load...`) console.warn(`Cache load failed: ${e}. Skipping cache load...`)
} }
const messages = await currentStore.service.get(); const messages = await currentStore.service.get();
currentStore.messages.set(messages); currentStore.messages.set(messages);
currentStore.messagesStatus.set(LoadStatus.loaded)
this.scrollToBottom("instant") this.scrollToBottom("instant")
await currentStore.service.joinWebSocketRoom(); await currentStore.service.joinWebSocketRoom();
@@ -295,4 +298,6 @@ export class Text {
} }
}); });
} }
protected readonly LoadStatus = LoadStatus;
} }

View File

@@ -13,32 +13,70 @@
<div class="items-right"></div> <div class="items-right"></div>
</navbar> </navbar>
<main tuiGroup orientation="vertical" style="width: 100%"> <tui-tabs [(activeItemIndex)]="tabActiveIndex">
@for (category of store.networkData().categories; track category) { <button
<div class="category"> iconStart="@tui.hash"
<h2>{{category.name}}</h2> tuiTab
type="button"
>
{{"chat.network.tabs.channels"|translate}}
</button>
<div tuiGroup orientation="vertical" style="width: 100%"> <button
@for (channel of category.channels; track channel) { disabled
<button tuiButton class="channel" [appearance]="router.url.endsWith(channel.channelId) ? 'primary' : 'secondary'" [disabled]="channel.type != 'message'" [routerLink]="'/chat/network/'+store.networkData().networkId+'/'+category.categoryId+'/'+channel.channelId"> iconStart="@tui.users"
@switch (channel.type) { tuiTab
@case ("message") { type="button"
<tui-icon icon="@tui.hash"></tui-icon> >
} {{"chat.network.tabs.members"|translate}}
@case ("broadcast") { </button>
<tui-icon icon="@tui.radio"></tui-icon>
} <button
@case ("voice") { iconStart="@tui.cog"
<tui-icon icon="@tui.audio-lines"></tui-icon> tuiTab
} type="button"
>
{{"chat.network.tabs.settings"|translate}}
</button>
</tui-tabs>
<br/>
@switch (tabActiveIndex) {
@case (0) {
<main tuiGroup orientation="vertical" style="width: 100%">
@for (category of store.networkData().categories; track category) {
<div class="category">
<h2>{{category.name}}</h2>
<div tuiGroup orientation="vertical" style="width: 100%">
@for (channel of category.channels; track channel) {
<button tuiButton class="channel" [appearance]="router.url.endsWith(channel.channelId) ? 'primary' : 'secondary'" [disabled]="channel.type != 'message'" [routerLink]="'/chat/network/'+store.networkData().networkId+'/'+category.categoryId+'/'+channel.channelId">
@switch (channel.type) {
@case ("message") {
<tui-icon icon="@tui.hash"></tui-icon>
}
@case ("broadcast") {
<tui-icon icon="@tui.radio"></tui-icon>
}
@case ("voice") {
<tui-icon icon="@tui.audio-lines"></tui-icon>
}
}
<span>{{channel.name}}</span>
</button>
} }
<span>{{channel.name}}</span> </div>
</button> </div>
} }
</div> </main>
</div>
} }
</main> @case (2) {
<main id="settings">
<network-settings [networkStore]="this.store"></network-settings>
</main>
}
}
</div> </div>
</div> </div>
} }

View File

@@ -8,6 +8,11 @@
padding: 15px; padding: 15px;
transition: 0.2s; transition: 0.2s;
#network-data {
display: flex;
flex-direction: column;
}
&.routerOutletActive { &.routerOutletActive {
overflow-y: hidden; overflow-y: hidden;
padding: 0; padding: 0;
@@ -42,4 +47,11 @@
justify-content: start; justify-content: start;
} }
} }
#settings {
width: 100%;
height: 100%;
background: var(--tui-background-base-alt);
border-radius: 20px;
}
} }

View File

@@ -11,6 +11,10 @@ import {IndexedDB} from '../../storage/indexed-db';
import {Navbar} from '../elements/navbar/navbar'; import {Navbar} from '../elements/navbar/navbar';
import {Oimg} from '../elements/oimg/oimg'; import {Oimg} from '../elements/oimg/oimg';
import {TUI_BREAKPOINT, TuiButton, TuiGroup, TuiIcon} from '@taiga-ui/core'; import {TUI_BREAKPOINT, TuiButton, TuiGroup, TuiIcon} from '@taiga-ui/core';
import {TuiTab, TuiTabsHorizontal, TuiTabsWithMore} from '@taiga-ui/kit';
import {TuiItem} from '@taiga-ui/cdk';
import {TranslatePipe} from '@ngx-translate/core';
import {Settings} from './settings/settings';
@Component({ @Component({
selector: 'app-network', selector: 'app-network',
@@ -21,7 +25,13 @@ import {TUI_BREAKPOINT, TuiButton, TuiGroup, TuiIcon} from '@taiga-ui/core';
TuiGroup, TuiGroup,
TuiButton, TuiButton,
TuiIcon, TuiIcon,
RouterLink RouterLink,
TuiTabsWithMore,
TuiItem,
TuiTab,
TranslatePipe,
TuiTabsHorizontal,
Settings
], ],
templateUrl: './network.html', templateUrl: './network.html',
styleUrl: './network.scss', styleUrl: './network.scss',
@@ -33,6 +43,8 @@ export class Network implements OnInit {
breakpoint = inject(TUI_BREAKPOINT) breakpoint = inject(TUI_BREAKPOINT)
router = inject(Router) router = inject(Router)
tabActiveIndex = 2
routerOutletActive = signal(false) routerOutletActive = signal(false)
networkId = "" networkId = ""

View File

@@ -0,0 +1,114 @@
<div tuiGroup id="options" orientation="vertical">
<div>
<header>
<tui-icon icon="@tui.image"></tui-icon>
{{ "chat.network.settings.overviewPage.networkPicture"|translate }}
<oimg [src]="networkStore().networkData().picture" height="25px" width="25px" [radius]="6"/>
</header>
<main>
<button tuiButton appearance="outline" disabled iconEnd="@tui.file-up">
{{ "chat.network.settings.overviewPage.uploadNewPicture"|translate }}
</button>
</main>
</div>
<div>
<ng-template [(tuiDialog)]="setNewNameDialogOpen"
[tuiDialogOptions]="{label: 'chat.network.settings.overviewPage.setNewName'|translate}">
<tui-textfield #newName iconStart="@tui.signature">
<label tuiLabel>{{"chat.network.settings.overviewPage.setNewNameDialog.label"|translate}}</label>
<input type="text" tuiInput>
</tui-textfield>
@if (setNewNameError() != "") {
<tui-error [error]="setNewNameError()"></tui-error>
}
<footer>
<button tuiButton iconStart="@tui.check" [loading]="setNewNamePending()" [disabled]="setNewNamePending()" (click)="setNewName(newName.value())">
{{'chat.network.settings.overviewPage.setNewName'|translate}}
</button>
</footer>
</ng-template>
<header>
<tui-icon icon="@tui.network"></tui-icon>
{{ "chat.network.settings.overviewPage.networkName"|translate }}
<b style="color: gray">{{ networkStore().networkData().name }}</b>
</header>
<main>
<button (click)="setNewNameDialogOpen.set(true)" tuiButton appearance="outline" iconEnd="@tui.pencil">
{{ "chat.network.settings.overviewPage.setNewName"|translate }}
</button>
</main>
</div>
<div>
<ng-template [(tuiDialog)]="makeNetworkPrivateWarnDialogOpen"
[tuiDialogOptions]="{label: 'chat.network.settings.overviewPage.changeToPrivateDialog.label'|translate}">
<ul>
<li>
<tui-icon icon="@tui.eye-off"/>
{{ "chat.network.settings.overviewPage.changeToPrivateDialog.warn.1"|translate }}
</li>
<li>
<tui-icon icon="@tui.ticket"/>
{{ "chat.network.settings.overviewPage.changeToPrivateDialog.warn.2"|translate }}
</li>
<li>
<tui-icon icon="@tui.radio"/>
{{ "chat.network.settings.overviewPage.changeToPrivateDialog.warn.3"|translate }}
</li>
</ul>
<footer>
<button tuiButton iconStart="@tui.eye-off" [loading]="networkVisChangePending()" [disabled]="networkVisChangePending()" (click)="changeVisibility('private')">
{{'chat.network.settings.overviewPage.changeToPrivate'|translate}}
</button>
</footer>
</ng-template>
<ng-template [(tuiDialog)]="makeNetworkPublicWarnDialogOpen"
[tuiDialogOptions]="{label: 'chat.network.settings.overviewPage.changeToPublicDialog.label'|translate}">
<ul>
<li>
<tui-icon icon="@tui.eye"/>
{{ "chat.network.settings.overviewPage.changeToPublicDialog.warn.1"|translate }}
</li>
<li>
<tui-icon icon="@tui.ticket"/>
{{ "chat.network.settings.overviewPage.changeToPublicDialog.warn.2"|translate }}
</li>
<li>
<tui-icon icon="@tui.radio"/>
{{ "chat.network.settings.overviewPage.changeToPublicDialog.warn.3"|translate }}
</li>
</ul>
<footer>
<button tuiButton iconStart="@tui.eye" [loading]="networkVisChangePending()" [disabled]="networkVisChangePending()" (click)="changeVisibility('public')">
{{'chat.network.settings.overviewPage.changeToPublic'|translate}}
</button>
</footer>
</ng-template>
<header>
@if (networkStore().networkData().visibility == "public") {
<tui-icon icon="@tui.eye"></tui-icon>
} @else {
<tui-icon icon="@tui.eye-off"></tui-icon>
}
{{ "chat.network.settings.overviewPage.networkVisibility"|translate }}
</header>
<main>
@if (networkStore().networkData().visibility == "public") {
<button tuiButton appearance="outline" iconEnd="@tui.eye-off" (click)="makeNetworkPrivateWarnDialogOpen.set(true)">
{{ "chat.network.settings.overviewPage.changeToPrivate"|translate }}
</button>
} @else {
<button tuiButton appearance="outline" iconEnd="@tui.eye" (click)="makeNetworkPublicWarnDialogOpen.set(true)">
{{ "chat.network.settings.overviewPage.changeToPublic"|translate }}
</button>
}
</main>
</div>
</div>

View File

@@ -0,0 +1,31 @@
#options {
display: flex;
flex-direction: column;
div {
width: 100%;
height: 60px;
background: var(--tui-background-base-alt);
padding: 15px;
display: grid;
grid-template-columns: 1fr 1fr;
align-items: center;
gap: 10px;
header {
display: flex;
gap: 5px;
align-items: center;
}
main {
display: flex;
align-items: center;
justify-content: end;
button {
height: 35px;
}
}
}
}

View File

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

View File

@@ -0,0 +1,79 @@
import {Component, inject, input, signal} from '@angular/core';
import {NetworkStorage} from '../../../../service-manager';
import {
TuiAlertService,
TuiButton,
TuiDialog,
TuiErrorComponent,
TuiGroup,
TuiIcon,
TuiInputDirective
} from '@taiga-ui/core';
import {TranslatePipe} from '@ngx-translate/core';
import {Oimg} from '../../../elements/oimg/oimg';
import {environment} from '../../../../../environments/environment';
import {TuiButtonLoading, TuiComboBox} from '@taiga-ui/kit';
@Component({
selector: 'network-settings-overview',
imports: [
TuiGroup,
TranslatePipe,
TuiIcon,
Oimg,
TuiButton,
TuiDialog,
TuiComboBox,
TuiInputDirective,
TuiButtonLoading,
TuiErrorComponent
],
templateUrl: './overview.html',
styleUrl: './overview.scss',
})
export class Overview {
networkStore = input.required<NetworkStorage>()
protected readonly environment = environment;
protected readonly localStorage = localStorage;
setNewNameDialogOpen = signal(false)
setNewNamePending = signal(false)
setNewNameError = signal("")
makeNetworkPrivateWarnDialogOpen = signal(false)
makeNetworkPublicWarnDialogOpen = signal(false)
networkVisChangePending = signal(false)
async setNewName(name: string) {
this.setNewNameError.set("")
this.setNewNamePending.set(true)
try {
await this.networkStore().service.editName(name)
this.setNewNamePending.set(false)
this.setNewNameDialogOpen.set(false)
this.networkStore().networkData.update(
value => {
value.name = name
return value
}
)
} catch (e) {
this.setNewNamePending.set(false)
this.setNewNameError.set(e as string)
}
}
async changeVisibility(newVisibility: "public" | "private") {
this.networkVisChangePending.set(true)
await this.networkStore().service.changeVisibility(newVisibility)
this.networkStore().networkData.update(
value => {
value.visibility = newVisibility
return value
}
)
this.networkVisChangePending.set(false)
this.makeNetworkPrivateWarnDialogOpen.set(false)
this.makeNetworkPublicWarnDialogOpen.set(false)
}
}

View File

@@ -0,0 +1,29 @@
<aside>
@for (category of networkSettingsOptions; track category) {
<header>
<tui-icon [icon]="'@tui.'+category.icon"></tui-icon>
@if (category.name == "networkName") {
{{networkStore().networkData().name}}
} @else {
{{'chat.network.settings.options.categories.'+category.name|translate}}
}
</header>
<div class="optionList">
@for (option of category.options; track option;) {
<button (click)="selectedOption = option.name" [appearance]="selectedOption == option.name ? 'primary' : 'flat'" tuiButton [disabled]="!option.implemented || !optionRequiredPermissionsGranted(networkStore().networkData().permissions, option.requiredAtLeastOneOf ?? [])">
<tui-icon [icon]="'@tui.'+option.icon"></tui-icon>
{{'chat.network.settings.options.'+option.name|translate}}
</button>
}
</div>
}
</aside>
<main>
@switch (selectedOption) {
@case ("overview") {
<network-settings-overview [networkStore]="networkStore()"/>
}
}
</main>

View File

@@ -0,0 +1,37 @@
:host {
height: 100%;
width: 100%;
display: grid;
grid-template-columns: 300px 1fr;
aside {
height: 100%;
width: 100%;
border-radius: 20px 0 0 20px;
padding: 15px;
header {
display: flex;
gap: 5px;
align-items: center;
}
div {
display: flex;
flex-direction: column;
gap: 1px;
padding: 5px;
button {
height: 40px;
display: flex;
justify-content: start;
}
}
}
main {
background: var(--tui-background-neutral-2);
border-radius: 0 20px 20px 0;
padding: 15px;
}
}

View File

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

View File

@@ -0,0 +1,150 @@
import {Component, inject, input} from '@angular/core';
import {TuiButton, TuiGroup, TuiIcon} from '@taiga-ui/core';
import {permissionGranted, permissions} from '@chatenium/chatenium-sdk/core/permissions';
import {NetworkStorage, ServiceManager} from '../../../service-manager';
import {Session} from '@chatenium/chatenium-sdk/domain/sessionManager.schema';
import {SessionManager} from '@chatenium/chatenium-sdk/services/sessionManager';
import {TranslatePipe} from '@ngx-translate/core';
import {Overview} from './overview/overview';
@Component({
selector: 'network-settings',
imports: [
TuiGroup,
TuiIcon,
TranslatePipe,
TuiButton,
Overview
],
templateUrl: './settings.html',
styleUrl: './settings.scss',
})
export class Settings {
serviceManager = inject(ServiceManager)
networkStore = input.required<NetworkStorage>()
selectedOption = "overview"
optionRequiredPermissionsGranted(permissions: number, required: number[]) {
if (!this.serviceManager.currentSession) {
return false
}
let granted = false;
if (this.networkStore().networkData().createdBy == this.serviceManager.currentSession()!.userData.userid) {
return true
}
required.forEach((permission) => {
if (permissionGranted(permissions, permission)) {
granted = true
}
})
return granted
}
networkSettingsOptions: {
name: string,
icon: string,
options: {
name: string, // ? channel.settings.[name]
icon: string // ? Class name
requiredAtLeastOneOf?: number[],
implemented: boolean
}[]
}[] = [
{
name: "networkName", // ? UI Will show network name
icon: "network",
options: [
{
icon: "eye",
name: "overview",
implemented: true,
requiredAtLeastOneOf: [permissions.changeNetworkNamePictureAndVisibility]
},
{
name: "rank",
icon: "book",
requiredAtLeastOneOf: [permissions.createAndEditRanks, permissions.deleteRanks],
implemented: false,
},
{
name: "emoji",
icon: "smile",
implemented: false,
requiredAtLeastOneOf: [permissions.createEmojis]
},
{
name: "embed",
icon: "code",
implemented: false,
requiredAtLeastOneOf: [permissions.manageEmbed]
},
{
name: "invite",
icon: "ticket",
implemented: false,
requiredAtLeastOneOf: [permissions.createInvites, permissions.deleteInvites]
}
]
},
{
name: "apps",
icon: "layout-panel-left",
options: [
{
name: "webhook",
icon: "globe",
implemented: false,
requiredAtLeastOneOf: [permissions.deleteWebhooks, permissions.createWebhooks]
},
{
name: "bots",
icon: "bot",
implemented: false,
}
]
},
{
name: "moderation",
icon: "shield-half",
options: [
{
name: "activityHistory",
icon: "scroll-text",
implemented: false,
},
{
name: "bans",
icon: "gavel",
implemented: false,
requiredAtLeastOneOf: [permissions.banMembers]
}
]
},
{
name: "community",
icon: "users",
options: [
{
name: "communityChannels",
icon: "hash",
implemented: false,
},
{
name: "networkIntroducer",
icon: "network",
implemented: false,
},
{
name: "members",
icon: "users",
implemented: false,
}
]
}
]
}

View File

@@ -30,18 +30,20 @@ export class PictureList implements OnInit {
async ngOnInit() { async ngOnInit() {
this.serviceManager.chatService = new ChatService(this.userid(), this.token(), this.indexedDb.getApi(), () => {}) this.serviceManager.chatService = new ChatService(this.userid(), this.token(), this.indexedDb.getApi(), () => {})
try { if (this.serviceManager.chatsStatus() != LoadStatus.loaded) {
this.serviceManager.chats.set(await this.serviceManager.chatService.getQuick()) try {
this.serviceManager.chatsStatus.set(LoadStatus.updating) this.serviceManager.chats.set(await this.serviceManager.chatService.getQuick())
} catch (e) { this.serviceManager.chatsStatus.set(LoadStatus.updating)
console.warn(`Cache load failed: ${e}. Skipping cache load...`) } catch (e) {
} console.warn(`Cache load failed: ${e}. Skipping cache load...`)
try { }
this.serviceManager.chats.set(await this.serviceManager.chatService.get()) try {
this.serviceManager.chatsStatus.set(LoadStatus.loaded) this.serviceManager.chats.set(await this.serviceManager.chatService.get())
} catch (e) { this.serviceManager.chatsStatus.set(LoadStatus.loaded)
console.error(e) } catch (e) {
this.serviceManager.chatsStatus.set(LoadStatus.error) console.error(e)
this.serviceManager.chatsStatus.set(LoadStatus.error)
}
} }
} }
} }

View File

@@ -53,6 +53,7 @@ export enum LoadStatus {
export interface DmStorage { export interface DmStorage {
service: DMService service: DMService
messages: WritableSignal<Message[]> messages: WritableSignal<Message[]>
messagesStatus: WritableSignal<LoadStatus>
messagesVm: MessagesViewModel messagesVm: MessagesViewModel
chatData: WritableSignal<Chat> chatData: WritableSignal<Chat>
messageBox: MessageBoxViewModel messageBox: MessageBoxViewModel
@@ -76,6 +77,7 @@ export interface TextChannelStorage {
service: TextChannelServiceService service: TextChannelServiceService
messages: WritableSignal<NetworkMessage[]> messages: WritableSignal<NetworkMessage[]>
messagesVm: MessagesViewModel messagesVm: MessagesViewModel
messagesStatus: WritableSignal<LoadStatus>
channelData: WritableSignal<NetworkChannel> channelData: WritableSignal<NetworkChannel>
categoryData: WritableSignal<NetworkCategory> categoryData: WritableSignal<NetworkCategory>
messageBox: MessageBoxViewModel messageBox: MessageBoxViewModel

View File

@@ -1,5 +1,5 @@
export const environment = { export const environment = {
version: "3.0-beta7", version: "3.0-beta8",
api_url: "http://localhost:3000", api_url: "http://localhost:3000",
cdn_url: "http://localhost:4000", cdn_url: "http://localhost:4000",
ws_url: "ws://localhost:3000", ws_url: "ws://localhost:3000",

View File

@@ -1,5 +1,5 @@
export const environment = { export const environment = {
version: "3.0-beta7", version: "3.0-beta8",
api_url: "https://api.chatenium.hu", api_url: "https://api.chatenium.hu",
cdn_url: "https://cdn.chatenium.hu", cdn_url: "https://cdn.chatenium.hu",
ws_url: "wss://api.chatenium.hu", ws_url: "wss://api.chatenium.hu",