feat(status): make the set-status menu work

feat(user): optimise get() function for react
qol(todos): update todos
This commit is contained in:
Alois 2026-08-10 13:48:32 +02:00
commit 1c90a0c859
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
30 changed files with 572 additions and 324 deletions

View file

@ -24,7 +24,7 @@ import {
} from "lucide-react";
import { useState, useMemo, useEffect, useRef, type WheelEvent } from "react";
import MediaSaveButton from "./mediaSaveButton";
import { type User, useUser } from "@tensamin/user/context";
import { useUserFields } from "@tensamin/user/context";
const zoomLevels = [1, 1.5, 2, 3];
@ -46,12 +46,7 @@ export default function Media({
const wheelDelta = useRef(0);
const hostname = useMemo(() => new URL(link).hostname, [link]);
// Get user's
const { get } = useUser();
const [user, setUser] = useState<undefined | User>(undefined);
useEffect(() => {
get(senderId).then(setUser);
}, [senderId, get]);
const { data: user } = useUserFields(senderId, ["Avatar", "Display"]);
const avatar = user?.Avatar
? `data:image/webp;base64,${user.Avatar}`
: undefined;

View file

@ -1,7 +1,7 @@
import type { RawMessage } from "../values";
import { AlertTriangle, Check, CheckLine, RefreshCw } from "lucide-react";
import { memo, useCallback, useEffect, useRef, useState } from "react";
import type { User } from "@tensamin/user/context";
import { type SelectedUser, useUserFields } from "@tensamin/user/context";
import {
Avatar,
@ -21,7 +21,6 @@ import { decryptChatText, encryptChatText } from "@tensamin/crypto/chatSecret";
import { log, toast } from "@tensamin/shared/log";
import { Emoji, Input, normalizeShortcode, Text } from "@methanium/ui/markdown";
import { useRecordEmojiUse } from "./emojiRanks";
import { useUser } from "@tensamin/user/context";
import ReplyBox from "./replyBox";
import { useHotkey } from "@tensamin/hotkeys";
import { cancelMessageEditHotkey } from "../hotkeys";
@ -40,7 +39,7 @@ function MessageComponent({
decryptionFailed?: boolean;
};
onSetEditing: (editing: boolean) => void;
user: User | null;
user: SelectedUser<readonly ["UserId", "Avatar", "Display"]> | null;
}) {
const actuallyFailed =
(message.failed && message.MessageState === "awaiting") ||
@ -150,13 +149,14 @@ function MessageComponent({
removeReaction,
replyTo,
} = useChat();
const { get: getUser } = useUser();
const [replyMessage, setReplyMessage] = useState<RawMessage | null>(null);
const [replyUser, setReplyUser] = useState<User | null>(null);
const { data: replyUser } = useUserFields(replyMessage?.SenderId ?? null, [
"Avatar",
"Display",
]);
useEffect(() => {
if (!message.ReplyId || !ownId || !chatSecret) {
setReplyMessage(null);
setReplyUser(null);
return;
}
@ -168,25 +168,20 @@ function MessageComponent({
send,
})
.then(async (reply) => {
const [Content, author] = await Promise.all([
decryptChatText(chatSecret, reply.Content),
getUser(reply.SenderId),
]);
const Content = await decryptChatText(chatSecret, reply.Content);
if (!active) return;
setReplyMessage({ ...reply, Content });
setReplyUser(author);
})
.catch((err) => {
if (!active) return;
setReplyMessage(null);
setReplyUser(null);
log(1, "chat", "red", "Failed to get replied-to message", err);
});
return () => {
active = false;
};
}, [chatSecret, getUser, message.ReplyId, ownId, send, userId]);
}, [chatSecret, message.ReplyId, ownId, send, userId]);
const recordUse = useRecordEmojiUse();
const editingRef = useRef(editing);
const onSetEditingRef = useRef(onSetEditing);

View file

@ -7,11 +7,13 @@ import {
Skeleton,
} from "@methanium/ui";
import { Text } from "@methanium/ui/markdown";
import type { User } from "@tensamin/user/context";
import type { SelectedUser } from "@tensamin/user/context";
import Wrapper from "@tensamin/user/wrapper";
import { Forward, X } from "lucide-react";
function ReplyUser({ user }: { user: User }) {
type ReplyUserData = SelectedUser<readonly ["Avatar", "Display"]>;
function ReplyUser({ user }: { user: ReplyUserData }) {
return (
<div className="flex gap-1 items-center">
<Avatar className="h-5 w-5 shrink-0">
@ -40,7 +42,7 @@ export default function ReplyBox({
content?: string;
loading?: boolean;
onDismiss?: () => void;
user?: User;
user?: ReplyUserData;
userId?: number;
variant: "composer" | "message";
}) {
@ -71,6 +73,7 @@ export default function ReplyBox({
) : userId ? (
<Wrapper
userId={userId}
fields={["Avatar", "Display"]}
loading={<Skeleton className="h-5 w-5 rounded-full" />}
component={(resolvedUser) => <ReplyUser user={resolvedUser} />}
/>

View file

@ -458,7 +458,7 @@ export default function Provider({ children }: { children: ReactNode }) {
secretId,
version: CHAT_SECRET_VERSION,
});
const peerUser = await getUser(userIdValue);
const peerUser = await getUser(userIdValue, ["PublicKey"]);
const peerWrapped = await wrapChatSecret({
chatSecret: rawSecret,
recipientKemPublicKey: kemPublicKeyFromPublicKeyBundle(

View file

@ -598,6 +598,7 @@ export default function Screen() {
<Wrapper
key={getMessageRenderKey(message)}
userId={message.SenderId}
fields={["UserId", "Avatar", "Display"]}
loading={null}
component={(user) => (
<>