Commit 5973a0c4 authored by Grant's avatar Grant
Browse files

draft: room invites & sending messages

parent 02ba1935
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -4,18 +4,20 @@ import { useState } from "react";
export const Tab = ({
  name,
  isNagging,
  defaultOpen,
  className,
  onClose,
  onFocus,
  children,
}: React.PropsWithChildren<{
  name: string;
  isNagging: boolean;
  isNagging?: boolean;
  defaultOpen?: boolean;
  className?: string;
  onClose?: () => unknown;
  onFocus?: () => unknown;
}>) => {
  const [open, setOpen] = useState(false);
  const [open, setOpen] = useState(defaultOpen ?? false);

  return (
    <div
+46 −81
Original line number Diff line number Diff line
import { useYap } from "@/context/utils";
import { Tab } from "../Tab";
import { useSubscriptions } from "@/hooks/useSubscriptions";
import { useRoom } from "@/hooks/useRoom";
import type { MXRoomID } from "@/main";
import { type JSX } from "react";
import { memo, useEffect, useState, type JSX } from "react";
import type { MatrixEvent } from "matrix-js-sdk";
import * as sdk from "matrix-js-sdk";
import { MXClient } from "@/lib/MXClient";
import { RoomListItem } from "../UI/RoomListItem";
import { List } from "../UI/List";

export const HomeTab = () => {
export const HomeTab = memo(() => {
  const { dispatch } = useYap<"Internal">();
  const { subscriptions, lastEvent } = useSubscriptions();
  const [invites, setInvites] = useState(0);
  const [subs, setSubs] = useState<{ roomId: MXRoomID }[]>([]);

  useEffect(() => {
    const { rooms } = MXClient.get();

    const onSync = (rooms: sdk.Room[]) => {
      setSubs(rooms.map((r) => ({ roomId: r.roomId as MXRoomID })));
    };
    const onInvites = (rooms: sdk.Room[]) => {
      setInvites(rooms.length);
    };

    setInvites(rooms.Invites.get().length);
    setSubs(
      rooms.Subscriptions.get().map((r) => ({ roomId: r.roomId as MXRoomID })),
    );

    rooms.Subscriptions.on("sync", onSync);
    rooms.Invites.on("sync", onInvites);

    return () => {
      rooms.Subscriptions.off("sync", onSync);
      rooms.Invites.off("sync", onInvites);
    };
  }, []);

  return (
    <Tab name="Chat" isNagging={false}>
      <ul>
        {Array.from(subscriptions)
          .sort((a, b) => {
            const aLast = lastEvent[a];
            const bLast = lastEvent[b];
            return (
              (bLast?.getDate()?.getTime() ?? 0) -
              (aLast?.getDate()?.getTime() ?? 0)
            );
          })
          .map((roomId) => (
            <ChatItem
      <List>
        {invites > 0 && (
          <List.Item onClick={() => dispatch(["open", "invites"])}>
            invites {invites}
          </List.Item>
        )}
        {subs.map(({ roomId }) => (
          <RoomListItem
            key={roomId}
            roomId={roomId}
            onClick={() => dispatch(["openChat", roomId])}
          />
        ))}
      </ul>
      </List>
    </Tab>
  );
};

const ChatItem = ({
  roomId,
  onClick,
}: {
  roomId: MXRoomID;
  onClick: () => unknown;
}) => {
  const { name, recent, membership } = useRoom(roomId);

  return (
    <li className="flex flex-row gap-1 p-1 relative">
      <button
        className="absolute top-0 left-0 w-full h-full cursor-pointer"
        onClick={onClick}
      ></button>
      <div className="flex items-center justify-center p-0.5">
        <div className="w-8 h-8 shrink-0 rounded-xl bg-gray-500"></div>
      </div>
      <div className="flex flex-col gap-px">
        <span className="font-bold">{name || roomId}</span>
        <span className="text-xs text-gray-800 text-ellipsis max-w-full">
          {membership}
          <ShortEvent event={recent} />
        </span>
      </div>
    </li>
  );
};

const ShortEvent = ({ event }: { event?: MatrixEvent }) => {
  const { client } = MXClient.get();

  if (!event) return null;

  const content: JSX.Element[] = [];

  if (event.getSender() === client.getUserId()) {
    content.push(
      <>
        <strong>You:</strong>{" "}
      </>,
    );
  }

  switch (event.getType()) {
    case "m.room.encrypted":
      content.push(<em>TODO: Encryption</em>);
      break;
    case "m.room.message":
      content.push(<>{event.getContent().body}</>);
      break;
    default:
      content.push(<em>Unknown: {event.getType()}</em>);
  }

  return content;
};
});
+102 −21
Original line number Diff line number Diff line
@@ -4,6 +4,8 @@ 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 { MXClient } from "@/lib/MXClient";

export const RoomTab = ({
  roomId,
@@ -14,8 +16,27 @@ export const RoomTab = ({
}) => {
  const { dispatch } = useYap<"Internal">();
  const room = useRoom(roomId);
  const [message, setMessage] = useState("");
  const { messages, loading, atBeginning, loadMore } = useMessages(roomId);

  const sendMessage = useCallback(
    (e: React.SubmitEvent) => {
      e.preventDefault();

      const mxclient = MXClient.get();
      mxclient
        .sendMessage(roomId, message)
        .then((event) => {
          console.log("sent message", event);
          setMessage("");
        })
        .catch((e) => {
          console.error(e);
        });
    },
    [message, roomId],
  );

  return (
    <Tab
      name={room.name || `room: ${roomId}`}
@@ -24,6 +45,14 @@ export const RoomTab = ({
      onClose={() => dispatch(["closeChat", roomId])}
      onFocus={() => dispatch(["unnag", roomId])}
    >
      {room.membership === "invite" && (
        <ConfirmInvite
          roomId={roomId}
          onDecline={() => dispatch(["closeChat", roomId])}
        />
      )}
      {room.membership === "join" && (
        <>
          <div className="p-2 grow overflow-y-auto">
            {/* <ChatBubble role="ME" flags={MessageFlag.SENDING ^ MessageFlag.SEEN} />
      <ChatBubble role="SYSTEM" />
@@ -36,16 +65,68 @@ export const RoomTab = ({
            />
          </div>
          <div className="p-2">
        <form
          action="#"
          onSubmit={(e) => {
            e.preventDefault();
          }}
        >
          <input type="text" placeholder="New Message" />
            <form action="#" onSubmit={sendMessage}>
              <input
                type="text"
                placeholder="New Message"
                value={message}
                onChange={(e) => setMessage(e.currentTarget.value)}
              />
              <button type="submit">Send</button>
            </form>
          </div>
        </>
      )}
    </Tab>
  );
};

const ConfirmInvite = ({
  roomId,
  onDecline,
}: {
  onDecline: () => unknown;
  roomId: MXRoomID;
}) => {
  const room = useRoom(roomId);

  const doAccept = useCallback(() => {
    const mxclient = MXClient.get();
    mxclient.rooms
      .joinRoom(roomId)
      .then(() => {
        console.log("joined room");
      })
      .catch((e) => {
        console.error(e);
      });
  }, [roomId]);

  const doDecline = useCallback(() => {
    const mxclient = MXClient.get();
    mxclient.rooms
      .leaveRoom(roomId)
      .then(() => {
        console.log("left room");
        onDecline();
      })
      .catch((e) => {
        console.error(e);
      });
  }, [onDecline, roomId]);

  return (
    <div className="max-w-full text-wrap">
      @user:name.com invited you to {room.name}
      <button
        className="text-green-700 border-2 rounded p-1"
        onClick={doAccept}
      >
        accept
      </button>
      <button className="text-red-700 border-2 rounded p-1" onClick={doDecline}>
        decline
      </button>
    </div>
  );
};
+2 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ import { RoomTab } from "./Tab/Room";
import { getOpenRooms, useYap } from "@/context/utils";
import { useEffect } from "react";
import { LoggedOut } from "./Tab/LoggedOut";
import { InvitesTab } from "./Tab/Invites";

export const Yapper = () => {
  const { state } = useYap<"Internal">();
@@ -22,6 +23,7 @@ export const Yapper = () => {
              isNagging={room.nag}
            />
          ))}
          {state.namedTabs.invites && <InvitesTab />}
          <HomeTab />
        </>
      ) : (
+17 −0
Original line number Diff line number Diff line
@@ -58,11 +58,28 @@ export const YapContext = ({ children }: React.PropsWithChildren) => {
              },
            },
          };
        case "open":
          return {
            ...state,
            namedTabs: {
              ...state.namedTabs,
              [action[1]]: true,
            },
          };
        case "close":
          return {
            ...state,
            namedTabs: {
              ...state.namedTabs,
              [action[1]]: false,
            },
          };
      }
      return state;
    },
    {
      stage: "INIT",
      namedTabs: {},
      rooms: {},
    },
  );
Loading