cetirizine

An experimental matrix client written in reactjs utilizing tailwind and storybook
git clone git://archive.git.mtrnord.blog/MTRNord/cetirizine.git
Log | Files | Refs | README | LICENSE

commit 9298a3768f7556c347ccb201761d72fd1a9fb799
parent d029747c0244485d21a4039fadb49309dc656283
Author: MTRNord <mtrnord1@gmail.com>
Date:   Tue,  2 May 2023 22:43:49 +0200

Memo all the stuff and make sure to only render leftOverRooms under others

Diffstat:
Msrc/components/avatar/avatar.tsx | 6+++---
Msrc/components/button/button.tsx | 8++++----
Msrc/components/header/header.tsx | 6+++---
Msrc/components/input/basic/input.tsx | 8++++----
Msrc/components/input/chat/input.tsx | 6+++---
Msrc/components/input/chat/plugins/ToolbarPlugin.tsx | 28++++++++++++++++------------
Msrc/components/login/login.tsx | 10++++++----
Msrc/components/roomList/roomList.tsx | 17+++++++++--------
Msrc/components/roomList/roomListItem/roomListItem.tsx | 6+++---
Msrc/pages/LoginPage.tsx | 7++++---
Msrc/pages/MainPage.tsx | 31+++++++++++++++++++++++++++----
11 files changed, 82 insertions(+), 51 deletions(-)

