Files
web/frontend/src/pages/app/ui/panels/PublicChatPanel.ts
T
2025-10-02 18:18:08 +03:00

192 lines
6.5 KiB
TypeScript

import { MessagePanel } from "./MessagePanel";
import { API_BASE_URL } from "../../core/config";
import { getAuthHeaders } from "../../auth/api";
import { request } from "../../core/websocket";
import type { ChatWebSocketMessage, Message, SendMessageRequest } from "../../core/types";
import type { UserState } from "../state";
export class PublicChatPanel extends MessagePanel {
private messagesLoaded: boolean = false;
constructor(
chatName: string,
currentUser: UserState
) {
super(`public-${chatName}`, currentUser);
this.updateState({
title: chatName,
online: true // Public chats are always "online"
});
}
isDm(): boolean {
return false;
}
async activate(): Promise<void> {
if (!this.messagesLoaded) {
await this.loadMessages();
}
}
deactivate(): void {
// Public chat doesn't need special cleanup
}
async loadMessages(): Promise<void> {
if (!this.currentUser.authToken || this.messagesLoaded) return;
this.setLoading(true);
try {
const response = await fetch(`${API_BASE_URL}/get_messages`, {
headers: getAuthHeaders(this.currentUser.authToken)
});
if (response.ok) {
const data = await response.json();
if (data.messages && data.messages.length > 0) {
this.clearMessages();
data.messages.forEach((msg: Message) => {
this.addMessage(msg);
});
}
}
this.messagesLoaded = true;
} catch (error) {
console.error("Error loading public chat messages:", error);
} finally {
this.setLoading(false);
}
}
protected async sendMessage(content: string, replyToId?: number, files: File[] = []): Promise<void> {
if (!this.currentUser.authToken || !content.trim()) return;
try {
if (files.length === 0) {
const response = await request({
data: {
content: content.trim(),
reply_to_id: replyToId ?? null
},
credentials: {
scheme: "Bearer",
credentials: this.currentUser.authToken
},
type: "sendMessage"
} satisfies SendMessageRequest);
if (response.error) {
console.error("Error sending message:", response.error);
}
} else {
const form = new FormData();
form.append("payload", JSON.stringify({
content: content.trim(),
reply_to_id: replyToId ?? null
} satisfies SendMessageRequest["data"]));
for (const f of files) form.append("files", f, f.name);
const res = await fetch(`${API_BASE_URL}/send_message`, {
method: "POST",
headers: getAuthHeaders(this.currentUser.authToken, false),
body: form
});
if (!res.ok) {
console.error("Error sending message with files", await res.text());
}
}
} catch (error) {
console.error("Error sending message:", error);
}
}
// Handle incoming WebSocket messages
handleWebSocketMessage = (response: ChatWebSocketMessage): void => {
switch (response.type) {
case 'messageEdited':
if (response.data) {
this.updateMessage(response.data.id, response.data);
}
break;
case 'messageDeleted':
if (response.data && response.data.message_id) {
this.removeMessage(response.data.message_id);
}
break;
case 'newMessage':
if (response.data) {
const newMsg = response.data;
// Check if this is a confirmation of a message we sent
const isOurMessage = newMsg.username === this.currentUser.currentUser?.username;
if (isOurMessage) {
// This is our message being confirmed, find the temp message and replace it
const tempMessages = this.getMessages().filter(m => m.id === -1 && m.runtimeData?.sendingState?.tempId);
for (const tempMsg of tempMessages) {
if (tempMsg.runtimeData?.sendingState?.retryData?.content === newMsg.content) {
this.handleMessageConfirmed(tempMsg.runtimeData.sendingState.tempId!, newMsg);
return;
}
}
}
this.addMessage(newMsg);
}
break;
}
};
// Reset for chat switching
reset(): void {
this.messagesLoaded = false;
this.clearMessages();
}
// Update chat name
setChatName(chatName: string): void {
this.updateState({
id: `public-${chatName}`,
title: chatName
});
}
// Update auth token
setAuthToken(authToken: string): void {
this.currentUser.authToken = authToken;
}
async handleEditMessage(messageId: number, content: string): Promise<void> {
if (!this.currentUser.authToken) return;
try {
await request({
type: "editMessage",
data: {
message_id: messageId,
content: content
},
credentials: {
scheme: "Bearer",
credentials: this.currentUser.authToken
}
});
} catch (error) {
console.error("Failed to edit message:", error);
}
}
async handleDeleteMessage(id: number): Promise<void> {
// Remove message immediately from UI
this.deleteMessageImmediately(id);
// Fire and forget server deletion; UI already updated
await request({
type: "deleteMessage",
data: { message_id: id },
credentials: {
scheme: "Bearer",
credentials: this.currentUser.authToken!
}
});
}
handleProfileClick(): void {}
}