From 147a6065d0429ca813d0f7d7f5e6bbc010146fa5 Mon Sep 17 00:00:00 2001 From: Ajay Bura <32841439+ajbura@users.noreply.github.com> Date: Sat, 31 Dec 2022 15:54:57 +0530 Subject: [PATCH] Use hook to bind atoms with sdk --- src/app/state/hooks/inviteList.ts | 63 ++++++++++++ src/app/state/hooks/roomList.ts | 54 ++++++++++ src/app/state/inviteList.ts | 56 +++++----- src/app/state/mDirectList.ts | 59 ++++++++--- src/app/state/mutedRoomList.ts | 2 +- src/app/state/roomList.ts | 52 +++++----- src/app/state/roomToParents.ts | 165 +++++++++++++++++++----------- src/app/state/roomToUnread.ts | 2 +- src/app/state/utils.ts | 61 ++++++----- 9 files changed, 356 insertions(+), 158 deletions(-) create mode 100644 src/app/state/hooks/inviteList.ts create mode 100644 src/app/state/hooks/roomList.ts diff --git a/src/app/state/hooks/inviteList.ts b/src/app/state/hooks/inviteList.ts new file mode 100644 index 00000000..f8b7e057 --- /dev/null +++ b/src/app/state/hooks/inviteList.ts @@ -0,0 +1,63 @@ +import { useAtomValue, WritableAtom } from 'jotai'; +import { selectAtom } from 'jotai/utils'; +import { MatrixClient } from 'matrix-js-sdk'; +import { useCallback } from 'react'; +import { isDirectInvite, isRoom, isSpace, isUnsupportedRoom } from '../../utils/room'; +import { compareRoomsEqual, RoomsAction } from '../utils'; +import { MDirectAction } from '../mDirectList'; + +export const useSpaceInvites = ( + mx: MatrixClient, + allInvitesAtom: WritableAtom +) => { + const selector = useCallback( + (rooms: string[]) => rooms.filter((roomId) => isSpace(mx.getRoom(roomId))), + [mx] + ); + return useAtomValue(selectAtom(allInvitesAtom, selector, compareRoomsEqual)); +}; + +export const useRoomInvites = ( + mx: MatrixClient, + allInvitesAtom: WritableAtom, + mDirectAtom: WritableAtom, MDirectAction> +) => { + const mDirects = useAtomValue(mDirectAtom); + const selector = useCallback( + (rooms: string[]) => + rooms.filter( + (roomId) => + isRoom(mx.getRoom(roomId)) && + !(mDirects.has(roomId) || isDirectInvite(mx.getRoom(roomId), mx.getUserId())) + ), + [mx, mDirects] + ); + return useAtomValue(selectAtom(allInvitesAtom, selector, compareRoomsEqual)); +}; + +export const useDirectInvites = ( + mx: MatrixClient, + allInvitesAtom: WritableAtom, + mDirectAtom: WritableAtom, MDirectAction> +) => { + const mDirects = useAtomValue(mDirectAtom); + const selector = useCallback( + (rooms: string[]) => + rooms.filter( + (roomId) => mDirects.has(roomId) || isDirectInvite(mx.getRoom(roomId), mx.getUserId()) + ), + [mx, mDirects] + ); + return useAtomValue(selectAtom(allInvitesAtom, selector, compareRoomsEqual)); +}; + +export const useUnsupportedInvites = ( + mx: MatrixClient, + allInvitesAtom: WritableAtom +) => { + const selector = useCallback( + (rooms: string[]) => rooms.filter((roomId) => isUnsupportedRoom(mx.getRoom(roomId))), + [mx] + ); + return useAtomValue(selectAtom(allInvitesAtom, selector, compareRoomsEqual)); +}; diff --git a/src/app/state/hooks/roomList.ts b/src/app/state/hooks/roomList.ts new file mode 100644 index 00000000..5d0890bd --- /dev/null +++ b/src/app/state/hooks/roomList.ts @@ -0,0 +1,54 @@ +import { useAtomValue, WritableAtom } from 'jotai'; +import { selectAtom } from 'jotai/utils'; +import { MatrixClient } from 'matrix-js-sdk'; +import { useCallback } from 'react'; +import { isRoom, isSpace, isUnsupportedRoom } from '../../utils/room'; +import { compareRoomsEqual, RoomsAction } from '../utils'; +import { MDirectAction } from '../mDirectList'; + +export const useSpaces = (mx: MatrixClient, allRoomsAtom: WritableAtom) => { + const selector = useCallback( + (rooms: string[]) => rooms.filter((roomId) => isSpace(mx.getRoom(roomId))), + [mx] + ); + return useAtomValue(selectAtom(allRoomsAtom, selector, compareRoomsEqual)); +}; + +export const useRooms = ( + mx: MatrixClient, + allRoomsAtom: WritableAtom, + mDirectAtom: WritableAtom, MDirectAction> +) => { + const mDirects = useAtomValue(mDirectAtom); + const selector = useCallback( + (rooms: string[]) => + rooms.filter((roomId) => isRoom(mx.getRoom(roomId)) && !mDirects.has(roomId)), + [mx, mDirects] + ); + return useAtomValue(selectAtom(allRoomsAtom, selector, compareRoomsEqual)); +}; + +export const useDirects = ( + mx: MatrixClient, + allRoomsAtom: WritableAtom, + mDirectAtom: WritableAtom, MDirectAction> +) => { + const mDirects = useAtomValue(mDirectAtom); + const selector = useCallback( + (rooms: string[]) => + rooms.filter((roomId) => isRoom(mx.getRoom(roomId)) && mDirects.has(roomId)), + [mx, mDirects] + ); + return useAtomValue(selectAtom(allRoomsAtom, selector, compareRoomsEqual)); +}; + +export const useUnsupportedRooms = ( + mx: MatrixClient, + allRoomsAtom: WritableAtom +) => { + const selector = useCallback( + (rooms: string[]) => rooms.filter((roomId) => isUnsupportedRoom(mx.getRoom(roomId))), + [mx] + ); + return useAtomValue(selectAtom(allRoomsAtom, selector, compareRoomsEqual)); +}; diff --git a/src/app/state/inviteList.ts b/src/app/state/inviteList.ts index 82c686c3..463fd352 100644 --- a/src/app/state/inviteList.ts +++ b/src/app/state/inviteList.ts @@ -1,32 +1,32 @@ -import { atom } from 'jotai'; -import { mx } from '../../client/mx'; +import { atom, WritableAtom } from 'jotai'; +import { MatrixClient } from 'matrix-js-sdk'; +import { useMemo } from 'react'; import { Membership } from '../../types/matrix/room'; -import { isDirectInvite, isRoom, isSpace, isUnsupportedRoom } from '../utils/room'; -import { mDirectAtom } from './mDirectList'; -import { atomRoomsWithMemberships } from './utils'; +import { RoomsAction, useBindRoomsWithMembershipsAtom } from './utils'; -export const allInvitesAtom = atom([]); -allInvitesAtom.onMount = (setAtom) => atomRoomsWithMemberships(setAtom, mx(), [Membership.Invite]); - -export const spaceInvitesAtom = atom((get) => - get(allInvitesAtom).filter((roomId) => isSpace(mx().getRoom(roomId))) +const baseRoomsAtom = atom([]); +export const allInvitesAtom = atom( + (get) => get(baseRoomsAtom), + (get, set, action) => { + if (action.type === 'INITIALIZE') { + set(baseRoomsAtom, action.rooms); + return; + } + set(baseRoomsAtom, (ids) => { + const newIds = ids.filter((id) => id !== action.roomId); + if (action.type === 'PUT') newIds.push(action.roomId); + return newIds; + }); + } ); -export const roomInvitesAtom = atom((get) => - get(allInvitesAtom).filter( - (roomId) => - isRoom(mx().getRoom(roomId)) && - !(get(mDirectAtom).has(roomId) || isDirectInvite(mx().getRoom(roomId), mx().getUserId())) - ) -); - -export const directInvitesAtom = atom((get) => - get(allInvitesAtom).filter( - (roomId) => - get(mDirectAtom).has(roomId) || isDirectInvite(mx().getRoom(roomId), mx().getUserId()) - ) -); - -export const unsupportedInvitesAtom = atom((get) => - get(allInvitesAtom).filter((roomId) => isUnsupportedRoom(mx().getRoom(roomId))) -); +export const useBindAllInvitesAtom = ( + mx: MatrixClient, + allRooms: WritableAtom +) => { + useBindRoomsWithMembershipsAtom( + mx, + allRooms, + useMemo(() => [Membership.Invite], []) + ); +}; diff --git a/src/app/state/mDirectList.ts b/src/app/state/mDirectList.ts index 263af4ad..96e2f0d0 100644 --- a/src/app/state/mDirectList.ts +++ b/src/app/state/mDirectList.ts @@ -1,20 +1,47 @@ -import { atom } from 'jotai'; -import { ClientEvent, MatrixEvent } from 'matrix-js-sdk'; -import { mx } from '../../client/mx'; +import { atom, useSetAtom, WritableAtom } from 'jotai'; +import { ClientEvent, MatrixClient, MatrixEvent } from 'matrix-js-sdk'; +import { useEffect } from 'react'; import { AccountDataEvent } from '../../types/matrix/accountData'; import { getAccountData, getMDirects } from '../utils/room'; -export const mDirectAtom = atom(new Set()); -mDirectAtom.onMount = (setAtom) => { - const mDirectEvent = getAccountData(mx(), AccountDataEvent.Direct); - if (mDirectEvent) setAtom(getMDirects(mDirectEvent)); - - const handleAccountData = (event: MatrixEvent) => { - setAtom(getMDirects(event)); - }; - - mx().on(ClientEvent.AccountData, handleAccountData); - return () => { - mx().removeListener(ClientEvent.AccountData, handleAccountData); - }; +export type MDirectAction = { + type: 'INITIALIZE' | 'UPDATE'; + rooms: Set; +}; + +const baseMDirectAtom = atom(new Set()); +export const mDirectAtom = atom, MDirectAction>( + (get) => get(baseMDirectAtom), + (get, set, action) => { + set(baseMDirectAtom, action.rooms); + } +); + +export const useBindMDirectAtom = ( + mx: MatrixClient, + mDirect: WritableAtom, MDirectAction> +) => { + const setMDirect = useSetAtom(mDirect); + + useEffect(() => { + const mDirectEvent = getAccountData(mx, AccountDataEvent.Direct); + if (mDirectEvent) { + setMDirect({ + type: 'INITIALIZE', + rooms: getMDirects(mDirectEvent), + }); + } + + const handleAccountData = (event: MatrixEvent) => { + setMDirect({ + type: 'UPDATE', + rooms: getMDirects(event), + }); + }; + + mx.on(ClientEvent.AccountData, handleAccountData); + return () => { + mx.removeListener(ClientEvent.AccountData, handleAccountData); + }; + }, [mx, setMDirect]); }; diff --git a/src/app/state/mutedRoomList.ts b/src/app/state/mutedRoomList.ts index a0ad66f3..7df16699 100644 --- a/src/app/state/mutedRoomList.ts +++ b/src/app/state/mutedRoomList.ts @@ -4,7 +4,7 @@ import { useEffect } from 'react'; import { MuteChanges } from '../../types/matrix/room'; import { findMutedRule, isMutedRule } from '../utils/room'; -type MutedRoomsUpdate = +export type MutedRoomsUpdate = | { type: 'INITIALIZE'; addRooms: string[]; diff --git a/src/app/state/roomList.ts b/src/app/state/roomList.ts index 64db835b..7a793d8c 100644 --- a/src/app/state/roomList.ts +++ b/src/app/state/roomList.ts @@ -1,27 +1,31 @@ -import { atom } from 'jotai'; -import { mx } from '../../client/mx'; +import { atom, WritableAtom } from 'jotai'; +import { MatrixClient } from 'matrix-js-sdk'; +import { useMemo } from 'react'; import { Membership } from '../../types/matrix/room'; -import { isRoom, isSpace, isUnsupportedRoom } from '../utils/room'; -import { mDirectAtom } from './mDirectList'; -import { atomRoomsWithMemberships } from './utils'; +import { RoomsAction, useBindRoomsWithMembershipsAtom } from './utils'; -export const allRoomsAtom = atom([]); -allRoomsAtom.onMount = (setAtom) => atomRoomsWithMemberships(setAtom, mx(), [Membership.Join]); - -export const spacesAtom = atom((get) => - get(allRoomsAtom).filter((roomId) => isSpace(mx().getRoom(roomId))) -); - -export const roomsAtom = atom((get) => - get(allRoomsAtom).filter( - (roomId) => isRoom(mx().getRoom(roomId)) && !get(mDirectAtom).has(roomId) - ) -); - -export const directsAtom = atom((get) => - get(allRoomsAtom).filter((roomId) => isRoom(mx().getRoom(roomId)) && get(mDirectAtom).has(roomId)) -); - -export const unsupportedRoomsAtom = atom((get) => - get(allRoomsAtom).filter((roomId) => isUnsupportedRoom(mx().getRoom(roomId))) +const baseRoomsAtom = atom([]); +export const allRoomsAtom = atom( + (get) => get(baseRoomsAtom), + (get, set, action) => { + if (action.type === 'INITIALIZE') { + set(baseRoomsAtom, action.rooms); + return; + } + set(baseRoomsAtom, (ids) => { + const newIds = ids.filter((id) => id !== action.roomId); + if (action.type === 'PUT') newIds.push(action.roomId); + return newIds; + }); + } ); +export const useBindAllRoomsAtom = ( + mx: MatrixClient, + allRooms: WritableAtom +) => { + useBindRoomsWithMembershipsAtom( + mx, + allRooms, + useMemo(() => [Membership.Join], []) + ); +}; diff --git a/src/app/state/roomToParents.ts b/src/app/state/roomToParents.ts index 2f761e94..374ddd57 100644 --- a/src/app/state/roomToParents.ts +++ b/src/app/state/roomToParents.ts @@ -1,7 +1,14 @@ import produce from 'immer'; -import { atom } from 'jotai'; -import { ClientEvent, MatrixEvent, Room, RoomEvent, RoomStateEvent } from 'matrix-js-sdk'; -import { mx } from '../../client/mx'; +import { atom, useSetAtom, WritableAtom } from 'jotai'; +import { + ClientEvent, + MatrixClient, + MatrixEvent, + Room, + RoomEvent, + RoomStateEvent, +} from 'matrix-js-sdk'; +import { useEffect } from 'react'; import { Membership, RoomToParents, StateEvent } from '../../types/matrix/room'; import { getRoomToParents, @@ -11,69 +18,103 @@ import { mapParentWithChildren, } from '../utils/room'; -export const roomToParentsAtom = atom(new Map()); -roomToParentsAtom.onMount = (setAtom) => { - setAtom(getRoomToParents(mx())); - - const deleteFromAtom = (roomId: string) => { - setAtom( - produce((roomToParents) => { - const noParentRooms: string[] = []; - roomToParents.delete(roomId); - roomToParents.forEach((parents, child) => { - parents.delete(roomId); - if (parents.size === 0) noParentRooms.push(child); - }); - noParentRooms.forEach((room) => roomToParents.delete(room)); - return roomToParents; - }) - ); - }; - - const addToAtom = (parent: string, children: string[]) => { - setAtom( - produce((roomToParents) => { - mapParentWithChildren(roomToParents, parent, children); - return roomToParents; - }) - ); - }; - - const handleAddRoom = (room: Room) => { - if (isSpace(room) && room.getMyMembership() !== Membership.Invite) { - addToAtom(room.roomId, getSpaceChildren(room)); +export type RoomToParentsAction = + | { + type: 'INITIALIZE'; + roomToParents: RoomToParents; } - }; - - const handleMembershipChange = (room: Room, membership: string) => { - if (isSpace(room) && membership === Membership.Join) { - addToAtom(room.roomId, getSpaceChildren(room)); + | { + type: 'PUT'; + parent: string; + children: string[]; } - }; + | { + type: 'DELETE'; + roomId: string; + }; - const handleStateChange = (mEvent: MatrixEvent) => { - if (mEvent.getType() === StateEvent.SpaceChild) { - const childId = mEvent.getStateKey(); - const roomId = mEvent.getRoomId(); - if (childId && roomId) { - if (isValidChild(mEvent)) addToAtom(roomId, [childId]); - else deleteFromAtom(childId); +const baseRoomToParents = atom(new Map()); +export const roomToParentsAtom = atom( + (get) => get(baseRoomToParents), + (get, set, action) => { + if (action.type === 'INITIALIZE') { + set(baseRoomToParents, action.roomToParents); + return; + } + if (action.type === 'PUT') { + set( + baseRoomToParents, + produce(get(baseRoomToParents), (draftRoomToParents) => { + mapParentWithChildren(draftRoomToParents, action.parent, action.children); + }) + ); + return; + } + if (action.type === 'DELETE') { + set( + baseRoomToParents, + produce(get(baseRoomToParents), (draftRoomToParents) => { + const noParentRooms: string[] = []; + draftRoomToParents.delete(action.roomId); + draftRoomToParents.forEach((parents, child) => { + parents.delete(action.roomId); + if (parents.size === 0) noParentRooms.push(child); + }); + noParentRooms.forEach((room) => draftRoomToParents.delete(room)); + }) + ); + } + } +); + +export const useBindRoomToParentsAtom = ( + mx: MatrixClient, + roomToParents: WritableAtom +) => { + const setRoomToParents = useSetAtom(roomToParents); + + useEffect(() => { + setRoomToParents({ type: 'INITIALIZE', roomToParents: getRoomToParents(mx) }); + + const handleAddRoom = (room: Room) => { + if (isSpace(room) && room.getMyMembership() !== Membership.Invite) { + setRoomToParents({ type: 'PUT', parent: room.roomId, children: getSpaceChildren(room) }); } - } - }; + }; - const handleDeleteRoom = (roomId: string) => { - deleteFromAtom(roomId); - }; + const handleMembershipChange = (room: Room, membership: string) => { + if (isSpace(room) && membership === Membership.Join) { + setRoomToParents({ type: 'PUT', parent: room.roomId, children: getSpaceChildren(room) }); + } + }; - mx().on(ClientEvent.Room, handleAddRoom); - mx().on(RoomEvent.MyMembership, handleMembershipChange); - mx().on(RoomStateEvent.Events, handleStateChange); - mx().on(ClientEvent.DeleteRoom, handleDeleteRoom); - return () => { - mx().removeListener(ClientEvent.Room, handleAddRoom); - mx().removeListener(RoomEvent.MyMembership, handleMembershipChange); - mx().removeListener(RoomStateEvent.Events, handleStateChange); - mx().removeListener(ClientEvent.DeleteRoom, handleDeleteRoom); - }; + const handleStateChange = (mEvent: MatrixEvent) => { + if (mEvent.getType() === StateEvent.SpaceChild) { + const childId = mEvent.getStateKey(); + const roomId = mEvent.getRoomId(); + if (childId && roomId) { + if (isValidChild(mEvent)) { + setRoomToParents({ type: 'PUT', parent: roomId, children: [childId] }); + } else { + setRoomToParents({ type: 'DELETE', roomId: childId }); + } + } + } + }; + + const handleDeleteRoom = (roomId: string) => { + setRoomToParents({ type: 'DELETE', roomId }); + }; + + mx.on(ClientEvent.Room, handleAddRoom); + mx.on(RoomEvent.MyMembership, handleMembershipChange); + mx.on(RoomStateEvent.Events, handleStateChange); + mx.on(ClientEvent.DeleteRoom, handleDeleteRoom); + return () => { + mx.removeListener(ClientEvent.Room, handleAddRoom); + mx.removeListener(RoomEvent.MyMembership, handleMembershipChange); + mx.removeListener(RoomStateEvent.Events, handleStateChange); + mx.removeListener(ClientEvent.DeleteRoom, handleDeleteRoom); + }; + }, [mx, setRoomToParents]); }; diff --git a/src/app/state/roomToUnread.ts b/src/app/state/roomToUnread.ts index 8c9ec822..0c7b6bd6 100644 --- a/src/app/state/roomToUnread.ts +++ b/src/app/state/roomToUnread.ts @@ -20,7 +20,7 @@ import { } from '../utils/room'; import { roomToParentsAtom } from './roomToParents'; -type RoomToUnreadAction = +export type RoomToUnreadAction = | { type: 'RESET'; unreadInfos: UnreadInfo[]; diff --git a/src/app/state/utils.ts b/src/app/state/utils.ts index 04abdc1e..355c9411 100644 --- a/src/app/state/utils.ts +++ b/src/app/state/utils.ts @@ -1,46 +1,50 @@ -import { SetStateAction } from 'jotai'; +import { useSetAtom, WritableAtom } from 'jotai'; import { ClientEvent, MatrixClient, Room, RoomEvent } from 'matrix-js-sdk'; +import { useEffect } from 'react'; import { Membership } from '../../types/matrix/room'; -import { disposable } from '../utils/disposable'; -export const atomRoomsWithMemberships = disposable( - ( - setAtom: (update: SetStateAction) => void, - mx: MatrixClient, - memberships: Membership[] - ) => { +export type RoomsAction = + | { + type: 'INITIALIZE'; + rooms: string[]; + } + | { + type: 'PUT' | 'DELETE'; + roomId: string; + }; + +export const useBindRoomsWithMembershipsAtom = ( + mx: MatrixClient, + roomsAtom: WritableAtom, + memberships: Membership[] +) => { + const setRoomsAtom = useSetAtom(roomsAtom); + + useEffect(() => { const satisfyMembership = (room: Room): boolean => !!memberships.find((membership) => membership === room.getMyMembership()); - - setAtom( - mx + setRoomsAtom({ + type: 'INITIALIZE', + rooms: mx .getRooms() .filter(satisfyMembership) - .map((room) => room.roomId) - ); - - const updateAtom = (type: 'PUT' | 'DELETE', roomId: string) => { - setAtom((ids) => { - const newIds = ids.filter((id) => id !== roomId); - if (type === 'PUT') newIds.push(roomId); - return newIds; - }); - }; + .map((room) => room.roomId), + }); const handleAddRoom = (room: Room) => { if (satisfyMembership(room)) { - updateAtom('PUT', room.roomId); + setRoomsAtom({ type: 'PUT', roomId: room.roomId }); } }; const handleMembershipChange = (room: Room) => { if (!satisfyMembership(room)) { - updateAtom('DELETE', room.roomId); + setRoomsAtom({ type: 'DELETE', roomId: room.roomId }); } }; const handleDeleteRoom = (roomId: string) => { - updateAtom('DELETE', roomId); + setRoomsAtom({ type: 'DELETE', roomId }); }; mx.on(ClientEvent.Room, handleAddRoom); @@ -51,5 +55,10 @@ export const atomRoomsWithMemberships = disposable( mx.removeListener(RoomEvent.MyMembership, handleMembershipChange); mx.removeListener(ClientEvent.DeleteRoom, handleDeleteRoom); }; - } -); + }, [mx, memberships, setRoomsAtom]); +}; + +export const compareRoomsEqual = (a: string[], b: string[]) => { + if (a.length !== b.length) return false; + return a.every((roomId, roomIdIndex) => roomId === b[roomIdIndex]); +};