implement message filter by reactions

This commit is contained in:
marlon-erler
2026-04-11 21:01:23 +00:00
parent 45e3ed31ee
commit 9c0a20d22f
9 changed files with 261 additions and 7 deletions
+4
View File
@@ -216,3 +216,7 @@
grid-template-columns: repeat(3, minmax(80px, 1fr));
}
}
.grid.message-reaction-filter {
grid-template-columns: repeat(3, 1fr);
}
+1
View File
@@ -30,6 +30,7 @@ export function ChatMessage(
<div
class="message-bubble"
toggle:sentbyuser={chatMessageViewModel.sentByUser}
toggle:hidden={chatMessageViewModel.isHidden}
>
<div class="main tile">
<div class="text-container">
@@ -0,0 +1,32 @@
import * as React from "bloatless-react";
import {
ChatMessageReaction,
ReactionSymbols,
} from "../../Model/Chat/chatModel";
import ChatMessageViewModel from "../../ViewModel/Chat/chatMessageViewModel";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
import MessagePageViewModel from "../../ViewModel/Pages/messagePageViewModel";
export function MessageReactionFilterButton(
messagePageViewModel: MessagePageViewModel,
content: ReactionSymbols,
) {
const select = () => {
messagePageViewModel.setReactionFilter(content);
};
const isSelected = React.createProxyState(
[messagePageViewModel.reactionFilter],
() => messagePageViewModel.reactionFilter.value == content,
);
return (
<button
class="flex justify-center"
on:click={select}
toggle:selected={isSelected}
>
{content}
</button>
);
}
+68 -5
View File
@@ -5,24 +5,40 @@ import { StringToOption } from "../Components/option";
import { v4 } from "uuid";
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
import MessagePageViewModel from "../../ViewModel/Pages/messagePageViewModel";
import { MessageReactionFilterButton } from "../Components/messageReactionFilterButton";
import { ReactionSymbols } from "../../Model/Chat/chatModel";
export function MessageFilterModal<T>(
coreViewModel: CoreViewModel,
messagePageViewModel: MessagePageViewModel,
converter: React.StateItemConverter<T>,
) {
const noFilter = React.createProxyState(
[messagePageViewModel.reactionFilter],
() => messagePageViewModel.reactionFilter.value == undefined,
);
return (
<div class="modal" toggle:open={messagePageViewModel.isFilterModalOpen}>
<div>
<main>
<h2>{coreViewModel.translations.chatPage.message.messageFilterHeadline}</h2>
<h2>
{
coreViewModel.translations.chatPage.message
.messageFilterHeadline
}
</h2>
<div class="flex-row width-input">
<input
placeholder={
coreViewModel.translations.general.searchLabel
}
bind:value={messagePageViewModel.searchViewModel.searchInput}
on:enter={messagePageViewModel.searchViewModel.applySearch}
bind:value={
messagePageViewModel.searchViewModel.searchInput
}
on:enter={
messagePageViewModel.searchViewModel.applySearch
}
></input>
<button
class="primary"
@@ -30,14 +46,61 @@ export function MessageFilterModal<T>(
coreViewModel.translations.general
.searchButtonAudioLabel
}
on:click={messagePageViewModel.searchViewModel.applySearch}
toggle:disabled={messagePageViewModel.searchViewModel.cannotApplySearch}
on:click={
messagePageViewModel.searchViewModel.applySearch
}
toggle:disabled={
messagePageViewModel.searchViewModel
.cannotApplySearch
}
>
<span class="icon">search</span>
</button>
</div>
<hr></hr>
<h3>
{
coreViewModel.translations.chatPage.message
.messageFilterReactionsHadline
}
</h3>
<div class="flex-column gap">
<button toggle:selected={noFilter} on:click={messagePageViewModel.revokeReactionFilter}>
{
coreViewModel.translations.chatPage.message
.messageFilterAllReactionsButton
}
</button>
<div class="grid gap message-reaction-filter">
{MessageReactionFilterButton(
messagePageViewModel,
ReactionSymbols.ThumbsUp,
)}
{MessageReactionFilterButton(
messagePageViewModel,
ReactionSymbols.Check,
)}
{MessageReactionFilterButton(
messagePageViewModel,
ReactionSymbols.Stop,
)}
{MessageReactionFilterButton(
messagePageViewModel,
ReactionSymbols.Attention,
)}
{MessageReactionFilterButton(
messagePageViewModel,
ReactionSymbols.DoubleAttention,
)}
{MessageReactionFilterButton(
messagePageViewModel,
ReactionSymbols.Question,
)}
</div>
</div>
</main>
<button on:click={messagePageViewModel.closeFilterModal}>
{coreViewModel.translations.general.closeButton}
+9
View File
@@ -184,7 +184,10 @@ const englishTranslations = {
message: {
messagesHeadline: "Messages",
messageFilterHeadline: "Filter messages",
messageFilterReactionsHadline: "Reactions",
messageFilterAllReactionsButton: "Show all",
///
@@ -482,7 +485,10 @@ export const allTranslations: {
message: {
messagesHeadline: "Nachrichten",
messageFilterHeadline: "Nachrichten filtern",
messageFilterReactionsHadline: "Reaktionen",
messageFilterAllReactionsButton: "Alle anzeigen",
composerInputPlaceholder: "Schreib eine Nachricht...",
sendMessageButtonAudioLabel: "nachricht senden",
@@ -754,7 +760,10 @@ export const allTranslations: {
message: {
messagesHeadline: "Mensajes",
messageFilterHeadline: "Filtrar mensajes",
messageFilterReactionsHadline: "Reacciones",
messageFilterAllReactionsButton: "Mostrar todas",
composerInputPlaceholder: "Escribe un mensaje...",
sendMessageButtonAudioLabel: "enviar mensaje",
@@ -22,6 +22,8 @@ export default class ChatMessageViewModel {
);
sentByUser: boolean;
isHidden = new React.State<boolean>(false);
allReactions = new React.MapState<ChatMessageReaction>();
userReaction = new React.State<ReactionSymbols | string | undefined>(
undefined,
@@ -129,5 +131,43 @@ export default class ChatMessageViewModel {
this.chatMessage = chatMessage;
this.sentByUser = sentByUser;
this.loadData();
this.messagePageViewModel.reactionFilter.subscribe((content) => {
console.log(content, content==undefined)
if (content == undefined) {
this.isHidden.value = false;
return;
}
let count = 0;
switch (content) {
case ReactionSymbols.ThumbsUp: {
count = this.reactionsThumbsUpCount.value;
break;
}
case ReactionSymbols.Check: {
count = this.reactionsCheckCount.value;
break;
}
case ReactionSymbols.Stop: {
count = this.reactionsStopCount.value;
break;
}
case ReactionSymbols.Attention: {
count = this.reactionsAttentionCount.value;
break;
}
case ReactionSymbols.DoubleAttention: {
count = this.reactionsDoubleAttentionCount.value;
break;
}
case ReactionSymbols.Question: {
count = this.reactionsQuestionCount.value;
break;
}
}
this.isHidden.value = count == 0;
})
}
}
@@ -18,6 +18,8 @@ export default class MessagePageViewModel {
new React.ListState();
searchViewModel: SearchViewModel<ChatMessageViewModel>;
isFilterModalOpen = new React.State<boolean>(false);
reactionFilter = new React.State<ReactionSymbols | undefined>(undefined);
composingMessage: React.State<string> = new React.State("");
// guards
@@ -94,6 +96,14 @@ export default class MessagePageViewModel {
this.isFilterModalOpen.value = false;
};
revokeReactionFilter = (): void => {
this.reactionFilter.value = undefined;
}
setReactionFilter = (content: ReactionSymbols): void => {
this.reactionFilter.value = content;
};
// load
loadData = (): void => {
this.chatMessageViewModels.clear();