diff --git a/src/components/avatar/avatar.tsx b/src/components/avatar/avatar.tsx @@ -1,4 +1,4 @@ -import { FC } from "react"; +import { FC, memo } from "react"; type AvatarProps = { /** @@ -19,7 +19,7 @@ type AvatarProps = { online: boolean }; -const Avatar: FC<AvatarProps> = ({ avatarUrl, displayname, dm = false, online = false }: AvatarProps) => { +const Avatar: FC<AvatarProps> = memo(({ avatarUrl, displayname, dm = false, online = false }: AvatarProps) => { if (avatarUrl) { return ( <div className="flex relative min-w-[2rem] min-h-[2rem] justify-center items-center m-0 mr-3 text-xl rounded-full text-white"> @@ -50,6 +50,6 @@ const Avatar: FC<AvatarProps> = ({ avatarUrl, displayname, dm = false, online = } </div> ); -} +}) export default Avatar; \ No newline at end of file diff --git a/src/components/button/button.tsx b/src/components/button/button.tsx @@ -25,7 +25,7 @@ type ButtonProps = { readonly: boolean }; -const Button: FC<ButtonProps> = ({ type = "button", style = "primary", onClick, children, readonly }: ButtonProps) => { +const Button: FC<ButtonProps> = memo(({ type = "button", style = "primary", onClick, children, readonly }: ButtonProps) => { if (style === "secondary") { return <button disabled={readonly} onClick={onClick} className="button bg-orange-400 hover:bg-orange-500 ease-out duration-150 disabled:bg-slate-200 disabled:cursor-not-allowed" type={type}>{children}</button>; } else if (style === "abort") { @@ -33,5 +33,5 @@ const Button: FC<ButtonProps> = ({ type = "button", style = "primary", onClick, } else { return <button disabled={readonly} onClick={onClick} className="button bg-green-400 hover:bg-green-500 ease-out duration-150 disabled:bg-slate-200 disabled:cursor-not-allowed" type={type}>{children}</button>; } -} -export default memo(Button); -\ No newline at end of file +}) +export default Button; +\ No newline at end of file diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx @@ -1,4 +1,4 @@ -import { FC } from "react"; +import { FC, memo } from "react"; type HeaderProps = { /** @@ -7,8 +7,8 @@ type HeaderProps = { children: string }; -const Header: FC<HeaderProps> = ({ children }: HeaderProps) => { +const Header: FC<HeaderProps> = memo(({ children }: HeaderProps) => { return <h1 className='text-black font-bold text-xl'>{children}</h1>; -} +}) export default Header \ No newline at end of file diff --git a/src/components/input/basic/input.tsx b/src/components/input/basic/input.tsx @@ -27,7 +27,7 @@ type InputProps = { onChange: (e: ChangeEvent<HTMLInputElement>) => void; }; -const Input: FC<InputProps> = ({ placeholder, password = false, autoFocus = false, value, readonly, onChange }: InputProps) => { +const Input: FC<InputProps> = memo(({ placeholder, password = false, autoFocus = false, value, readonly, onChange }: InputProps) => { return ( <input disabled={readonly} @@ -39,5 +39,5 @@ const Input: FC<InputProps> = ({ placeholder, password = false, autoFocus = fals onChange={onChange} /> ); -} -export default memo(Input); -\ No newline at end of file +}) +export default Input; +\ No newline at end of file diff --git a/src/components/input/chat/input.tsx b/src/components/input/chat/input.tsx @@ -21,7 +21,7 @@ import CodeHighlightPlugin from './plugins/CodeHighlightPlugin'; import EditorTheme from './theme'; import './input.scss'; -import { FC } from 'react'; +import { FC, memo } from 'react'; type ChatInputProps = { /** @@ -42,7 +42,7 @@ function Placeholder() { return <div className="editor-placeholder" id="editor-placeholder">Enter message...</div>; } -const ChatInput: FC<ChatInputProps> = ({ namespace, onChange, onError }: ChatInputProps) => { +const ChatInput: FC<ChatInputProps> = memo(({ namespace, onChange, onError }: ChatInputProps) => { const initialConfig: InitialConfigType = { namespace: namespace, theme: EditorTheme, @@ -81,6 +81,6 @@ const ChatInput: FC<ChatInputProps> = ({ namespace, onChange, onError }: ChatInp </div> </LexicalComposer> ); -} +}) export default ChatInput; \ No newline at end of file diff --git a/src/components/input/chat/plugins/ToolbarPlugin.tsx b/src/components/input/chat/plugins/ToolbarPlugin.tsx @@ -10,7 +10,8 @@ import { MutableRefObject, Dispatch, SetStateAction, - createElement + createElement, + memo } from "react"; import { CAN_REDO_COMMAND, @@ -97,9 +98,9 @@ const blockTypeToBlockName: BlockTypes = { type BlockType = keyof typeof blockTypeToBlockName; -function Divider() { +const Divider = memo(() => { return <div className="divider" />; -} +}) function positionEditorElement(editor: HTMLDivElement, rect: DOMRect | undefined) { if (!rect) { @@ -114,7 +115,7 @@ function positionEditorElement(editor: HTMLDivElement, rect: DOMRect | undefined } } -function FloatingLinkEditor({ editor }: { editor: LexicalEditor }) { +const FloatingLinkEditor = memo(({ editor }: { editor: LexicalEditor }) => { const editorRef: MutableRefObject<HTMLDivElement | null> = useRef(null); const inputRef: MutableRefObject<HTMLInputElement | null> = useRef(null); const mouseDownRef = useRef(false); @@ -252,9 +253,9 @@ function FloatingLinkEditor({ editor }: { editor: LexicalEditor }) { )} </div> ); -} +}) -function Select({ onChange, className, options, value }: { onChange: ChangeEventHandler<HTMLSelectElement>, className: string, options: string[], value: string | ReadonlyArray<string> | number }) { +const Select = memo(({ onChange, className, options, value }: { onChange: ChangeEventHandler<HTMLSelectElement>, className: string, options: string[], value: string | ReadonlyArray<string> | number }) => { return ( <select className={className} onChange={onChange} value={value}> <option hidden={true} value="" /> @@ -265,7 +266,7 @@ function Select({ onChange, className, options, value }: { onChange: ChangeEvent ))} </select> ); -} +}) function getSelectedNode(selection: RangeSelection | GridSelection) { const anchor = selection.anchor; @@ -283,12 +284,12 @@ function getSelectedNode(selection: RangeSelection | GridSelection) { } } -function BlockOptionsDropdownList({ +const BlockOptionsDropdownList = memo(({ editor, blockType, toolbarRef, setShowBlockOptionsDropDown -}: { editor: LexicalEditor, blockType: BlockType, toolbarRef: MutableRefObject<HTMLDivElement | null>, setShowBlockOptionsDropDown: Dispatch<SetStateAction<boolean>> }) { +}: { editor: LexicalEditor, blockType: BlockType, toolbarRef: MutableRefObject<HTMLDivElement | null>, setShowBlockOptionsDropDown: Dispatch<SetStateAction<boolean>> }) => { const dropDownRef: MutableRefObject<HTMLDivElement | null> = useRef(null); useEffect(() => { @@ -433,9 +434,9 @@ function BlockOptionsDropdownList({ </button> </div> ); -} +}) -export default function ToolbarPlugin() { +const ToolbarPlugin = memo(() => { const [editor] = useLexicalComposerContext(); const toolbarRef = useRef(null); const [canUndo, setCanUndo] = useState(false); @@ -711,4 +712,6 @@ export default function ToolbarPlugin() { )} </div> ); -} +}) + +export default ToolbarPlugin; +\ No newline at end of file diff --git a/src/components/login/login.tsx b/src/components/login/login.tsx @@ -1,11 +1,11 @@ -import { useContext, useState } from 'react'; +import { memo, useContext, useState } from 'react'; import Button from '../button/button'; import Header from '../header/header'; import Input from '../input/basic/input'; import { Navigate } from 'react-router-dom'; import { MatrixContext } from '../../app/sdk/client'; -export function Login() { +const Login = memo(() => { const matrixClient = useContext(MatrixContext); const [loginPending, setLoginPending] = useState(false); const [loginError, setLoginError] = useState(''); @@ -52,4 +52,6 @@ export function Login() { </Button> </form> ); -} -\ No newline at end of file +}) + +export default Login; +\ No newline at end of file diff --git a/src/components/roomList/roomList.tsx b/src/components/roomList/roomList.tsx @@ -1,4 +1,4 @@ -import { FC, useState } from "react"; +import { FC, memo, useState } from "react"; import RoomListItem from "./roomListItem/roomListItem"; import { ChevronDown, ChevronRight } from "lucide-react"; import './roomList.scss'; @@ -79,13 +79,14 @@ type RoomListRoomsProps = { hidden: boolean }; -const RoomListRooms: FC<RoomListRoomsProps> = ({ sectionID, rooms, onClick, activeRoom, hidden }: RoomListRoomsProps) => { +const RoomListRooms: FC<RoomListRoomsProps> = memo(({ sectionID, rooms, onClick, activeRoom, hidden }: RoomListRoomsProps) => { + // Get room ids of rooms const roomsRendered = rooms.map(room => { return ( <RoomListItem roomId={room.roomID} hidden={hidden} - key={room.roomID + sectionID} + key={`${room.roomID}+${sectionID}`} avatarUrl={room.avatarUrl} displayname={room.displayname} dm={room.dm} @@ -100,9 +101,9 @@ const RoomListRooms: FC<RoomListRoomsProps> = ({ sectionID, rooms, onClick, acti {roomsRendered} </> ); -} +}) -const RoomSection: FC<{ section: Section, onRoomClick: (roomID: string) => void, activeRoom: string | undefined }> = ({ section, onRoomClick, activeRoom }: { section: Section, onRoomClick: (roomID: string) => void, activeRoom: string | undefined }) => { +const RoomSection: FC<{ section: Section, onRoomClick: (roomID: string) => void, activeRoom: string | undefined }> = memo(({ section, onRoomClick, activeRoom }: { section: Section, onRoomClick: (roomID: string) => void, activeRoom: string | undefined }) => { const [hidden, setHidden] = useState<boolean>(true); return ( <div key={section.roomID} className="flex flex-col gap-1 pl-4"> @@ -131,9 +132,9 @@ const RoomSection: FC<{ section: Section, onRoomClick: (roomID: string) => void, )} </div> ); -} +}) -const RoomList: FC<RoomListProps> = ({ sections, rooms }: RoomListProps) => { +const RoomList: FC<RoomListProps> = memo(({ sections, rooms }: RoomListProps) => { const [activeRoom, setActiveRoom] = useState<string | undefined>(undefined); return ( @@ -163,6 +164,6 @@ const RoomList: FC<RoomListProps> = ({ sections, rooms }: RoomListProps) => { </div> ); -} +}) export default RoomList \ No newline at end of file diff --git a/src/components/roomList/roomListItem/roomListItem.tsx b/src/components/roomList/roomListItem/roomListItem.tsx @@ -1,6 +1,6 @@ import { useInView } from "react-intersection-observer"; import Avatar from "../../avatar/avatar"; -import { FC, useContext, useEffect } from "react"; +import { FC, memo, useContext, useEffect } from "react"; import { MatrixContext } from "../../../app/sdk/client"; type RoomListItemProps = { @@ -38,7 +38,7 @@ type RoomListItemProps = { hidden: boolean }; -const RoomListItem: FC<RoomListItemProps> = ({ roomId, avatarUrl, displayname, dm = false, online = false, active = false, onClick, hidden }: RoomListItemProps) => { +const RoomListItem: FC<RoomListItemProps> = memo(({ roomId, avatarUrl, displayname, dm = false, online = false, active = false, onClick, hidden }: RoomListItemProps) => { const { ref, inView } = useInView({ triggerOnce: true, rootMargin: '200px 0px', @@ -69,6 +69,6 @@ const RoomListItem: FC<RoomListItemProps> = ({ roomId, avatarUrl, displayname, d } </div> ); -} +}) export default RoomListItem \ No newline at end of file diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx @@ -1,12 +1,13 @@ import './LoginPage.scss'; -import { Login } from '../components/login/login'; +import Login from '../components/login/login'; +import { memo } from 'react'; -function LoginPage() { +const LoginPage = memo(() => { return ( <div className="flex flex-col items-center justify-center min-h-screen bg-img"> <Login /> </div> ); -} +}) export default LoginPage; diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx @@ -5,7 +5,9 @@ import RoomList, { Section } from '../components/roomList/roomList'; import './MainPage.scss'; import { useProfile, useRooms, useSpaces } from '../app/sdk/client'; import { Room } from '../app/sdk/room'; -export default function MainPage() { +import { memo } from 'react'; + +const MainPage = memo(() => { const profile = useProfile(); const spacesWithRooms = useSpaces(); const rooms = useRooms(); @@ -22,6 +24,18 @@ export default function MainPage() { return not_a_child && no_parents; }); + // Filter rooms which are not part of any space and are not a space. + // A room is not part of any space if it is not a child of any space. + // A room is not a space if it has not any space as parent. + const leftOverRooms = [...rooms].filter(room => { + const not_a_child = ![...spacesWithRooms].some(({ children }) => { + return [...children].some(otherRoom => otherRoom.roomID === room.roomID); + }); + const no_parents = room.getSpaceParentIDs().length === 0; + const not_a_space = !room.isSpace(); + return not_a_child && no_parents && not_a_space; + }); + // Generate a list of sections. // Each section apart from special toplevel ones is a space. // Each space has a list of rooms and subsections. @@ -80,7 +94,7 @@ export default function MainPage() { }); // Add the toplevel section "Other" to the end of the list. - const otherRooms = [...rooms].filter(room => !room.isSpace()).map(room => { + const otherRooms = leftOverRooms.filter(room => !room.isSpace()).map(room => { return { roomID: room.roomID, displayname: room.getName(), @@ -90,6 +104,13 @@ export default function MainPage() { } }); + // Check and print if otherRooms has duplicates. + const otherRoomsIDs = otherRooms.map(room => room.roomID); + const otherRoomsDuplicates = otherRoomsIDs.filter((id, index) => otherRoomsIDs.indexOf(id) !== index); + if (otherRoomsDuplicates.length > 0) { + console.error('otherRooms has duplicates', otherRoomsDuplicates); + } + return <div className='flex flex-row w-full gap-2 min-h-screen h-screen'> < div className='flex flex-col bg-gradient-to-br from-slate-100 via-gray-200 to-orange-200 border-r-[1px] border-slate-300' > <div className='flex flex-row gap-2 m-2 p-1 items-center border-b-2'> @@ -107,4 +128,6 @@ export default function MainPage() { <ChatInput namespace='Editor' onChange={() => { }} onError={(e) => console.error(e)} /> </div> </div > -} -\ No newline at end of file +}) + +export default MainPage; +\ No newline at end of file