Use hook to bind atoms with sdk

This commit is contained in:
Ajay Bura 2022-12-31 15:54:57 +05:30
parent 67cd2fc5c4
commit 147a6065d0
9 changed files with 356 additions and 158 deletions

View file

@ -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<string[], RoomsAction>
) => {
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<string[], RoomsAction>,
mDirectAtom: WritableAtom<Set<string>, 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<string[], RoomsAction>,
mDirectAtom: WritableAtom<Set<string>, 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<string[], RoomsAction>
) => {
const selector = useCallback(
(rooms: string[]) => rooms.filter((roomId) => isUnsupportedRoom(mx.getRoom(roomId))),
[mx]
);
return useAtomValue(selectAtom(allInvitesAtom, selector, compareRoomsEqual));
};

View file

@ -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<string[], RoomsAction>) => {
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<string[], RoomsAction>,
mDirectAtom: WritableAtom<Set<string>, 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<string[], RoomsAction>,
mDirectAtom: WritableAtom<Set<string>, 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<string[], RoomsAction>
) => {
const selector = useCallback(
(rooms: string[]) => rooms.filter((roomId) => isUnsupportedRoom(mx.getRoom(roomId))),
[mx]
);
return useAtomValue(selectAtom(allRoomsAtom, selector, compareRoomsEqual));
};

View file

@ -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<string[]>([]);
allInvitesAtom.onMount = (setAtom) => atomRoomsWithMemberships(setAtom, mx(), [Membership.Invite]);
export const spaceInvitesAtom = atom((get) =>
get(allInvitesAtom).filter((roomId) => isSpace(mx().getRoom(roomId)))
const baseRoomsAtom = atom<string[]>([]);
export const allInvitesAtom = atom<string[], RoomsAction>(
(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<string[], RoomsAction>
) => {
useBindRoomsWithMembershipsAtom(
mx,
allRooms,
useMemo(() => [Membership.Invite], [])
);
};

View file

@ -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<string>());
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<string>;
};
const baseMDirectAtom = atom(new Set<string>());
export const mDirectAtom = atom<Set<string>, MDirectAction>(
(get) => get(baseMDirectAtom),
(get, set, action) => {
set(baseMDirectAtom, action.rooms);
}
);
export const useBindMDirectAtom = (
mx: MatrixClient,
mDirect: WritableAtom<Set<string>, 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]);
};

View file

@ -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[];

View file

@ -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<string[]>([]);
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<string[]>([]);
export const allRoomsAtom = atom<string[], RoomsAction>(
(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<string[], RoomsAction>
) => {
useBindRoomsWithMembershipsAtom(
mx,
allRooms,
useMemo(() => [Membership.Join], [])
);
};

View file

@ -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<RoomToParents>(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<RoomToParents>(new Map());
export const roomToParentsAtom = atom<RoomToParents, RoomToParentsAction>(
(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<RoomToParents, RoomToParentsAction>
) => {
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]);
};

View file

@ -20,7 +20,7 @@ import {
} from '../utils/room';
import { roomToParentsAtom } from './roomToParents';
type RoomToUnreadAction =
export type RoomToUnreadAction =
| {
type: 'RESET';
unreadInfos: UnreadInfo[];

View file

@ -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<string[]>) => void,
mx: MatrixClient,
memberships: Membership[]
) => {
export type RoomsAction =
| {
type: 'INITIALIZE';
rooms: string[];
}
| {
type: 'PUT' | 'DELETE';
roomId: string;
};
export const useBindRoomsWithMembershipsAtom = (
mx: MatrixClient,
roomsAtom: WritableAtom<string[], RoomsAction>,
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]);
};