TeaWeb/shared/js/ui/frames/side/AbstractConversationController.ts

327 lines
14 KiB
TypeScript
Raw Normal View History

import {AbstractConversationUiEvents, ChatHistoryState} from "./AbstractConversationDefinitions";
import {EventHandler, Registry} from "../../../events";
import {LogCategory, logError} from "../../../log";
import {tr, tra} from "../../../i18n/localize";
import {
AbstractChat,
AbstractChatManager,
AbstractChatManagerEvents,
AbstractConversationEvents
} from "tc-shared/conversations/AbstractConversion";
export const kMaxChatFrameMessageSize = 50; /* max 100 messages, since the server does not support more than 100 messages queried at once */
export abstract class AbstractConversationController<
Events extends AbstractConversationUiEvents,
Manager extends AbstractChatManager<ManagerEvents, ConversationType, ConversationEvents>,
ManagerEvents extends AbstractChatManagerEvents<ConversationType>,
ConversationType extends AbstractChat<ConversationEvents>,
ConversationEvents extends AbstractConversationEvents
> {
protected readonly uiEvents: Registry<Events>;
protected conversationManager: Manager | undefined;
protected listenerManager: (() => void)[];
protected currentSelectedConversation: ConversationType;
protected currentSelectedListener: (() => void)[];
protected constructor() {
this.uiEvents = new Registry<Events>();
this.currentSelectedListener = [];
this.listenerManager = [];
}
destroy() {
this.listenerManager.forEach(callback => callback());
this.listenerManager.splice(0, this.listenerManager.length);
this.uiEvents.fire("notify_destroy");
this.uiEvents.destroy();
}
getUiEvents() : Registry<Events> {
return this.uiEvents;
}
protected setConversationManager(manager: Manager | undefined) {
if(this.conversationManager === manager) {
return;
}
this.listenerManager.forEach(callback => callback());
this.listenerManager = [];
this.conversationManager = manager;
if(manager) {
this.registerConversationManagerEvents(manager);
this.setCurrentlySelected(manager.getSelectedConversation());
} else {
this.setCurrentlySelected(undefined);
}
}
protected registerConversationManagerEvents(manager: Manager) {
this.listenerManager.push(manager.events.on("notify_selected_changed", event => this.setCurrentlySelected(event.newConversation)));
this.listenerManager.push(manager.events.on("notify_cross_conversation_support_changed", () => {
const currentConversation = this.getCurrentConversation();
if(currentConversation) {
this.reportStateToUI(currentConversation);
}
}));
}
protected registerConversationEvents(conversation: ConversationType) {
this.currentSelectedListener.push(conversation.events.on("notify_unread_timestamp_changed", event =>
this.uiEvents.fire_react("notify_unread_timestamp_changed", { chatId: conversation.getChatId(), timestamp: event.timestamp })));
this.currentSelectedListener.push(conversation.events.on("notify_send_toggle", event =>
this.uiEvents.fire_react("notify_send_enabled", { chatId: conversation.getChatId(), enabled: event.enabled })));
this.currentSelectedListener.push(conversation.events.on("notify_chat_event", event => {
this.uiEvents.fire_react("notify_chat_event", { chatId: conversation.getChatId(), event: event.event, triggerUnread: event.triggerUnread });
}));
this.currentSelectedListener.push(conversation.events.on("notify_state_changed", () => {
this.reportStateToUI(conversation);
}));
this.currentSelectedListener.push(conversation.events.on("notify_history_state_changed", () => {
this.reportStateToUI(conversation);
}));
this.currentSelectedListener.push(conversation.events.on("notify_read_state_changed", () => {
this.reportStateToUI(conversation);
}));
}
protected setCurrentlySelected(conversation: ConversationType | undefined) {
if(this.currentSelectedConversation === conversation) {
return;
}
this.currentSelectedListener.forEach(callback => callback());
this.currentSelectedListener = [];
this.currentSelectedConversation = conversation;
this.uiEvents.fire_react("notify_selected_chat", { chatId: conversation ? conversation.getChatId() : "unselected" });
if(conversation) {
this.registerConversationEvents(conversation);
}
}
protected reportStateToUI(conversation: AbstractChat<any>) {
let historyState: ChatHistoryState;
const localHistoryState = this.conversationManager.historyUiStates[conversation.getChatId()];
if(!localHistoryState) {
historyState = conversation.hasHistory() ? "available" : "none";
} else {
if(Date.now() < localHistoryState.historyRetryTimestamp && localHistoryState.historyErrorMessage) {
historyState = "error";
} else if(localHistoryState.executingUIHistoryQuery) {
historyState = "loading";
} else if(conversation.hasHistory()) {
historyState = "available";
} else {
historyState = "none";
}
}
switch (conversation.getCurrentMode()) {
case "normal":
if(conversation.isPrivate() && !conversation.isReadable()) {
this.uiEvents.fire_react("notify_conversation_state", {
chatId: conversation.getChatId(),
state: "private",
crossChannelChatSupported: this.conversationManager.hasCrossConversationSupport()
});
return;
}
this.uiEvents.fire_react("notify_conversation_state", {
chatId: conversation.getChatId(),
state: "normal",
historyState: historyState,
historyErrorMessage: localHistoryState?.historyErrorMessage,
historyRetryTimestamp: localHistoryState ? localHistoryState.historyRetryTimestamp : 0,
chatFrameMaxMessageCount: kMaxChatFrameMessageSize,
unreadTimestamp: conversation.getUnreadTimestamp(),
showUserSwitchEvents: conversation.isPrivate() || !this.conversationManager.hasCrossConversationSupport(),
sendEnabled: conversation.isSendEnabled(),
events: [...conversation.getPresentEvents(), ...conversation.getPresentMessages()]
});
break;
case "loading":
case "unloaded":
this.uiEvents.fire_react("notify_conversation_state", {
chatId: conversation.getChatId(),
state: "loading"
});
break;
case "error":
this.uiEvents.fire_react("notify_conversation_state", {
chatId: conversation.getChatId(),
state: "error",
errorMessage: conversation.getErrorMessage()
});
break;
case "no-permissions":
this.uiEvents.fire_react("notify_conversation_state", {
chatId: conversation.getChatId(),
state: "no-permissions",
failedPermission: conversation.getFailedPermission()
});
break;
}
}
public uiQueryHistory(conversation: AbstractChat<any>, timestamp: number, enforce?: boolean) {
const localHistoryState = this.conversationManager.historyUiStates[conversation.getChatId()] || (this.conversationManager.historyUiStates[conversation.getChatId()] = {
executingUIHistoryQuery: false,
historyErrorMessage: undefined,
historyRetryTimestamp: 0
});
if(localHistoryState.executingUIHistoryQuery && !enforce) {
return;
}
localHistoryState.executingUIHistoryQuery = true;
conversation.queryHistory({ end: 1, begin: timestamp, limit: kMaxChatFrameMessageSize }).then(result => {
localHistoryState.executingUIHistoryQuery = false;
localHistoryState.historyErrorMessage = undefined;
localHistoryState.historyRetryTimestamp = result.nextAllowedQuery;
switch (result.status) {
case "success":
this.uiEvents.fire_react("notify_conversation_history", {
chatId: conversation.getChatId(),
state: "success",
hasMoreMessages: result.moreEvents,
retryTimestamp: localHistoryState.historyRetryTimestamp,
events: result.events
});
break;
case "private":
this.uiEvents.fire_react("notify_conversation_history", {
chatId: conversation.getChatId(),
state: "error",
errorMessage: localHistoryState.historyErrorMessage = tr("chat is private"),
retryTimestamp: localHistoryState.historyRetryTimestamp
});
break;
case "no-permission":
this.uiEvents.fire_react("notify_conversation_history", {
chatId: conversation.getChatId(),
state: "error",
errorMessage: localHistoryState.historyErrorMessage = tra("failed on {}", result.failedPermission || tr("unknown permission")),
retryTimestamp: localHistoryState.historyRetryTimestamp
});
break;
case "error":
this.uiEvents.fire_react("notify_conversation_history", {
chatId: conversation.getChatId(),
state: "error",
errorMessage: localHistoryState.historyErrorMessage = result.errorMessage,
retryTimestamp: localHistoryState.historyRetryTimestamp
});
break;
}
});
}
protected getCurrentConversation() : ConversationType | undefined {
return this.currentSelectedConversation;
}
@EventHandler<AbstractConversationUiEvents>("query_conversation_state")
protected handleQueryConversationState(event: AbstractConversationUiEvents["query_conversation_state"]) {
const conversation = this.conversationManager?.findConversationById(event.chatId);
if(!conversation) {
this.uiEvents.fire_react("notify_conversation_state", {
state: "error",
errorMessage: tr("Unknown conversation"),
chatId: event.chatId
});
return;
}
if(conversation.getCurrentMode() === "unloaded") {
/* will switch the state to "loading" and already reports the state to the ui */
conversation.queryCurrentMessages();
} else {
this.reportStateToUI(conversation);
}
}
@EventHandler<AbstractConversationUiEvents>("query_conversation_history")
protected handleQueryHistory(event: AbstractConversationUiEvents["query_conversation_history"]) {
const conversation = this.conversationManager?.findConversationById(event.chatId);
if(!conversation) {
this.uiEvents.fire_react("notify_conversation_history", {
state: "error",
errorMessage: tr("Unknown conversation"),
retryTimestamp: Date.now() + 10 * 1000,
chatId: event.chatId
});
logError(LogCategory.CLIENT, tr("Tried to query history for an unknown conversation with id %s"), event.chatId);
return;
}
this.uiQueryHistory(conversation, event.timestamp);
}
@EventHandler<AbstractConversationUiEvents>(["action_clear_unread_flag", "action_self_typing"])
protected handleClearUnreadFlag(event: AbstractConversationUiEvents["action_clear_unread_flag" | "action_self_typing"]) {
const conversation = this.conversationManager?.findConversationById(event.chatId);
conversation?.setUnreadTimestamp(Date.now());
}
@EventHandler<AbstractConversationUiEvents>("action_send_message")
protected handleSendMessage(event: AbstractConversationUiEvents["action_send_message"]) {
const conversation = this.conversationManager?.findConversationById(event.chatId);
if(!conversation) {
logError(LogCategory.CLIENT, tr("Tried to send a chat message to an unknown conversation with id %s"), event.chatId);
return;
}
conversation.sendMessage(event.text);
}
@EventHandler<AbstractConversationUiEvents>("action_jump_to_present")
protected handleJumpToPresent(event: AbstractConversationUiEvents["action_jump_to_present"]) {
const conversation = this.conversationManager?.findConversationById(event.chatId);
if(!conversation) {
logError(LogCategory.CLIENT, tr("Tried to jump to present for an unknown conversation with id %s"), event.chatId);
return;
}
this.reportStateToUI(conversation);
}
@EventHandler<AbstractConversationUiEvents>("query_selected_chat")
private handleQuerySelectedChat() {
this.uiEvents.fire_react("notify_selected_chat", { chatId: this.currentSelectedConversation ? this.currentSelectedConversation.getChatId() : "unselected"});
}
@EventHandler<AbstractConversationUiEvents>("action_select_chat")
private handleActionSelectChat(event: AbstractConversationUiEvents["action_select_chat"]) {
const conversation = this.conversationManager?.findConversationById(event.chatId);
this.conversationManager.setSelectedConversation(conversation);
}
}