Commit b083d719 authored by Grant's avatar Grant
Browse files

draft: event rendering

parent 5973a0c4
Loading
Loading
Loading
Loading
+28 −34
Original line number Diff line number Diff line
import { MessageFlag, type Message } from "@/lib/const";
import { useYap } from "@/context/utils";
import type React from "react";
import type { MatrixEvent } from "matrix-js-sdk";

type MessageRole = "ME" | "OTHER" | "SYSTEM";

export const ChatBubble = ({ message }: { message: Message }) => {
  const { state } = useYap<"Internal">();
  const isSystem = (_str: string) => false;
  const role: MessageRole = isSystem(message.sender)
    ? "SYSTEM"
    : message.sender === state.user
      ? "ME"
      : "OTHER";
  const flags = message.flags;
export const ChatBubble = ({ event }: { event: MatrixEvent }) => {
  const { client } = useYap<"Internal">();
  const isRight = event.getSender() === client.client.getUserId();

  return (
    <div
      className="flex flex-col w-full"
      style={{
        alignItems:
          role === "ME" ? "end" : role === "OTHER" ? "start" : "center",
        alignItems: isRight ? "end" : "start",
      }}
    >
      {role === "SYSTEM" ? (
        <div className="self-center">{message.content}</div>
      ) : (
      {/* {role === "SYSTEM" ? (
        <div className="self-center">{event.content}</div>
      ) : ( */}
      <>
        <div className="flex flex-row gap-1 *:text-xs">
          <span>{event.getSender()}</span>
        </div>
        <div
          className="rounded-xl p-1"
          style={{
              backgroundColor:
                role === "ME"
            backgroundColor: isRight
              ? "var(--color-gray-500)"
              : "var(--color-gray-300)",
          }}
        >
            {message.content}
          {event.getContent().body}
        </div>
        <div className="flex flex-row gap-1 *:text-xs">
            {flags & MessageFlag.SENDING ? <span>sending</span> : ""}
          {/* {flags & MessageFlag.SENDING ? <span>sending</span> : ""}
            {flags & MessageFlag.SEEN ? <span>seen</span> : ""}
            {flags & MessageFlag.EDITED ? <span>edited</span> : ""}
            {flags & MessageFlag.EDITED ? <span>edited</span> : ""} */}
        </div>
      </>
      )}
      {/* )} */}
    </div>
  );
};
+1 −1
Original line number Diff line number Diff line
@@ -26,7 +26,7 @@ export const ChatLog = ({
      )}
      <React.Fragment>
        {messages.map((msg) => (
          <ChatBubble key={msg.eventId} message={msg} />
          <ChatBubble key={msg.eventId} event={msg} />
        ))}
      </React.Fragment>
      <div ref={bottom} />
+76 −14
Original line number Diff line number Diff line
@@ -4,8 +4,10 @@ import type { MXRoomID } from "@/lib/const";
import { useMessages } from "@/hooks/useMessages";
import { useYap } from "@/context/utils";
import { useRoom } from "@/hooks/useRoom";
import React, { useCallback, useState } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { MXClient } from "@/lib/MXClient";
import { Room } from "matrix-js-sdk";
import { ChatTimeline } from "../ChatLog/ChatTimeline";

export const RoomTab = ({
  roomId,
@@ -57,13 +59,17 @@ export const RoomTab = ({
            {/* <ChatBubble role="ME" flags={MessageFlag.SENDING ^ MessageFlag.SEEN} />
      <ChatBubble role="SYSTEM" />
      <ChatBubble role="OTHER" /> */}
            <ChatLog
            {/* <ChatLog
              messages={messages}
              loading={loading}
              atBeginning={atBeginning}
              loadMore={loadMore}
            />
            /> */}
            <ChatTimeline roomId={roomId} />
          </div>
          {room.replacedBy ? (
            <ReplacedByBanner roomId={roomId} />
          ) : (
            <div className="p-2">
              <form action="#" onSubmit={sendMessage}>
                <input
@@ -75,12 +81,68 @@ export const RoomTab = ({
                <button type="submit">Send</button>
              </form>
            </div>
          )}
        </>
      )}
    </Tab>
  );
};

const ReplacedByBanner = ({ roomId }: { roomId: MXRoomID }) => {
  const { dispatch } = useYap<"Internal">();
  const { replacedBy } = useRoom(roomId);
  const [room, setRoom] = useState<Room>();

  useEffect(() => {
    const mxclient = MXClient.get();

    const onDynamic = (room: Room) => {
      if (replacedBy && room.roomId === replacedBy) {
        setRoom(room);
      }
    };

    if (replacedBy) {
      setRoom(mxclient.rooms.getRoom(replacedBy) ?? undefined);
    }

    mxclient.rooms.on("new", onDynamic);

    return () => {
      mxclient.rooms.off("new", onDynamic);
    };
  }, [replacedBy]);

  const doJoin = useCallback(
    (oldRoomId: MXRoomID, newRoomId: MXRoomID) => {
      const mxclient = MXClient.get();
      mxclient.rooms
        .joinRoom(newRoomId)
        .then(() => {
          dispatch(["openChat", newRoomId]);
          dispatch(["closeChat", oldRoomId]);
        })
        .catch((e) => {
          console.error("failed to join replacement room", e);
        });
    },
    [dispatch],
  );

  if (!replacedBy) return null;

  return (
    <div className="bg-red-400/50 p-1 w-full overflow-x-clip relative">
      room replaced by {room?.name ?? replacedBy}
      <button
        className="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer"
        title="Join new room"
        onClick={() => doJoin(roomId, replacedBy)}
      ></button>
    </div>
  );
};

const ConfirmInvite = ({
  roomId,
  onDecline,
+36 −44
Original line number Diff line number Diff line
import { MessageFlag, type Message, type MXRoomID } from "@/lib/const";
import { useCallback, useState } from "react";

/**
 * sorted oldest to newest
 */
const TEST_MESSAGES: Message[] = [
  {
    eventId: `$a`,
    sender: "@grant:aftermath.gg",
    content: "hello",
    flags: MessageFlag.SEEN,
    createdAt: "2026-03-30T17:40:46.659Z",
  },
  {
    eventId: `$b`,
    sender: "@tnarg:aftermath.gg",
    content: "canvas!",
    flags: 0,
    createdAt: "2026-03-30T17:41:46.659Z",
  },
  {
    eventId: "$c",
    sender: "@canvas:aftermath.gg",
    content: "canvas 2026 has started",
    flags: 0,
    createdAt: "2026-03-30T17:45:46.659Z",
  },
  {
    eventId: "$d",
    sender: "@tnarg:aftermath.gg",
    content: "lets gooo",
    flags: 0,
    createdAt: "2026-03-30T17:46:46.659Z",
  },
  {
    eventId: "$e",
    sender: "@grant:aftermath.gg",
    content: "very cool",
    flags: MessageFlag.SENDING,
    createdAt: "2026-03-30T17:47:46.659Z",
  },
];
import {
  MessageFlag,
  type Message,
  type MXEventID,
  type MXRoomID,
  type MXUserID,
} from "@/lib/const";
import { MXClient } from "@/lib/MXClient";
import { useCallback, useEffect, useState } from "react";

export const useMessages = (
  _roomId: MXRoomID,
  roomId: MXRoomID,
): {
  messages: Message[];
  loading: boolean;
@@ -55,6 +21,32 @@ export const useMessages = (
  const [messages, setMessages] = useState<Message[]>([]);
  const [cursor, setCursor] = useState<string>();

  useEffect(() => {
    const mxclient = MXClient.get();
    const room = mxclient.rooms.getRoom(roomId);

    if (!room) {
      console.warn("useMessages: failed to get room " + roomId);
      return;
    }

    const events = room.getLiveTimeline().getEvents();
    const messages = events.filter((e) => e.getType() === "m.room.message");

    setMessages(
      messages.map(
        (mxevent): Message => ({
          eventId: mxevent.getId()! as MXEventID,
          content: mxevent.getContent().body,
          createdAt: mxevent.getDate()!.toISOString(),
          sender: mxevent.getSender()! as MXUserID,
          event: mxevent,
          flags: 0,
        }),
      ),
    );
  }, [roomId]);

  const loadMore = useCallback(() => {
    if (loading) return;

+20 −2
Original line number Diff line number Diff line
import type { MXRoomID } from "@/lib/const";
import { MXClient } from "@/lib/MXClient";
import { MatrixEvent, Room, RoomEvent } from "matrix-js-sdk";
import {
  Direction,
  EventType,
  MatrixEvent,
  Room,
  RoomEvent,
} from "matrix-js-sdk";
import { useEffect, useState } from "react";

export const useRoom = (roomId: MXRoomID) => {
  const [name, setName] = useState<string>();
  const [recent, setRecent] = useState<MatrixEvent>();
  const [membership, setMembership] = useState<string>();
  const [replacedBy, setReplacedBy] = useState<MXRoomID>();

  useEffect(() => {
    const { client } = MXClient.get();
@@ -14,6 +21,13 @@ export const useRoom = (roomId: MXRoomID) => {
    setName(room?.name);
    setMembership(room?.getMyMembership());
    setRecent(room?.getLiveTimeline().getEvents().at(-1));
    setReplacedBy(
      room
        ?.getLiveTimeline()
        .getState(Direction.Forward)
        ?.getStateEvents(EventType.RoomTombstone, "")
        ?.getContent()?.replacement_room as MXRoomID,
    );

    const handleName = (room: Room) => {
      if (room.roomId !== roomId) return;
@@ -26,6 +40,10 @@ export const useRoom = (roomId: MXRoomID) => {
      if (!room || room.roomId !== roomId) return;

      setRecent(event);

      if (event.getType() === EventType.RoomTombstone) {
        setReplacedBy(event.getContent().replacement_room);
      }
    });
    client.on(RoomEvent.MyMembership, (room, membership) => {
      if (room.roomId !== roomId) return;
@@ -38,5 +56,5 @@ export const useRoom = (roomId: MXRoomID) => {
    };
  }, [roomId]);

  return { name, recent, membership };
  return { name, recent, membership, replacedBy };
};
Loading