show reactions per user, allow only one reaction per message per user
This commit is contained in:
@@ -416,11 +416,7 @@ export default class ChatModel {
|
||||
const fileContent = FileModel.createFileContent(v4(), "reaction");
|
||||
const reaction: ChatMessageReaction = {
|
||||
...fileContent,
|
||||
fileId: ChatModel.createMessageReactionId(
|
||||
messageId,
|
||||
content,
|
||||
sender,
|
||||
),
|
||||
fileId: ChatModel.createMessageReactionId(messageId, sender),
|
||||
|
||||
messageId,
|
||||
sender,
|
||||
@@ -432,10 +428,9 @@ export default class ChatModel {
|
||||
|
||||
static createMessageReactionId = (
|
||||
messageId: string,
|
||||
content: ReactionSymbols,
|
||||
sender: string,
|
||||
): string => {
|
||||
return messageId + sender + reactionStringMap[content];
|
||||
return messageId + sender;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -449,11 +444,6 @@ export enum ReactionSymbols {
|
||||
Question = "❓",
|
||||
}
|
||||
|
||||
const reactionStringMap: { [key: ReactionSymbols | string]: string } = {};
|
||||
Object.entries(ReactionSymbols).forEach((value) => {
|
||||
reactionStringMap[value[1]] = value[0];
|
||||
});
|
||||
|
||||
export interface ChatInfo extends ValidObject {
|
||||
primaryChannel: string;
|
||||
secondaryChannels: string[];
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.message-bubble > .message-reaction-row:has(button:not([zero])) {
|
||||
.message-bubble > .message-reaction-row:has(button:not([count="0"])) {
|
||||
display: grid !important;
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
animation: slide-up var(--animation-duration);
|
||||
}
|
||||
|
||||
.message-bubble > .message-reaction-row button[zero] {
|
||||
.message-bubble > .message-reaction-row button[count="0"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,50 +13,35 @@ export function MessageReactionButton(
|
||||
) {
|
||||
let audioLabel: string;
|
||||
let count: React.State<number>;
|
||||
let reactionState: React.MapState<ChatMessageReaction>;
|
||||
let isActive = new React.State(false);
|
||||
let isZero = new React.State(true);
|
||||
let isActive = React.createProxyState([chatMessageViewModel.userReaction], (() => chatMessageViewModel.userReaction.value == content))
|
||||
|
||||
function sendReaction() {
|
||||
chatMessageViewModel.sendReaction(content, isActive.value);
|
||||
}
|
||||
|
||||
function getUsername(): string {
|
||||
return chatMessageViewModel.messagePageViewModel.chatViewModel
|
||||
.settingsViewModel.username.value;
|
||||
}
|
||||
|
||||
function checkIsHighlighted(): boolean {
|
||||
return reactionState.value.has(getUsername());
|
||||
}
|
||||
|
||||
switch (content) {
|
||||
case ReactionSymbols.ThumbsUp: {
|
||||
audioLabel =
|
||||
coreViewModel.translations.chatPage.message.thumbsUpReaction;
|
||||
count = chatMessageViewModel.reactionsThumbsUpCount;
|
||||
reactionState = chatMessageViewModel.reactionsThumbsUp;
|
||||
break;
|
||||
}
|
||||
case ReactionSymbols.Check: {
|
||||
audioLabel =
|
||||
coreViewModel.translations.chatPage.message.checkReaction;
|
||||
count = chatMessageViewModel.reactionsCheckCount;
|
||||
reactionState = chatMessageViewModel.reactionsCheck;
|
||||
break;
|
||||
}
|
||||
case ReactionSymbols.Stop: {
|
||||
audioLabel =
|
||||
coreViewModel.translations.chatPage.message.stopReaction;
|
||||
count = chatMessageViewModel.reactionsStopCount;
|
||||
reactionState = chatMessageViewModel.reactionsStop;
|
||||
break;
|
||||
}
|
||||
case ReactionSymbols.Attention: {
|
||||
audioLabel =
|
||||
coreViewModel.translations.chatPage.message.attentionReaction;
|
||||
count = chatMessageViewModel.reactionsAttentionCount;
|
||||
reactionState = chatMessageViewModel.reactionsAttention;
|
||||
break;
|
||||
}
|
||||
case ReactionSymbols.DoubleAttention: {
|
||||
@@ -64,30 +49,23 @@ export function MessageReactionButton(
|
||||
coreViewModel.translations.chatPage.message
|
||||
.doubleAttentionReaction;
|
||||
count = chatMessageViewModel.reactionsDoubleAttentionCount;
|
||||
reactionState = chatMessageViewModel.reactionsDoubleAttention;
|
||||
break;
|
||||
}
|
||||
case ReactionSymbols.Question: {
|
||||
audioLabel =
|
||||
coreViewModel.translations.chatPage.message.questionReaction;
|
||||
count = chatMessageViewModel.reactionsQuestionCount;
|
||||
reactionState = chatMessageViewModel.reactionsQuestion;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
reactionState.subscribe(() => {
|
||||
isActive.value = checkIsHighlighted();
|
||||
isZero.value = reactionState.value.size == 0;
|
||||
});
|
||||
|
||||
return (
|
||||
<button
|
||||
class="flex"
|
||||
on:click={sendReaction}
|
||||
aria-label={audioLabel}
|
||||
toggle:selected={isActive}
|
||||
toggle:zero={isZero}
|
||||
set:count={count}
|
||||
>
|
||||
{content}
|
||||
<span subscribe:innerText={count}></span>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import * as React from "bloatless-react";
|
||||
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
|
||||
import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel";
|
||||
import { ChatMessageReaction } from "../../Model/Chat/chatModel";
|
||||
|
||||
export function MessageReactionEntry(reaction: ChatMessageReaction) {
|
||||
return (
|
||||
<div class="tile flex-row">
|
||||
<span class="flex width-100">{reaction.sender}</span>
|
||||
<span>{reaction.content}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel";
|
||||
import { MessageReactionButtonRow } from "../Components/messageReactionButtonRow";
|
||||
import { InfoTile } from "../Components/infoTile";
|
||||
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
|
||||
import { MessageReactionEntry } from "../Components/messageReactionEntry";
|
||||
|
||||
export function ChatMessageInfoModal(
|
||||
coreViewModel: CoreViewModel,
|
||||
@@ -76,10 +77,20 @@ export function ChatMessageInfoModal(
|
||||
|
||||
<hr></hr>
|
||||
|
||||
{MessageReactionButtonRow(
|
||||
coreViewModel,
|
||||
chatMessageViewModel,
|
||||
)}
|
||||
<div class="flex-column gap">
|
||||
{MessageReactionButtonRow(
|
||||
coreViewModel,
|
||||
chatMessageViewModel,
|
||||
)}
|
||||
|
||||
<div
|
||||
class="flex-column gap"
|
||||
children:append={[
|
||||
chatMessageViewModel.allReactions,
|
||||
MessageReactionEntry,
|
||||
]}
|
||||
></div>
|
||||
</div>
|
||||
</main>
|
||||
<button on:click={chatMessageViewModel.hideInfoModal}>
|
||||
{coreViewModel.translations.general.closeButton}
|
||||
|
||||
@@ -3,7 +3,6 @@ import * as React from "bloatless-react";
|
||||
import {
|
||||
ChatMessage,
|
||||
ChatMessageReaction,
|
||||
ChatMessageReactionReference,
|
||||
ChatMessageStatuses,
|
||||
ReactionSymbols,
|
||||
} from "../../Model/Chat/chatModel";
|
||||
@@ -23,36 +22,44 @@ export default class ChatMessageViewModel {
|
||||
);
|
||||
sentByUser: boolean;
|
||||
|
||||
reactionsThumbsUp = new React.MapState<ChatMessageReaction>();
|
||||
reactionsCheck = new React.MapState<ChatMessageReaction>();
|
||||
reactionsStop = new React.MapState<ChatMessageReaction>();
|
||||
reactionsAttention = new React.MapState<ChatMessageReaction>();
|
||||
reactionsDoubleAttention = new React.MapState<ChatMessageReaction>();
|
||||
reactionsQuestion = new React.MapState<ChatMessageReaction>();
|
||||
allReactions = new React.MapState<ChatMessageReaction>();
|
||||
userReaction = new React.State<ReactionSymbols | string | undefined>(undefined);
|
||||
reactionsThumbsUp = React.MapState<ChatMessageReaction>;
|
||||
reactionsCheck = React.MapState<ChatMessageReaction>;
|
||||
reactionsStop = React.MapState<ChatMessageReaction>;
|
||||
reactionsAttention = React.MapState<ChatMessageReaction>;
|
||||
reactionsDoubleAttention = React.MapState<ChatMessageReaction>;
|
||||
reactionsQuestion = React.MapState<ChatMessageReaction>;
|
||||
|
||||
reactionsThumbsUpCount = React.createProxyState(
|
||||
[this.reactionsThumbsUp],
|
||||
() => this.reactionsThumbsUp.value.size,
|
||||
generateReactionCountProxyState = (
|
||||
content: ReactionSymbols,
|
||||
): React.State<number> => {
|
||||
return React.createProxyState(
|
||||
[this.allReactions],
|
||||
() =>
|
||||
[...this.allReactions.value.values()].filter(
|
||||
(x) => x.content == content,
|
||||
).length,
|
||||
);
|
||||
};
|
||||
|
||||
reactionsThumbsUpCount = this.generateReactionCountProxyState(
|
||||
ReactionSymbols.ThumbsUp,
|
||||
);
|
||||
reactionsCheckCount = React.createProxyState(
|
||||
[this.reactionsCheck],
|
||||
() => this.reactionsCheck.value.size,
|
||||
reactionsCheckCount = this.generateReactionCountProxyState(
|
||||
ReactionSymbols.Check,
|
||||
);
|
||||
reactionsStopCount = React.createProxyState(
|
||||
[this.reactionsStop],
|
||||
() => this.reactionsStop.value.size,
|
||||
reactionsStopCount = this.generateReactionCountProxyState(
|
||||
ReactionSymbols.Stop,
|
||||
);
|
||||
reactionsAttentionCount = React.createProxyState(
|
||||
[this.reactionsAttention],
|
||||
() => this.reactionsAttention.value.size,
|
||||
reactionsAttentionCount = this.generateReactionCountProxyState(
|
||||
ReactionSymbols.Attention,
|
||||
);
|
||||
reactionsDoubleAttentionCount = React.createProxyState(
|
||||
[this.reactionsDoubleAttention],
|
||||
() => this.reactionsDoubleAttention.value.size,
|
||||
reactionsDoubleAttentionCount = this.generateReactionCountProxyState(
|
||||
ReactionSymbols.DoubleAttention,
|
||||
);
|
||||
reactionsQuestionCount = React.createProxyState(
|
||||
[this.reactionsQuestion],
|
||||
() => this.reactionsQuestion.value.size,
|
||||
reactionsQuestionCount = this.generateReactionCountProxyState(
|
||||
ReactionSymbols.Question,
|
||||
);
|
||||
|
||||
// state
|
||||
@@ -80,28 +87,14 @@ export default class ChatMessageViewModel {
|
||||
|
||||
// reactions
|
||||
handleReaction = (reaction: ChatMessageReaction): void => {
|
||||
function setReaction(mapState: React.MapState<any>) {
|
||||
if (reaction.isDeleting) {
|
||||
mapState.remove(reaction.sender);
|
||||
} else {
|
||||
mapState.set(reaction.sender, reaction);
|
||||
}
|
||||
if (reaction.isDeleting) {
|
||||
this.allReactions.remove(reaction.sender);
|
||||
} else {
|
||||
this.allReactions.set(reaction.sender, reaction);
|
||||
}
|
||||
|
||||
switch (reaction.content) {
|
||||
case ReactionSymbols.ThumbsUp:
|
||||
return setReaction(this.reactionsThumbsUp);
|
||||
case ReactionSymbols.Check:
|
||||
return setReaction(this.reactionsCheck);
|
||||
case ReactionSymbols.Stop:
|
||||
return setReaction(this.reactionsStop);
|
||||
case ReactionSymbols.Attention:
|
||||
return setReaction(this.reactionsAttention);
|
||||
case ReactionSymbols.DoubleAttention:
|
||||
return setReaction(this.reactionsDoubleAttention);
|
||||
case ReactionSymbols.Question:
|
||||
return setReaction(this.reactionsQuestion);
|
||||
}
|
||||
if (reaction.sender != this.coreViewModel.settingsModel.username) return;
|
||||
this.userReaction.value = reaction.isDeleting ? undefined : reaction.content;
|
||||
};
|
||||
|
||||
sendReaction = (content: ReactionSymbols, isDeleting: boolean): void => {
|
||||
|
||||
Reference in New Issue
Block a user