typing indicators

This commit is contained in:
2025-12-23 13:57:13 +01:00
parent 3890e36078
commit e732eb7a20
3 changed files with 107 additions and 42 deletions
+6 -2
View File
@@ -1,19 +1,22 @@
import { Channel, Message, Server } from "@/types/types";
import { Message } from "@/types/types";
import MessageBox from "./MessageBox";
import App from "@/types/app";
import React, { Ref, useEffect, useRef } from "react";
import React from "react";
import PluginChannel from "./PluginChannel";
import { IndicatorContext } from "@/types/protocol";
export default function ChannelView({
app,
messages,
ip,
serverRef,
indicators,
}: {
app: App;
messages: Message[];
ip: string;
serverRef: React.RefObject<WebSocket | null>;
indicators: IndicatorContext[];
}) {
if (!app.currentChannel)
return (
@@ -27,6 +30,7 @@ export default function ChannelView({
channelName={app.currentChannel.name}
messages={messages}
sendRequest={(req) => serverRef.current?.send(JSON.stringify(req))}
indicators={indicators}
/>
);
+66 -38
View File
@@ -25,7 +25,7 @@ import {
ContextMenuItem,
ContextMenuTrigger,
} from "@/components/ui/context-menu";
import { ClientMessage } from "@/types/protocol";
import { ClientMessage, IndicatorContext } from "@/types/protocol";
import { AnimatePresence, motion } from "framer-motion";
function MessageContainer({
@@ -213,7 +213,7 @@ function MessageInput({
onEnter: () => void;
onTyping?: () => void;
}) {
const TYPING_INTERVAL = 2000;
const TYPING_INTERVAL = 1000;
const inputRef = useRef<any>(null);
const lastTypingSent = useRef(0);
const stopTypingTimeout = useRef<NodeJS.Timeout | null>(null);
@@ -303,12 +303,14 @@ export default function MessageBox({
channelName,
channelIcon,
messages,
indicators,
}: {
app: App;
sendRequest: (req: ClientMessage) => void;
channelName?: string;
channelIcon?: string;
messages: Message[];
indicators: IndicatorContext[];
}) {
const [text, setText] = useState("");
@@ -406,45 +408,71 @@ export default function MessageBox({
<div ref={ref} />
</AnimatePresence>
</div>
<footer className="flex gap-3 pr-5 w-full h-max">
<MessageInput
text={text}
setText={setText}
onEnter={sendMessage}
onTyping={() => {
console.log("Typing..");
if (!app.currentChannel) return;
sendRequest({
type: "typing",
params: {
channel_id: app.currentChannel.id,
},
});
}}
/>
<footer>
<div className="relative h-1">
<AnimatePresence>
{indicators.length > 0 && (
<motion.div
key={indicators
.map((i) => i.indicator.params.user_id)
.join("-")}
className="absolute inset-0 text-muted-foreground text-xs top-[-26px]"
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.3 }}
>
{indicators
.map(
(indicator) =>
app.profiles[indicator.indicator.params.user_id]
?.display_name ?? null
)
.join(", ")}
{indicators.length > 0 && " is typing"}
</motion.div>
)}
</AnimatePresence>
</div>
<div className="flex gap-3 pr-5 w-full h-max">
<MessageInput
text={text}
setText={setText}
onEnter={sendMessage}
onTyping={() => {
if (!app.currentChannel) return;
sendRequest({
type: "typing",
params: {
channel_id: app.currentChannel.id,
},
});
}}
/>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary" className="hidden md:block">
<SmilePlusIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary" className="hidden md:block">
<SmilePlusIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="">
<EmojiPicker
onEmojiClick={(e) => setText(text + e.emoji)}
theme={
app.clientSettings.theme === "dark"
? emoji.Theme.DARK
: emoji.Theme.LIGHT
}
/>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenuContent className="">
<EmojiPicker
onEmojiClick={(e) => setText(text + e.emoji)}
theme={
app.clientSettings.theme === "dark"
? emoji.Theme.DARK
: emoji.Theme.LIGHT
}
/>
</DropdownMenuContent>
</DropdownMenu>
<Button variant="secondary" onClick={sendMessage}>
<SendIcon />
</Button>
<Button variant="secondary" onClick={sendMessage}>
<SendIcon />
</Button>
</div>
</footer>
</div>
);