add backend for message reactions
This commit is contained in:
+124
-7
@@ -1,11 +1,16 @@
|
||||
// this file is responsible for managing chats.
|
||||
|
||||
import { DATA_VERSION, ValidObject } from "../Utility/typeSafety";
|
||||
import {
|
||||
checkMatchesObjectStructure,
|
||||
DATA_VERSION,
|
||||
ValidObject,
|
||||
} from "../Utility/typeSafety";
|
||||
import FileModel, { FileContent } from "../Files/fileModel";
|
||||
import {
|
||||
HandlerManager,
|
||||
createTimestamp,
|
||||
localeCompare,
|
||||
parse,
|
||||
parseValidObject,
|
||||
stringify,
|
||||
} from "../Utility/utility";
|
||||
@@ -36,6 +41,8 @@ export default class ChatModel {
|
||||
|
||||
chatMessageHandlerManager: HandlerManager<ChatMessage> =
|
||||
new HandlerManager();
|
||||
reactionHandlerManager: HandlerManager<ChatMessageReaction> =
|
||||
new HandlerManager();
|
||||
|
||||
get secondaryChannels(): string[] {
|
||||
return this.info.secondaryChannels.sort(localeCompare);
|
||||
@@ -74,6 +81,17 @@ export default class ChatModel {
|
||||
return [...this.getMessageDirPath(), id];
|
||||
};
|
||||
|
||||
getReactionDirPath = (): string[] => {
|
||||
return StorageModel.getPath(
|
||||
StorageModelSubPath.Chat,
|
||||
filePaths.chat.reactions(this.id),
|
||||
);
|
||||
};
|
||||
|
||||
getReactionPath = (id: string): string[] => {
|
||||
return [...this.getReactionDirPath(), id];
|
||||
};
|
||||
|
||||
// handlers
|
||||
handleMessage = (body: string): void => {
|
||||
const chatMessage: ChatMessage | null = parseValidObject(
|
||||
@@ -83,11 +101,19 @@ export default class ChatModel {
|
||||
if (chatMessage == null) return;
|
||||
|
||||
chatMessage.status = ChatMessageStatus.Received;
|
||||
this.addMessage(chatMessage);
|
||||
|
||||
this.addMessage(chatMessage);
|
||||
this.setReadStatus(true);
|
||||
};
|
||||
|
||||
handleReaction = (reaction: ChatMessageReaction | any): void => {
|
||||
if (
|
||||
!checkMatchesObjectStructure(reaction, ChatMessageReactionReference)
|
||||
)
|
||||
return;
|
||||
this.addReaction(reaction);
|
||||
};
|
||||
|
||||
handleMessageSent = (chatMessage: ChatMessage): void => {
|
||||
chatMessage.status = ChatMessageStatus.Sent;
|
||||
this.addMessage(chatMessage);
|
||||
@@ -132,10 +158,13 @@ export default class ChatModel {
|
||||
);
|
||||
};
|
||||
|
||||
sendMessage = async (
|
||||
body: string,
|
||||
fileContent?: FileContent<string>,
|
||||
): Promise<boolean> => {
|
||||
addReaction = async (reaction: ChatMessageReaction): Promise<void> => {
|
||||
const reactionPath: string[] = this.getReactionPath(reaction.fileId);
|
||||
this.storageModel.writeStringifiable(reactionPath, reaction);
|
||||
this.reactionHandlerManager.trigger(reaction);
|
||||
};
|
||||
|
||||
getNameAndChannel = (): [string, string] | false => {
|
||||
const senderName = this.settingsModel.username;
|
||||
if (senderName == "") return false;
|
||||
|
||||
@@ -146,6 +175,17 @@ export default class ChatModel {
|
||||
|
||||
const combinedChannel: string = allChannels.join("/");
|
||||
|
||||
return [senderName, combinedChannel];
|
||||
};
|
||||
|
||||
sendMessage = async (
|
||||
body: string,
|
||||
fileContent?: FileContent<string>,
|
||||
): Promise<boolean> => {
|
||||
const nameAndChannel = this.getNameAndChannel();
|
||||
if (nameAndChannel == false) return;
|
||||
const [senderName, combinedChannel] = nameAndChannel;
|
||||
|
||||
const chatMessage: ChatMessage = await ChatModel.createChatMessage(
|
||||
combinedChannel,
|
||||
senderName,
|
||||
@@ -172,6 +212,23 @@ export default class ChatModel {
|
||||
chatMessage.stringifiedFile = decryptedFile;
|
||||
};
|
||||
|
||||
sendReaction = async (
|
||||
messageId: string,
|
||||
content: ReactionSymbol,
|
||||
): Promise<void> => {
|
||||
const nameAndChannel = this.getNameAndChannel();
|
||||
if (nameAndChannel == false) return;
|
||||
const [senderName, combinedChannel] = nameAndChannel;
|
||||
|
||||
const reaction = ChatModel.createMessageReaction(
|
||||
messageId,
|
||||
senderName,
|
||||
content,
|
||||
);
|
||||
this.sendMessage("", reaction);
|
||||
this.addReaction(reaction);
|
||||
};
|
||||
|
||||
subscribe = (): void => {
|
||||
this.connectionModel.addChannel(this.info.primaryChannel);
|
||||
};
|
||||
@@ -231,11 +288,12 @@ export default class ChatModel {
|
||||
const chatMessages: ChatMessage[] = [];
|
||||
for (const messageId of messageIds) {
|
||||
const messagePath: string[] = this.getMessagePath(messageId);
|
||||
const chatMessage: ChatMessage | any =
|
||||
const chatMessage: ChatMessage | null =
|
||||
this.storageModel.readStringifiable(
|
||||
messagePath,
|
||||
ChatMessageReference,
|
||||
);
|
||||
if (chatMessage == null) continue;
|
||||
chatMessages.push(chatMessage);
|
||||
}
|
||||
|
||||
@@ -245,6 +303,27 @@ export default class ChatModel {
|
||||
return sorted;
|
||||
}
|
||||
|
||||
get reactions(): ChatMessageReaction[] {
|
||||
const reactionIds: string[] = this.storageModel.list(
|
||||
this.getReactionDirPath(),
|
||||
);
|
||||
if (!Array.isArray(reactionIds)) return [];
|
||||
|
||||
const reactions: ChatMessageReaction[] = [];
|
||||
for (const reactionId of reactionIds) {
|
||||
const reactionPath: string[] = this.getReactionPath(reactionId);
|
||||
const reaction: ChatMessageReaction | null =
|
||||
this.storageModel.readStringifiable(
|
||||
reactionPath,
|
||||
ChatMessageReactionReference,
|
||||
);
|
||||
if (reaction == null) continue;
|
||||
reactions.push(reaction);
|
||||
}
|
||||
|
||||
return reactions;
|
||||
}
|
||||
|
||||
// init
|
||||
constructor(
|
||||
storageModel: StorageModel,
|
||||
@@ -324,9 +403,27 @@ export default class ChatModel {
|
||||
|
||||
return chatMessage;
|
||||
};
|
||||
|
||||
static createMessageReaction = (
|
||||
messageId: string,
|
||||
sender: string,
|
||||
content: ReactionSymbol,
|
||||
): ChatMessageReaction => {
|
||||
const fileContent = FileModel.createFileContent(v4(), "reaction");
|
||||
const reaction: ChatMessageReaction = {
|
||||
...fileContent,
|
||||
|
||||
messageId,
|
||||
sender,
|
||||
content,
|
||||
};
|
||||
return reaction;
|
||||
};
|
||||
}
|
||||
|
||||
// types
|
||||
export type ReactionSymbol = "👍" | "✅" | "❗️" | "‼️" | "❓";
|
||||
|
||||
export interface ChatInfo extends ValidObject {
|
||||
primaryChannel: string;
|
||||
secondaryChannels: string[];
|
||||
@@ -355,6 +452,13 @@ export interface ChatMessage extends ValidObject {
|
||||
stringifiedFile: string;
|
||||
}
|
||||
|
||||
export interface ChatMessageReaction
|
||||
extends ValidObject, FileContent<"reaction"> {
|
||||
messageId: string;
|
||||
sender: string;
|
||||
content: ReactionSymbol | string;
|
||||
}
|
||||
|
||||
// references
|
||||
export const ChatInfoReference: ChatInfo = {
|
||||
dataVersion: DATA_VERSION,
|
||||
@@ -380,3 +484,16 @@ export const ChatMessageReference: ChatMessage = {
|
||||
|
||||
stringifiedFile: "",
|
||||
};
|
||||
|
||||
export const ChatMessageReactionReference: ChatMessageReaction = {
|
||||
dataVersion: DATA_VERSION,
|
||||
|
||||
fileId: "",
|
||||
fileContentId: "",
|
||||
creationDate: "",
|
||||
type: "reaction",
|
||||
|
||||
messageId: "",
|
||||
sender: "",
|
||||
content: "",
|
||||
};
|
||||
|
||||
@@ -65,6 +65,7 @@ export default class FileModel {
|
||||
if (didStore == false) return;
|
||||
|
||||
this.boardsAndTasksModel.handleFileContent(fileContent);
|
||||
this.chatModel.handleReaction(fileContent);
|
||||
};
|
||||
|
||||
// methods
|
||||
|
||||
@@ -247,6 +247,10 @@ export const filePaths = {
|
||||
info: (id: string) => [...filePaths.chat.chatBase(id), "info"],
|
||||
color: (id: string) => [...filePaths.chat.chatBase(id), "color"],
|
||||
messages: (id: string) => [...filePaths.chat.chatBase(id), "messages"],
|
||||
reactions: (id: string) => [
|
||||
...filePaths.chat.chatBase(id),
|
||||
"reactions",
|
||||
],
|
||||
lastUsedPage: (id: string) => [
|
||||
...filePaths.chat.chatBase(id),
|
||||
"last-used-page",
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import * as React from "bloatless-react";
|
||||
|
||||
import { ChatMessage, ChatMessageStatus } from "../../Model/Chat/chatModel";
|
||||
import {
|
||||
ChatMessage,
|
||||
ChatMessageReaction,
|
||||
ChatMessageReactionReference,
|
||||
ChatMessageStatus,
|
||||
ReactionSymbol,
|
||||
} from "../../Model/Chat/chatModel";
|
||||
|
||||
import CoreViewModel from "../Global/coreViewModel";
|
||||
import MessagePageViewModel from "../Pages/messagePageViewModel";
|
||||
@@ -19,6 +25,12 @@ export default class ChatMessageViewModel {
|
||||
);
|
||||
sentByUser: boolean;
|
||||
|
||||
reactionsThumbsUp = new React.MapState<ChatMessageReaction>();
|
||||
reactionsCheck = new React.MapState<ChatMessageReaction>();
|
||||
reactionsAttention = new React.MapState<ChatMessageReaction>();
|
||||
reactionsDoubleAttention = new React.MapState<ChatMessageReaction>();
|
||||
reactionsQuestion = new React.MapState<ChatMessageReaction>();
|
||||
|
||||
// state
|
||||
isPresentingInfoModal: React.State<boolean> = new React.State(false);
|
||||
|
||||
@@ -42,6 +54,25 @@ export default class ChatMessageViewModel {
|
||||
this.isPresentingInfoModal.value = false;
|
||||
};
|
||||
|
||||
// reactions
|
||||
addReaction = (reaction: ChatMessageReaction): void => {
|
||||
switch (reaction.content) {
|
||||
case "👍":
|
||||
return this.reactionsThumbsUp.set(reaction.fileId, reaction);
|
||||
case "✅":
|
||||
return this.reactionsCheck.set(reaction.fileId, reaction);
|
||||
case "❗️":
|
||||
return this.reactionsAttention.set(reaction.fileId, reaction);
|
||||
case "‼️":
|
||||
return this.reactionsDoubleAttention.set(
|
||||
reaction.fileId,
|
||||
reaction,
|
||||
);
|
||||
case "❓":
|
||||
return this.reactionsQuestion.set(reaction.fileId, reaction);
|
||||
}
|
||||
};
|
||||
|
||||
// load
|
||||
loadData = (): void => {
|
||||
this.channel = this.chatMessage.channel;
|
||||
|
||||
@@ -3,7 +3,10 @@ import * as React from "bloatless-react";
|
||||
import CalendarPageViewModel, {
|
||||
CALENDAR_EVENT_BOARD_ID,
|
||||
} from "../Pages/calendarPageViewModel";
|
||||
import ChatModel, { ChatMessage } from "../../Model/Chat/chatModel";
|
||||
import ChatModel, {
|
||||
ChatMessage,
|
||||
ChatMessageReaction,
|
||||
} from "../../Model/Chat/chatModel";
|
||||
import StorageModel, {
|
||||
StorageModelSubPath,
|
||||
filePaths,
|
||||
@@ -176,6 +179,11 @@ export default class ChatViewModel {
|
||||
this.notificationViewModel.showNotification(chatMessage);
|
||||
},
|
||||
);
|
||||
chatModel.reactionHandlerManager.addHandler(
|
||||
(reaction: ChatMessageReaction) => {
|
||||
this.messagePageViewModel.showReaction(reaction);
|
||||
},
|
||||
);
|
||||
|
||||
// load
|
||||
this.loadPageSelection();
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import * as React from "bloatless-react";
|
||||
|
||||
import { ChatMessage } from "../../Model/Chat/chatModel";
|
||||
import {
|
||||
ChatMessage,
|
||||
ChatMessageReaction,
|
||||
ReactionSymbol,
|
||||
} from "../../Model/Chat/chatModel";
|
||||
import ChatMessageViewModel from "../Chat/chatMessageViewModel";
|
||||
import ChatViewModel from "../Chat/chatViewModel";
|
||||
import CoreViewModel from "../Global/coreViewModel";
|
||||
@@ -37,6 +41,10 @@ export default class MessagePageViewModel {
|
||||
messageViewModel.loadData();
|
||||
};
|
||||
|
||||
sendReaction = (messageId: string, content: ReactionSymbol): void => {
|
||||
this.chatViewModel.chatModel.sendReaction(messageId, content);
|
||||
};
|
||||
|
||||
// view
|
||||
showChatMessage = (chatMessage: ChatMessage): void => {
|
||||
const chatMessageViewModel = new ChatMessageViewModel(
|
||||
@@ -60,12 +68,22 @@ export default class MessagePageViewModel {
|
||||
}
|
||||
};
|
||||
|
||||
showReaction = (reaction: ChatMessageReaction): void => {
|
||||
const messageViewModel: ChatMessageViewModel | undefined =
|
||||
this.chatMessageViewModels.value.get(reaction.messageId);
|
||||
if (messageViewModel == undefined) return;
|
||||
messageViewModel.addReaction(reaction);
|
||||
};
|
||||
|
||||
// load
|
||||
loadData = (): void => {
|
||||
this.chatMessageViewModels.clear();
|
||||
for (const chatMessage of this.chatViewModel.chatModel.messages) {
|
||||
this.showChatMessage(chatMessage);
|
||||
}
|
||||
for (const reaction of this.chatViewModel.chatModel.reactions) {
|
||||
this.showReaction(reaction);
|
||||
}
|
||||
};
|
||||
|
||||
// init
|
||||
|
||||
Reference in New Issue
Block a user