Browse Source

Display message timestamps.

main
Titouan Rigoudy 4 years ago
parent
commit
d11fe88b58
3 changed files with 58 additions and 42 deletions
  1. +10
    -10
      src/modules/room/RoomChatMessageList.tsx
  2. +19
    -4
      src/modules/room/message.ts
  3. +29
    -28
      src/modules/room/slice.ts

+ 10
- 10
src/modules/room/RoomChatMessageList.tsx View File

@ -9,31 +9,31 @@ interface Props {
const messageShape = "px-3 py-1 rounded-xl"; const messageShape = "px-3 py-1 rounded-xl";
function boolToString(b: boolean): string {
return b ? "true" : "false";
}
const RoomChatMessageList: FC<Props> = ({ loginUserName, messages }) => { const RoomChatMessageList: FC<Props> = ({ loginUserName, messages }) => {
// Append all messages in the chat room. // Append all messages in the chat room.
const children = []; const children = [];
let i = 0; let i = 0;
for (const { userName, message, acked } of messages) {
const ackedString = boolToString(acked);
for (const { userName, message, receivedAt } of messages) {
let time = "";
if (receivedAt !== null) {
time = receivedAt.toLocaleTimeString();
}
if (userName === loginUserName) { if (userName === loginUserName) {
children.push( children.push(
<li key={i} className="self-end py-1 max-w-3/4"> <li key={i} className="self-end py-1 max-w-3/4">
{time}
<div className={`bg-yellow-600 text-white ${messageShape}`}> <div className={`bg-yellow-600 text-white ${messageShape}`}>
{message}, acked: {ackedString}
{message}
</div> </div>
</li> </li>
); );
} else { } else {
children.push( children.push(
<li key={i} className="self-start py-1 max-w-3/4"> <li key={i} className="self-start py-1 max-w-3/4">
{time}
<div className="text-sm text-yellow-600 px-1 py-1">{userName}</div> <div className="text-sm text-yellow-600 px-1 py-1">{userName}</div>
<div className={`bg-yellow-100 ${messageShape}`}>
{message}, acked: {ackedString}
</div>
<div className={`bg-yellow-100 ${messageShape}`}>{message}</div>
</li> </li>
); );
} }


+ 19
- 4
src/modules/room/message.ts View File

@ -2,7 +2,7 @@ import { AppDispatch } from "app/store";
import { import {
RoomMembership, RoomMembership,
RoomMessage, RoomMessage,
RoomMessagePayload,
RoomSendMessagePayload,
RoomState, RoomState,
roomGetAll, roomGetAll,
roomReceiveMessage, roomReceiveMessage,
@ -32,12 +32,24 @@ function convertMembership(membership: string): RoomMembership {
} }
} }
interface SystemTime {
secs_since_epoch: number;
nanos_since_epoch: number;
}
function convertDate(time: SystemTime): Date {
return new Date(
time.secs_since_epoch * 1000 + time.nanos_since_epoch / 1000000
);
}
function convertMessages(messages: any[]): RoomMessage[] { function convertMessages(messages: any[]): RoomMessage[] {
const result = []; const result = [];
for (const message of messages) { for (const message of messages) {
result.push({ result.push({
userName: message.user_name, userName: message.user_name,
message: message.message, message: message.message,
receivedAt: convertDate(message.received_at),
acked: true, acked: true,
}); });
} }
@ -78,8 +90,11 @@ function handleRoomMessageResponse(dispatch: AppDispatch, response: any): void {
dispatch( dispatch(
roomReceiveMessage({ roomReceiveMessage({
roomName: response.room_name, roomName: response.room_name,
userName: response.user_name,
message: response.message,
message: {
userName: response.message.user_name,
message: response.message.message,
receivedAt: convertDate(response.message.received_at),
},
}) })
); );
} }
@ -97,7 +112,7 @@ function roomListRequest(): SocketMessage {
return "RoomListRequest"; return "RoomListRequest";
} }
function roomMessageRequest({ roomName, message }: RoomMessagePayload) {
function roomMessageRequest({ roomName, message }: RoomSendMessagePayload) {
return { return {
RoomMessageRequest: { RoomMessageRequest: {
room_name: roomName, room_name: roomName,


+ 29
- 28
src/modules/room/slice.ts View File

@ -20,31 +20,32 @@ export interface RoomMessage {
// The message contents. // The message contents.
message: string; message: string;
// Whether the server has acknowledged the message in a response.
// Only ever false for messages we sent.
acked: boolean;
// TODO: timestamp.
// The time at which this message was received. `null` if unacked.
receivedAt: Date | null;
} }
// Attempts to ack `message` upon receival of `messageContents` from `userName`.
// Attempts to ack `existing` upon receival of `received`.
// //
// Returns true if `message` was unacked, was sent by `userName` and contains
// `messageContents`, in which case `message.acked` is set to `true`.
// Returns true if `existing` was unacked and matched `received`, in which case
// `existing.receivedAt` is set to `received.receivedAt`.
// Returns false otherwise. // Returns false otherwise.
function maybeAckMessage( function maybeAckMessage(
message: RoomMessage,
userName: string,
messageContents: string
existing: RoomMessage,
received: RoomMessage
): boolean { ): boolean {
if (message.acked) {
if (existing.receivedAt !== null) {
return false; // Already acked. return false; // Already acked.
} }
if (message.userName !== userName || message.message !== messageContents) {
if (existing.userName !== received.userName) {
return false; // Wrong user.
}
if (existing.message !== received.message) {
return false; // Wrong message. return false; // Wrong message.
} }
message.acked = true;
existing.receivedAt = received.receivedAt;
return true; return true;
} }
@ -95,8 +96,12 @@ const initialState: RoomSliceState = {
rooms: {}, rooms: {},
}; };
// The payload of an action to send a message to a chat room.
export interface RoomMessagePayload {
export interface RoomReceiveMessagePayload {
roomName: string;
message: RoomMessage;
}
export interface RoomSendMessagePayload {
roomName: string; roomName: string;
userName: string; userName: string;
message: string; message: string;
@ -125,16 +130,12 @@ export const roomSlice = createSlice({
}, },
roomReceiveMessage: ( roomReceiveMessage: (
state: RoomSliceState, state: RoomSliceState,
action: PayloadAction<RoomMessagePayload>
action: PayloadAction<RoomReceiveMessagePayload>
) => { ) => {
const { roomName, userName } = action.payload;
const messageContents = action.payload.message;
const { roomName, message } = action.payload;
const room = state.rooms[roomName]; const room = state.rooms[roomName];
if (room === undefined) { if (room === undefined) {
console.log(
`Unknown room ${roomName} received message from ` +
`${userName}: ${messageContents}`
);
console.log(`Unknown room ${roomName} received message:`, message);
return; return;
} }
@ -145,17 +146,17 @@ export const roomSlice = createSlice({
// - using a map of unacked contents to messages // - using a map of unacked contents to messages
// //
for (let i = room.messages.length - 1; i >= 0; i--) { for (let i = room.messages.length - 1; i >= 0; i--) {
const message = room.messages[i];
if (maybeAckMessage(message, userName, messageContents)) {
const existing = room.messages[i];
if (maybeAckMessage(existing, message)) {
return; return;
} }
} }
room.messages.push({ userName, message: messageContents, acked: true });
room.messages.push(message);
}, },
roomSendMessage: ( roomSendMessage: (
state: RoomSliceState, state: RoomSliceState,
action: PayloadAction<RoomMessagePayload>
action: PayloadAction<RoomSendMessagePayload>
) => { ) => {
const { roomName, userName, message } = action.payload; const { roomName, userName, message } = action.payload;
const room = state.rooms[roomName]; const room = state.rooms[roomName];
@ -166,7 +167,7 @@ export const roomSlice = createSlice({
return; return;
} }
room.messages.push({ userName, message, acked: false });
room.messages.push({ userName, message, receivedAt: null });
}, },
roomSetAll: (state: RoomSliceState, action: PayloadAction<RoomState[]>) => { roomSetAll: (state: RoomSliceState, action: PayloadAction<RoomState[]>) => {
state.rooms = {}; state.rooms = {};


Loading…
Cancel
Save