(feat): add message states
(qol): prep for mtp migration
This commit is contained in:
parent
b100967fda
commit
c484a3c303
6 changed files with 110 additions and 33 deletions
|
|
@ -69,6 +69,8 @@ export default function InputComponent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log(3, "chat", "purple", "Message send init, adding live message ...");
|
||||||
|
|
||||||
const reference = addLiveMessage({
|
const reference = addLiveMessage({
|
||||||
height: 0,
|
height: 0,
|
||||||
not_encrypted: true,
|
not_encrypted: true,
|
||||||
|
|
@ -78,7 +80,19 @@ export default function InputComponent({
|
||||||
message_state: "awaiting",
|
message_state: "awaiting",
|
||||||
});
|
});
|
||||||
|
|
||||||
const encryptedContext = await encryptText(sharedSecret, currentValue);
|
log(3, "chat", "purple", "Live message added, encrypting...");
|
||||||
|
|
||||||
|
const encryptedContext = await encryptText(
|
||||||
|
sharedSecret,
|
||||||
|
currentValue,
|
||||||
|
).catch((err) => {
|
||||||
|
toast("error", "Failed to encrypt message", String(err));
|
||||||
|
reference.setFailed(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!encryptedContext) return;
|
||||||
|
|
||||||
|
log(3, "chat", "purple", "Content encrypted, sending message...");
|
||||||
|
|
||||||
send("message_send", {
|
send("message_send", {
|
||||||
height: 0,
|
height: 0,
|
||||||
|
|
@ -96,6 +110,8 @@ export default function InputComponent({
|
||||||
toast("error", "Failed to send message");
|
toast("error", "Failed to send message");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
log(3, "chat", "purple", "Message sent");
|
||||||
|
|
||||||
moveUserIdToTop(userId);
|
moveUserIdToTop(userId);
|
||||||
|
|
||||||
if (!preserveContent) {
|
if (!preserveContent) {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import type { RawMessage } from "../values";
|
import type { RawMessage } from "../values";
|
||||||
import Text from "@tensamin/markdown/text";
|
import Text from "@tensamin/markdown/text";
|
||||||
import { AlertTriangle } from "lucide-react";
|
import { AlertTriangle, Check, CheckCheck } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import type { User } from "@tensamin/user/context";
|
import type { User } from "@tensamin/user/context";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
@ -16,6 +16,8 @@ import {
|
||||||
} from "@tensamin/ui";
|
} from "@tensamin/ui";
|
||||||
import MessageContextMenu from "./messageContextMenu";
|
import MessageContextMenu from "./messageContextMenu";
|
||||||
import Media from "./media";
|
import Media from "./media";
|
||||||
|
import { useStorage } from "@tensamin/storage/context";
|
||||||
|
import { useTTP } from "@tensamin/ttp";
|
||||||
|
|
||||||
function MessageComponent({
|
function MessageComponent({
|
||||||
grouped,
|
grouped,
|
||||||
|
|
@ -29,14 +31,14 @@ function MessageComponent({
|
||||||
user: User | null;
|
user: User | null;
|
||||||
}) {
|
}) {
|
||||||
const actuallyFailed = message.failed && message.message_state === "awaiting";
|
const actuallyFailed = message.failed && message.message_state === "awaiting";
|
||||||
|
|
||||||
|
// Fade-in
|
||||||
const [hasFadedIn, setHasFadedIn] = useState(false);
|
const [hasFadedIn, setHasFadedIn] = useState(false);
|
||||||
const opacityClass = !hasFadedIn
|
const opacityClass = !hasFadedIn
|
||||||
? "opacity-0"
|
? "opacity-0"
|
||||||
: actuallyFailed || message.message_state === "awaiting"
|
: actuallyFailed || message.message_state === "awaiting"
|
||||||
? "opacity-50"
|
? "opacity-50"
|
||||||
: "opacity-100";
|
: "opacity-100";
|
||||||
|
|
||||||
const [isValidURL, setIsValidURL] = useState(false);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeout = window.setTimeout(() => {
|
const timeout = window.setTimeout(() => {
|
||||||
setHasFadedIn(true);
|
setHasFadedIn(true);
|
||||||
|
|
@ -47,6 +49,8 @@ function MessageComponent({
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Check if message is a url
|
||||||
|
const [isValidURL, setIsValidURL] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
if (message.content.split(" ").length > 1) throw new Error();
|
if (message.content.split(" ").length > 1) throw new Error();
|
||||||
|
|
@ -58,6 +62,58 @@ function MessageComponent({
|
||||||
}
|
}
|
||||||
}, [message.content]);
|
}, [message.content]);
|
||||||
|
|
||||||
|
// Message states
|
||||||
|
const { load } = useStorage();
|
||||||
|
const { send } = useTTP();
|
||||||
|
const messageStateReadUpdate = useCallback(async () => {
|
||||||
|
const readConfirmations = await load("settings.read_confirmations");
|
||||||
|
|
||||||
|
if (readConfirmations) {
|
||||||
|
if (!user?.user_id) return;
|
||||||
|
|
||||||
|
await send("message_state", {
|
||||||
|
chat_partner_id: user?.user_id,
|
||||||
|
send_time: message.send_time,
|
||||||
|
message_state: "read",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await send("message_state", {
|
||||||
|
chat_partner_id: user?.user_id,
|
||||||
|
send_time: message.send_time,
|
||||||
|
message_state: "received",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [load, message.send_time, user?.user_id, send]);
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
const receiveConfirmations = await load("settings.receive_confirmations");
|
||||||
|
if (cancelled) return;
|
||||||
|
|
||||||
|
switch (message.message_state) {
|
||||||
|
case "sent":
|
||||||
|
if (receiveConfirmations) {
|
||||||
|
messageStateReadUpdate();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "received":
|
||||||
|
messageStateReadUpdate();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [message.message_state, load, messageStateReadUpdate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
// pt-3 is to get a gap between messages
|
// pt-3 is to get a gap between messages
|
||||||
|
|
@ -111,6 +167,17 @@ function MessageComponent({
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
|
<div>
|
||||||
|
{message.message_state === "read" ? (
|
||||||
|
<CheckCheck size={14} color="var(--primary)" />
|
||||||
|
) : message.message_state === "received" ? (
|
||||||
|
<CheckCheck size={14} color="var(--muted-foreground)" />
|
||||||
|
) : message.message_state === "sent" ? (
|
||||||
|
<Check size={14} color="var(--muted-foreground)" />
|
||||||
|
) : message.message_state === "sending" ? (
|
||||||
|
<div>IDK</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isValidURL ? (
|
{isValidURL ? (
|
||||||
|
|
|
||||||
|
|
@ -52,20 +52,20 @@ export default function Provider(props: { children: React.ReactNode }) {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update message state
|
// Update message state
|
||||||
if (
|
if (userId === sender_id) {
|
||||||
(await load("settings.read_confirmations")) &&
|
addLiveMessage({
|
||||||
userId === sender_id
|
...message,
|
||||||
) {
|
content: decryptedContent,
|
||||||
void send(
|
sent_by_self: false,
|
||||||
"message_state",
|
});
|
||||||
{
|
return;
|
||||||
message_state: "read",
|
}
|
||||||
},
|
|
||||||
{
|
// todo: add notification symbol to conversation cards (incl. message start)
|
||||||
id: ttpMessage.id,
|
|
||||||
},
|
moveUserIdToTop(sender_id);
|
||||||
);
|
|
||||||
} else {
|
if (await load("settings.receive_confirmations")) {
|
||||||
void send(
|
void send(
|
||||||
"message_state",
|
"message_state",
|
||||||
{
|
{
|
||||||
|
|
@ -77,20 +77,6 @@ export default function Provider(props: { children: React.ReactNode }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userId === sender_id) {
|
|
||||||
addLiveMessage({
|
|
||||||
...message,
|
|
||||||
content: decryptedContent,
|
|
||||||
sent_by_self: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo: add notification symbol to conversation cards (incl. message start)
|
|
||||||
|
|
||||||
moveUserIdToTop(sender_id);
|
|
||||||
|
|
||||||
if (isTauri()) {
|
if (isTauri()) {
|
||||||
console.log("weewoo");
|
console.log("weewoo");
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ export const message = z.object({
|
||||||
avatar: z.boolean().optional(),
|
avatar: z.boolean().optional(),
|
||||||
display: z.boolean().optional(),
|
display: z.boolean().optional(),
|
||||||
message_state: z
|
message_state: z
|
||||||
.enum(["read", "received", "sent", "sending", "awaiting"])
|
.enum(["read", "received", "sent", "sending", "awaiting"]) // awaiting for 'internal' use
|
||||||
.default("received"), // awaiting for 'internal' use
|
.default("received"),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const failedUser = {
|
export const failedUser = {
|
||||||
|
|
|
||||||
|
|
@ -67,26 +67,31 @@ const size = 20;
|
||||||
export function toast(
|
export function toast(
|
||||||
type: "error" | "info" | "warn" | "success",
|
type: "error" | "info" | "warn" | "success",
|
||||||
message: string,
|
message: string,
|
||||||
|
description?: string,
|
||||||
) {
|
) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "error":
|
case "error":
|
||||||
sonnerToast(message, {
|
sonnerToast(message, {
|
||||||
icon: <Ban size={size} />,
|
icon: <Ban size={size} />,
|
||||||
|
description,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "info":
|
case "info":
|
||||||
sonnerToast(message, {
|
sonnerToast(message, {
|
||||||
icon: <Info size={size} />,
|
icon: <Info size={size} />,
|
||||||
|
description,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "warn":
|
case "warn":
|
||||||
sonnerToast(message, {
|
sonnerToast(message, {
|
||||||
icon: <TriangleAlert size={size} />,
|
icon: <TriangleAlert size={size} />,
|
||||||
|
description,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "success":
|
case "success":
|
||||||
sonnerToast(message, {
|
sonnerToast(message, {
|
||||||
icon: <Check size={size} />,
|
icon: <Check size={size} />,
|
||||||
|
description,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
packages/ttp/todo.md
Normal file
3
packages/ttp/todo.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
- Replace with MTP
|
||||||
|
- Remove queue
|
||||||
|
- Use Date.now() for message id
|
||||||
Loading…
Reference in a new issue