Commit 1b752216 authored by Grant's avatar Grant
Browse files

draft: matrix impl

parent 82f033d7
Loading
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
import { MessageFlag, type Message } from "@/lib/const";
import { useYap } from "@/lib/context";
import { useYap } from "@/context/utils";
import type React from "react";

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

export const ChatBubble = ({ message }: { message: Message }) => {
  const { userId, isSystem } = useYap();
  const { state } = useYap<"Internal">();
  const isSystem = (_str: string) => false;
  const role: MessageRole = isSystem(message.sender)
    ? "SYSTEM"
    : message.sender === userId
    : message.sender === state.user
      ? "ME"
      : "OTHER";
  const flags = message.flags;
+61 −22
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 { useReducer, type JSX } from "react";
import type { MatrixEvent } from "matrix-js-sdk";
import { MXClient } from "@/lib/MXClient";

const TEST_ROOMS = [
  "!room1:cool.chat",
  "!room2:bad.chat",
  "!room3:the.chat",
] as const;
export const HomeTab = () => {
  const { dispatch } = useYap<"Internal">();
  const { subscriptions, lastEvent } = useSubscriptions();

export const HomeTab = ({
  openRoom,
}: {
  openRoom: (roomId: `!${string}:${string}`) => unknown;
}) => {
  return (
    <Tab name="Chat" isNagging={false}>
      <ul>
        {TEST_ROOMS.map((roomId) => (
        {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
              key={roomId}
              roomId={roomId}
            onClick={() => openRoom(roomId)}
              onClick={() => dispatch(["openChat", roomId])}
            />
          ))}
      </ul>
@@ -30,9 +39,11 @@ const ChatItem = ({
  roomId,
  onClick,
}: {
  roomId: string;
  roomId: MXRoomID;
  onClick: () => unknown;
}) => {
  const { name, recent, membership } = useRoom(roomId);

  return (
    <li className="flex flex-row gap-1 p-1 relative">
      <button
@@ -43,13 +54,41 @@ const ChatItem = ({
        <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">{roomId}</span>
        <span className="font-bold">{name || roomId}</span>
        <span className="text-xs text-gray-800 text-ellipsis max-w-full">
          hey man you can't place therehey man you can't place therehey man you
          can't place therehey man you can't place therehey man you can't place
          there
          {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;
};
+7 −7
Original line number Diff line number Diff line
@@ -2,27 +2,27 @@ import { Tab } from "../Tab";
import { ChatLog } from "../ChatLog/ChatLog";
import type { MXRoomID } from "@/lib/const";
import { useMessages } from "@/hooks/useMessages";
import { useYap } from "@/context/utils";
import { useRoom } from "@/hooks/useRoom";

export const RoomTab = ({
  roomId,
  isNagging,
  onClose,
  onFocus,
}: {
  roomId: MXRoomID;
  isNagging: boolean;
  onClose: () => unknown;
  onFocus: () => unknown;
}) => {
  const { dispatch } = useYap<"Internal">();
  const room = useRoom(roomId);
  const { messages, loading, atBeginning, loadMore } = useMessages(roomId);

  return (
    <Tab
      name={`room: ${roomId}`}
      name={room.name || `room: ${roomId}`}
      isNagging={isNagging}
      className="flex flex-col"
      onClose={onClose}
      onFocus={onFocus}
      onClose={() => dispatch(["closeChat", roomId])}
      onFocus={() => dispatch(["unnag", roomId])}
    >
      <div className="p-2 grow overflow-y-auto">
        {/* <ChatBubble role="ME" flags={MessageFlag.SENDING ^ MessageFlag.SEEN} />
+7 −26
Original line number Diff line number Diff line
import type { MXUserID } from "@/lib/const";
import { HomeTab } from "./Tab/Home";
import { RoomTab } from "./Tab/Room";
import { ContextProvider, useYap } from "@/lib/context";
import type { YapController } from "@/YapController";
import { getOpenRooms, useYap } from "@/context/utils";
import { useEffect } from "react";

type Props = {
  userId: MXUserID;
  isSystem: (userId: MXUserID) => boolean;
};

export const Yapper = ({ controller }: { controller: YapController }) => {
  return (
    <ContextProvider userId={`@:`} isSystem={() => false}>
      <YapperInner />
    </ContextProvider>
  );
};

const YapperInner = () => {
  const { state, dispatch } = useYap();
export const Yapper = () => {
  const { state, dispatch } = useYap<"Internal">();

  return (
    <div
      id="chat-overlay"
      className="absolute bottom-0 right-0 flex gap-1 px-1"
    >
      {state.open.map((roomId) => (
        <RoomTab
          key={roomId}
          roomId={roomId}
          isNagging={state.nag.indexOf(roomId) > -1}
          onClose={() => dispatch(["close", roomId])}
          onFocus={() => dispatch(["unnag", roomId])}
        />
      {getOpenRooms(state).map((room) => (
        <RoomTab key={room.roomId} roomId={room.roomId} isNagging={room.nag} />
      ))}
      <HomeTab openRoom={(roomId) => dispatch(["open", roomId])} />
      <HomeTab />
    </div>
  );
};
+49 −44
Original line number Diff line number Diff line
import type { MXRoomID, MXUserID } from "@/lib/const";
import { MXClient } from "@/lib/MXClient";
import type React from "react";
import {
  createContext,
  useCallback,
  useContext,
  useMemo,
  useReducer,
} from "react";

type State = {
  stage: "INIT";
  user?: MXUserID;
};
type Actions = ["login"] | ["ready"];

type InternalAPI = {
  state: State;
  dispatch: React.ActionDispatch<[Actions]>;
};

type PublicAPI = {
  ready: () => unknown;
  doLogin: () => unknown;
  openChat: (withWho: MXUserID | MXRoomID) => unknown;
  setSystem: (handler: (mxid: MXUserID) => boolean) => unknown;
};

const context = createContext<InternalAPI & PublicAPI>(
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  null as any,
);

// eslint-disable-next-line react-refresh/only-export-components
export const useYap: <
  Mode extends "Internal" | "" = "",
>() => Mode extends "Internal" ? InternalAPI & PublicAPI : PublicAPI = () =>
  useContext(context);
import { useCallback, useMemo, useReducer } from "react";
import type { Actions, State } from "./types";
import { context } from "./utils";

export const YapContext = ({ children }: React.PropsWithChildren) => {
  // @ts-expect-error ignore
  const client = useMemo<MXClient>(() => new MXClient(), []);
  const client = useMemo<MXClient>(() => MXClient.get(), []);

  const [state, dispatch] = useReducer<State, [Actions]>(
    (state, [action, ...data]) => {
      switch (action) {
        case "login":
          window.open(client.getLoginUrl(window.location.href), "_blank");
          return state;
    (state, action) => {
      switch (action[0]) {
        case "ready":
          return state;
        case "openChat":
          return {
            ...state,
            rooms: {
              ...state.rooms,
              [action[1]]: {
                nag: true,
                open: true,
              },
            },
          };
        case "closeChat":
          return {
            ...state,
            rooms: {
              ...state.rooms,
              [action[1]]: {
                ...state.rooms[action[1]],
                open: false,
              },
            },
          };
        case "unnag":
          return {
            ...state,
            rooms: {
              ...state.rooms,
              [action[1]]: {
                ...state.rooms[action[1]],
                nag: false,
              },
            },
          };
      }
      return state;
    },
    {
      stage: "INIT",
      rooms: {},
    },
  );

@@ -97,7 +94,15 @@ export const YapContext = ({ children }: React.PropsWithChildren) => {

  return (
    <context.Provider
      value={{ state, dispatch, doLogin, openChat, setSystem, ready }}
      value={{
        state,
        client,
        dispatch,
        doLogin,
        openChat,
        setSystem,
        ready,
      }}
    >
      {children}
    </context.Provider>
Loading