conversationSlice
This commit is contained in:
parent
f0755936fc
commit
6888a0a7bc
@ -7,17 +7,21 @@ type ListElementProps = {
|
||||
element: Element;
|
||||
outerDivStyleClass: string;
|
||||
children?: ReactNode;
|
||||
unreadCount?: number;
|
||||
};
|
||||
|
||||
const ListElement = ({
|
||||
element,
|
||||
outerDivStyleClass,
|
||||
children,
|
||||
unreadCount,
|
||||
}: ListElementProps) => {
|
||||
const renderElement = () => {
|
||||
switch (element.type) {
|
||||
case 'request-approval':
|
||||
return <RequestApprovalElement element={element} />;
|
||||
return (
|
||||
<RequestApprovalElement element={element} unreadCount={unreadCount} />
|
||||
);
|
||||
case 'missile-incoming':
|
||||
return <MissileIncomingElement element={element} />;
|
||||
default:
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { MissileIncomingElement as MissileIncomingElementType } from 'src/types/element';
|
||||
import IconElement from 'src/components/Element/Simple/IconElement';
|
||||
import { useAppSelector } from 'src/redux/hooks';
|
||||
import { getMessage } from 'src/redux/slices/minimapSlice';
|
||||
import { getMessage } from 'src/redux/slices/conversationSlice';
|
||||
|
||||
type MissileIncomingProps = {
|
||||
element: MissileIncomingElementType;
|
||||
|
@ -1,20 +1,21 @@
|
||||
import { useEffect, type ReactNode } from 'react';
|
||||
import { type RequestApprovalElement as RequestApprovalElementType } from 'src/types/element';
|
||||
import ButtonElement from '../Simple/ButtonElement';
|
||||
import IconElement from '../Simple/IconElement';
|
||||
import { useAppSelector } from 'src/redux/hooks';
|
||||
import { getMessage } from 'src/redux/slices/minimapSlice';
|
||||
import { getMessage } from 'src/redux/slices/conversationSlice';
|
||||
|
||||
type RequestApprovalProps = {
|
||||
element: RequestApprovalElementType;
|
||||
children?: ReactNode;
|
||||
unreadCount?: number;
|
||||
};
|
||||
|
||||
const RequestApprovalElement = ({
|
||||
element,
|
||||
children,
|
||||
unreadCount,
|
||||
}: RequestApprovalProps) => {
|
||||
const { id, collapsed, icon, rightButton, leftButton, messageId } = element;
|
||||
const { id, icon, messageId } = element;
|
||||
const message = useAppSelector((state) => getMessage(state, messageId));
|
||||
|
||||
return (
|
||||
@ -22,6 +23,8 @@ const RequestApprovalElement = ({
|
||||
<IconElement element={icon} />
|
||||
{/* @ts-ignore */}
|
||||
<span>{message?.data?.target?.type}</span>
|
||||
|
||||
{unreadCount && unreadCount > 0 && <span>{unreadCount}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { useAppSelector } from 'src/redux/hooks';
|
||||
import { type HistoryWidget as HistoryWidgetType } from 'src/types/widget';
|
||||
import HistoryElement from 'src/components/Element/Complex/HistoryElement';
|
||||
import { getConversationMessages } from 'src/redux/slices/minimapSlice';
|
||||
import { getConversation } from 'src/redux/slices/conversationSlice';
|
||||
import { getCommunication } from 'src/redux/slices/communicationSlice';
|
||||
import MessageNumber from 'src/ui/history/MessageNumber';
|
||||
|
||||
@ -13,10 +13,11 @@ const HistoryWidget = ({ widget }: HistoryWidgetProps) => {
|
||||
const { id, x, y, w, h, elements } = widget;
|
||||
const { activeConversationId } = useAppSelector(getCommunication);
|
||||
|
||||
const convoMessages = useAppSelector((state) =>
|
||||
getConversationMessages(state, activeConversationId),
|
||||
const conversation = useAppSelector((state) =>
|
||||
getConversation(state, activeConversationId),
|
||||
);
|
||||
const numMessages = convoMessages.length;
|
||||
const messages = conversation?.messages ? [...conversation.messages] : [];
|
||||
const numMessages = messages.length || 0;
|
||||
|
||||
// const activeElementID = useAppSelector(getSelectedElementID);
|
||||
// const highlightIndex = convoMessages.findIndex(
|
||||
@ -27,7 +28,7 @@ const HistoryWidget = ({ widget }: HistoryWidgetProps) => {
|
||||
if (numMessages) {
|
||||
return (
|
||||
<div className="grid grid-cols-12 items-start justify-start p-4 gap-4">
|
||||
{convoMessages.reverse().map((message, index) => (
|
||||
{messages.reverse().map((message, index) => (
|
||||
<>
|
||||
<div key={message.id} className="col-span-1 flex flex-col h-full">
|
||||
<MessageNumber number={numMessages - index} glow />
|
||||
|
@ -7,7 +7,10 @@ import {
|
||||
import { getElementsInGaze, getGazesAndKeys } from 'src/redux/slices/gazeSlice';
|
||||
import type { Widget } from 'src/types/widget';
|
||||
import ListElement from 'src/components/Element/Complex/ListElement';
|
||||
import { getMessages } from 'src/redux/slices/minimapSlice';
|
||||
import {
|
||||
getConversations,
|
||||
getMessages,
|
||||
} from 'src/redux/slices/conversationSlice';
|
||||
|
||||
type ListWidgetProps = {
|
||||
widget: Widget;
|
||||
@ -17,7 +20,8 @@ const LIST_ELEMENT_HEIGHT = 80;
|
||||
const GAP_BETWEEN_ELEMENTS = 6;
|
||||
|
||||
const ListWidget = ({ widget }: ListWidgetProps) => {
|
||||
const messages = useAppSelector(getMessages);
|
||||
const conversations = useAppSelector(getConversations);
|
||||
|
||||
const elementsInGaze = useAppSelector(getElementsInGaze);
|
||||
const gazesAndKeys = useAppSelector(getGazesAndKeys);
|
||||
const { activeElementId } = useAppSelector(getCommunication);
|
||||
@ -87,13 +91,13 @@ const ListWidget = ({ widget }: ListWidgetProps) => {
|
||||
dispatch(
|
||||
updateCommunication({
|
||||
// @ts-ignore
|
||||
activeConversationId: messages[element.messageId].conversationId,
|
||||
activeConversationId: element.conversationId,
|
||||
activeElementId: element.id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
}, [gazesAndKeys, dispatch, elementInGazeId, messages, widget.elements]);
|
||||
}, [gazesAndKeys, dispatch, elementInGazeId, widget.elements]);
|
||||
|
||||
// check if the list is overflowed
|
||||
// useEffect(() => {
|
||||
@ -148,10 +152,15 @@ const ListWidget = ({ widget }: ListWidgetProps) => {
|
||||
// don't render element if it's message is not the latest in the conversation
|
||||
// @ts-ignore
|
||||
const elemMessageId = element.messageId;
|
||||
if (elemMessageId) {
|
||||
if (!messages[elemMessageId].latestInConvo) {
|
||||
return null;
|
||||
}
|
||||
// @ts-ignore
|
||||
const elemConvoId = element.conversationId;
|
||||
if (
|
||||
elemMessageId &&
|
||||
elemConvoId &&
|
||||
conversations[elemConvoId] &&
|
||||
conversations[elemConvoId].latestMessageId !== elemMessageId
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// style for the element which is current being hoverd over
|
||||
|
@ -19,7 +19,7 @@ const useWorldSim = () => {
|
||||
if (message)
|
||||
setMessages((prevMessages) => [
|
||||
...prevMessages,
|
||||
{ ...message, fulfilled: false, read: false, latestInConvo: true }, // newest message is latest in convo
|
||||
{ ...message, fulfilled: false, read: false }, // newest message is latest in convo
|
||||
]);
|
||||
if (stressLevel) setStressLevel(stressLevel);
|
||||
});
|
||||
|
@ -5,10 +5,10 @@ import {
|
||||
type InitialMinimapState,
|
||||
getWidgets,
|
||||
initializeState,
|
||||
addMessage,
|
||||
getStressLevel,
|
||||
setStressLevel,
|
||||
} from 'src/redux/slices/minimapSlice';
|
||||
import { addMessage } from 'src/redux/slices/conversationSlice';
|
||||
// ~~~~~~~ Cusdom Hooks ~~~~~~~
|
||||
import useWorldSim from 'src/hooks/useWorldSim';
|
||||
// ~~~~~~~ Prototype ~~~~~~~
|
||||
|
@ -35,6 +35,7 @@ const requestApprovalToAttackMessageHigh = (
|
||||
'list',
|
||||
),
|
||||
message.id,
|
||||
message.conversationId,
|
||||
listWidgetId,
|
||||
lpdHelper.generateIconElement(
|
||||
lpdHelper.generateBaseElement(uuid(), 'visual', 56, 56),
|
||||
@ -182,6 +183,7 @@ const missileToOwnshipDetectedMessageHigh = (
|
||||
'list',
|
||||
),
|
||||
message.id,
|
||||
message.conversationId,
|
||||
listWidgetId,
|
||||
lpdHelper.generateIconElement(
|
||||
lpdHelper.generateBaseElement(uuid(), 'visual', 56, 56),
|
||||
|
@ -28,6 +28,7 @@ const requestApprovalToAttackMessageLow = (
|
||||
lpdHelper.generateRequestApprovalElement(
|
||||
lpdHelper.generateBaseElement(uuid(), 'visual', 30, 30, message.priority),
|
||||
message.id,
|
||||
message.conversationId,
|
||||
listWidgetId,
|
||||
lpdHelper.generateIconElement(
|
||||
lpdHelper.generateBaseElement(uuid(), 'visual', 56, 56),
|
||||
@ -224,6 +225,7 @@ const missileToOwnshipDetectedMessageLow = (
|
||||
message.priority,
|
||||
),
|
||||
message.id,
|
||||
message.conversationId,
|
||||
listWidgetId,
|
||||
lpdHelper.generateIconElement(
|
||||
lpdHelper.generateBaseElement(uuid(), 'visual', 56, 56),
|
||||
|
@ -28,6 +28,7 @@ const requestApprovalToAttackMessageMedium = (
|
||||
lpdHelper.generateRequestApprovalElement(
|
||||
lpdHelper.generateBaseElement(uuid(), 'visual', 30, 30, message.priority),
|
||||
message.id,
|
||||
message.conversationId,
|
||||
listWidgetId,
|
||||
lpdHelper.generateIconElement(
|
||||
lpdHelper.generateBaseElement(uuid(), 'visual', 56, 56),
|
||||
@ -174,6 +175,7 @@ const missileToOwnshipDetectedMessageMedium = (
|
||||
message.priority,
|
||||
),
|
||||
message.id,
|
||||
message.conversationId,
|
||||
listWidgetId,
|
||||
lpdHelper.generateIconElement(
|
||||
lpdHelper.generateBaseElement(uuid(), 'visual', 56, 56),
|
||||
|
88
src/redux/slices/conversationSlice.ts
Normal file
88
src/redux/slices/conversationSlice.ts
Normal file
@ -0,0 +1,88 @@
|
||||
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
|
||||
import type { Message } from 'src/types/schema-types';
|
||||
|
||||
type InitialState = {
|
||||
conversations: {
|
||||
[key: string]: {
|
||||
id: string;
|
||||
messages: Message[];
|
||||
numUnreadMessages: number;
|
||||
latestMessageId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const initialState: InitialState = {
|
||||
conversations: {},
|
||||
};
|
||||
|
||||
export const conversationSlice = createSlice({
|
||||
name: 'conversation',
|
||||
initialState,
|
||||
reducers: {
|
||||
addMessage: (state, action: PayloadAction<Message>) => {
|
||||
const message = action.payload;
|
||||
const { conversationId } = message;
|
||||
|
||||
if (!state.conversations[conversationId]) {
|
||||
state.conversations[conversationId] = {
|
||||
id: conversationId,
|
||||
messages: [message],
|
||||
numUnreadMessages: 1,
|
||||
latestMessageId: message.id,
|
||||
};
|
||||
} else {
|
||||
state.conversations[conversationId].messages.push(message);
|
||||
state.conversations[conversationId].numUnreadMessages++;
|
||||
state.conversations[conversationId].latestMessageId = message.id;
|
||||
}
|
||||
},
|
||||
},
|
||||
selectors: {
|
||||
getMessages: (state) => {
|
||||
const messages: Message[] = [];
|
||||
for (const conversationId in state.conversations) {
|
||||
const conversation = state.conversations[conversationId];
|
||||
messages.push(...conversation.messages);
|
||||
}
|
||||
|
||||
return messages;
|
||||
},
|
||||
getMessage: (state, messageId: string) => {
|
||||
for (const conversationId in state.conversations) {
|
||||
const conversation = state.conversations[conversationId];
|
||||
const message = conversation.messages.find(
|
||||
(message) => message.id === messageId,
|
||||
);
|
||||
|
||||
if (message) return message;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
getConversationOfMessage: (state, messageId: string) => {
|
||||
for (const conversationId in state.conversations) {
|
||||
const conversation = state.conversations[conversationId];
|
||||
const message = conversation.messages.find(
|
||||
(message) => message.id === messageId,
|
||||
);
|
||||
|
||||
if (message) return conversation;
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getConversations: (state) => state.conversations,
|
||||
getConversation: (state, conversationId: string) =>
|
||||
state.conversations[conversationId],
|
||||
},
|
||||
});
|
||||
|
||||
export const { addMessage } = conversationSlice.actions;
|
||||
|
||||
export const {
|
||||
getConversations,
|
||||
getConversation,
|
||||
getConversationOfMessage,
|
||||
getMessage,
|
||||
getMessages,
|
||||
} = conversationSlice.selectors;
|
@ -15,7 +15,6 @@ export type InitialMinimapState = {
|
||||
drones: VehicleWidget[];
|
||||
|
||||
widgets: WidgetMap;
|
||||
messages: MessageMap;
|
||||
sections: Section[];
|
||||
|
||||
stressLevel: number;
|
||||
@ -26,7 +25,6 @@ const initialState: InitialMinimapState = {
|
||||
audioComplexity: 0,
|
||||
ownship: null,
|
||||
drones: [],
|
||||
messages: {},
|
||||
widgets: {},
|
||||
sections: [],
|
||||
stressLevel: 0,
|
||||
@ -48,7 +46,6 @@ export const minimapSlice = createSlice({
|
||||
state.ownship = action.payload.ownship;
|
||||
state.drones = action.payload.drones;
|
||||
state.widgets = action.payload.widgets;
|
||||
state.messages = action.payload.messages;
|
||||
state.sections = action.payload.sections;
|
||||
},
|
||||
|
||||
@ -396,22 +393,6 @@ export const minimapSlice = createSlice({
|
||||
state.audioComplexity = action.payload;
|
||||
},
|
||||
|
||||
addMessage: (state, action: PayloadAction<Message>) => {
|
||||
const updatedMessages = { ...state.messages };
|
||||
|
||||
// set latestInConvo to false for all messages in the same conversation
|
||||
Object.keys(updatedMessages).forEach((id) => {
|
||||
if (
|
||||
updatedMessages[id].conversationId === action.payload.conversationId
|
||||
) {
|
||||
updatedMessages[id].latestInConvo = false;
|
||||
}
|
||||
});
|
||||
|
||||
updatedMessages[action.payload.id] = action.payload;
|
||||
state.messages = updatedMessages;
|
||||
},
|
||||
|
||||
setStressLevel: (state, action: PayloadAction<number>) => {
|
||||
state.stressLevel = action.payload;
|
||||
},
|
||||
@ -460,18 +441,6 @@ export const minimapSlice = createSlice({
|
||||
|
||||
getVisualComplexity: (state) => state.visualComplexity,
|
||||
getAudioComplexity: (state) => state.audioComplexity,
|
||||
getMessages: (state) => state.messages,
|
||||
getMessage: (state, messageId: string) => state.messages[messageId],
|
||||
getConversationMessages: (state, conversationId: string) => {
|
||||
const messages: Message[] = [];
|
||||
Object.keys(state.messages).forEach((messageId) => {
|
||||
if (state.messages[messageId].conversationId === conversationId) {
|
||||
messages.push(state.messages[messageId]);
|
||||
}
|
||||
});
|
||||
|
||||
return messages;
|
||||
},
|
||||
getStressLevel: (state) => state.stressLevel,
|
||||
|
||||
// ~~~~~ selectors for ships ~~~~~
|
||||
@ -495,7 +464,6 @@ export const {
|
||||
initializeState,
|
||||
|
||||
addMapSection,
|
||||
addMessage,
|
||||
addWidget,
|
||||
addHandledMessageToWidget,
|
||||
addElementsToWidget,
|
||||
@ -528,10 +496,6 @@ export const {
|
||||
getAllElements,
|
||||
getElementsOnScreen,
|
||||
|
||||
getMessages,
|
||||
getMessage,
|
||||
getConversationMessages,
|
||||
|
||||
getOwnship,
|
||||
getDrones,
|
||||
|
||||
|
@ -8,11 +8,17 @@ import {
|
||||
import { minimapSlice } from './slices/minimapSlice';
|
||||
import { gazeSlice } from './slices/gazeSlice';
|
||||
import { communicationSlice } from './slices/communicationSlice';
|
||||
import { conversationSlice } from './slices/conversationSlice';
|
||||
|
||||
// pass in slices to combine into combineSlices()
|
||||
// `combineSlices` automatically combines the reducers using
|
||||
// their `reducerPath`s, therefore we no longer need to call `combineReducers`.
|
||||
const rootReducer = combineSlices(minimapSlice, gazeSlice, communicationSlice);
|
||||
const rootReducer = combineSlices(
|
||||
minimapSlice,
|
||||
gazeSlice,
|
||||
communicationSlice,
|
||||
conversationSlice,
|
||||
);
|
||||
|
||||
// Infer the `RootState` type from the root reducer
|
||||
type RootState = ReturnType<typeof rootReducer>;
|
||||
|
@ -86,6 +86,7 @@ export type CustomElement = BaseElement & {
|
||||
export type RequestApprovalElement = BaseElement & {
|
||||
type: 'request-approval';
|
||||
messageId: string;
|
||||
conversationId: string;
|
||||
icon: IconElement;
|
||||
leftButton: ButtonElement;
|
||||
rightButton: ButtonElement;
|
||||
@ -96,6 +97,7 @@ export type RequestApprovalElement = BaseElement & {
|
||||
export type MissileIncomingElement = BaseElement & {
|
||||
type: 'missile-incoming';
|
||||
messageId: string;
|
||||
conversationId: string;
|
||||
icon: IconElement;
|
||||
widgetId: string;
|
||||
};
|
||||
|
@ -24,7 +24,6 @@ export type BaseMessage<TKind extends string, TData extends object> = {
|
||||
priority: Priority;
|
||||
kind: TKind;
|
||||
data: TData;
|
||||
latestInConvo?: boolean;
|
||||
read?: boolean;
|
||||
fulfilled?: boolean;
|
||||
tags?: string[];
|
||||
|
@ -211,12 +211,14 @@ const generateAudioElement = (
|
||||
const generateMissileIncomingElement = (
|
||||
baseElement: Element.BaseElement,
|
||||
messageId: string,
|
||||
conversationId: string,
|
||||
widgetId: string,
|
||||
icon: Element.IconElement,
|
||||
): Element.MissileIncomingElement => ({
|
||||
...baseElement,
|
||||
type: 'missile-incoming',
|
||||
messageId,
|
||||
conversationId,
|
||||
widgetId,
|
||||
icon,
|
||||
});
|
||||
@ -224,6 +226,7 @@ const generateMissileIncomingElement = (
|
||||
const generateRequestApprovalElement = (
|
||||
baseElement: Element.BaseElement,
|
||||
messageId: string,
|
||||
conversationId: string,
|
||||
widgetId: string,
|
||||
icon: Element.IconElement,
|
||||
leftButton: Element.ButtonElement,
|
||||
@ -232,6 +235,7 @@ const generateRequestApprovalElement = (
|
||||
...baseElement,
|
||||
type: 'request-approval',
|
||||
messageId,
|
||||
conversationId,
|
||||
widgetId,
|
||||
icon,
|
||||
leftButton,
|
||||
|
Loading…
Reference in New Issue
Block a user