mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-12 10:10:29 +03:00
feat: URL navigation in auth (#1603)
* bump to react 18 and install react-router-dom * Upgrade to react 18 root * update vite * add cs api's * convert state/auth to ts * add client config context * add auto discovery context * add spec version context * add auth flow context * add background dot pattern css * add promise utils * init url based routing * update auth route server path as effect * add auth server hook * always use server from discovery info in context * login - WIP * upgrade jotai to v2 * add atom with localStorage util * add multi account sessions atom * add default IGNORE res to auto discovery * add error type in async callback hook * handle password login error * fix async callback hook * allow password login * Show custom server not allowed error in mxId login * add sso login component * add token login * fix hardcoded m.login.password in login func * update server input on url change * Improve sso login labels * update folds * fix async callback batching state update in safari * wrap async callback set state in queueMicrotask * wip * wip - register * arrange auth file structure * add error codes * extract filed error component form password login * add register util function * handle register flow - WIP * update unsupported auth flow method reasons * improve password input styles * Improve UIA flow next stage calculation complete stages can have any order so we will look for first stage which is not in completed * process register UIA flow stages * Extract register UIA stages component * improve register error messages * add focus trap & step count in UIA stages * add reset password path and path utils * add path with origin hook * fix sso redirect url * rename register token query param to token * restyle auth screen header * add reset password component - WIP * add reset password form * add netlify rewrites * fix netlify file indentation * test netlify redirect * fix vite to include netlify toml * add more netlify redirects * add splat to public and assets path * fix vite base name * add option to use hash router in config and remove appVersion * add splash screen component * add client config loading and error screen * fix server picker bug * fix reset password email input type * make auth page small screen responsive * fix typo in reset password screen
This commit is contained in:
parent
bb88eb7154
commit
20db27fa7e
103 changed files with 4772 additions and 543 deletions
|
|
@ -1,28 +1,26 @@
|
|||
import { useAtomValue, WritableAtom } from 'jotai';
|
||||
import { useAtomValue } 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';
|
||||
import { compareRoomsEqual } from '../utils';
|
||||
import { mDirectAtom } from '../mDirectList';
|
||||
import { allInvitesAtom } from '../inviteList';
|
||||
|
||||
export const useSpaceInvites = (
|
||||
mx: MatrixClient,
|
||||
allInvitesAtom: WritableAtom<string[], RoomsAction>
|
||||
) => {
|
||||
export const useSpaceInvites = (mx: MatrixClient, invitesAtom: typeof allInvitesAtom) => {
|
||||
const selector = useCallback(
|
||||
(rooms: string[]) => rooms.filter((roomId) => isSpace(mx.getRoom(roomId))),
|
||||
[mx]
|
||||
);
|
||||
return useAtomValue(selectAtom(allInvitesAtom, selector, compareRoomsEqual));
|
||||
return useAtomValue(selectAtom(invitesAtom, selector, compareRoomsEqual));
|
||||
};
|
||||
|
||||
export const useRoomInvites = (
|
||||
mx: MatrixClient,
|
||||
allInvitesAtom: WritableAtom<string[], RoomsAction>,
|
||||
mDirectAtom: WritableAtom<Set<string>, MDirectAction>
|
||||
invitesAtom: typeof allInvitesAtom,
|
||||
directAtom: typeof mDirectAtom
|
||||
) => {
|
||||
const mDirects = useAtomValue(mDirectAtom);
|
||||
const mDirects = useAtomValue(directAtom);
|
||||
const selector = useCallback(
|
||||
(rooms: string[]) =>
|
||||
rooms.filter(
|
||||
|
|
@ -32,15 +30,15 @@ export const useRoomInvites = (
|
|||
),
|
||||
[mx, mDirects]
|
||||
);
|
||||
return useAtomValue(selectAtom(allInvitesAtom, selector, compareRoomsEqual));
|
||||
return useAtomValue(selectAtom(invitesAtom, selector, compareRoomsEqual));
|
||||
};
|
||||
|
||||
export const useDirectInvites = (
|
||||
mx: MatrixClient,
|
||||
allInvitesAtom: WritableAtom<string[], RoomsAction>,
|
||||
mDirectAtom: WritableAtom<Set<string>, MDirectAction>
|
||||
invitesAtom: typeof allInvitesAtom,
|
||||
directAtom: typeof mDirectAtom
|
||||
) => {
|
||||
const mDirects = useAtomValue(mDirectAtom);
|
||||
const mDirects = useAtomValue(directAtom);
|
||||
const selector = useCallback(
|
||||
(rooms: string[]) =>
|
||||
rooms.filter(
|
||||
|
|
@ -48,16 +46,13 @@ export const useDirectInvites = (
|
|||
),
|
||||
[mx, mDirects]
|
||||
);
|
||||
return useAtomValue(selectAtom(allInvitesAtom, selector, compareRoomsEqual));
|
||||
return useAtomValue(selectAtom(invitesAtom, selector, compareRoomsEqual));
|
||||
};
|
||||
|
||||
export const useUnsupportedInvites = (
|
||||
mx: MatrixClient,
|
||||
allInvitesAtom: WritableAtom<string[], RoomsAction>
|
||||
) => {
|
||||
export const useUnsupportedInvites = (mx: MatrixClient, invitesAtom: typeof allInvitesAtom) => {
|
||||
const selector = useCallback(
|
||||
(rooms: string[]) => rooms.filter((roomId) => isUnsupportedRoom(mx.getRoom(roomId))),
|
||||
[mx]
|
||||
);
|
||||
return useAtomValue(selectAtom(allInvitesAtom, selector, compareRoomsEqual));
|
||||
return useAtomValue(selectAtom(invitesAtom, selector, compareRoomsEqual));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,54 +1,52 @@
|
|||
import { useAtomValue, WritableAtom } from 'jotai';
|
||||
import { useAtomValue } 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';
|
||||
import { compareRoomsEqual } from '../utils';
|
||||
import { mDirectAtom } from '../mDirectList';
|
||||
import { allRoomsAtom } from '../roomList';
|
||||
|
||||
export const useSpaces = (mx: MatrixClient, allRoomsAtom: WritableAtom<string[], RoomsAction>) => {
|
||||
export const useSpaces = (mx: MatrixClient, roomsAtom: typeof allRoomsAtom) => {
|
||||
const selector = useCallback(
|
||||
(rooms: string[]) => rooms.filter((roomId) => isSpace(mx.getRoom(roomId))),
|
||||
[mx]
|
||||
);
|
||||
return useAtomValue(selectAtom(allRoomsAtom, selector, compareRoomsEqual));
|
||||
return useAtomValue(selectAtom(roomsAtom, selector, compareRoomsEqual));
|
||||
};
|
||||
|
||||
export const useRooms = (
|
||||
mx: MatrixClient,
|
||||
allRoomsAtom: WritableAtom<string[], RoomsAction>,
|
||||
mDirectAtom: WritableAtom<Set<string>, MDirectAction>
|
||||
roomsAtom: typeof allRoomsAtom,
|
||||
directAtom: typeof mDirectAtom
|
||||
) => {
|
||||
const mDirects = useAtomValue(mDirectAtom);
|
||||
const mDirects = useAtomValue(directAtom);
|
||||
const selector = useCallback(
|
||||
(rooms: string[]) =>
|
||||
rooms.filter((roomId) => isRoom(mx.getRoom(roomId)) && !mDirects.has(roomId)),
|
||||
[mx, mDirects]
|
||||
);
|
||||
return useAtomValue(selectAtom(allRoomsAtom, selector, compareRoomsEqual));
|
||||
return useAtomValue(selectAtom(roomsAtom, selector, compareRoomsEqual));
|
||||
};
|
||||
|
||||
export const useDirects = (
|
||||
mx: MatrixClient,
|
||||
allRoomsAtom: WritableAtom<string[], RoomsAction>,
|
||||
mDirectAtom: WritableAtom<Set<string>, MDirectAction>
|
||||
roomsAtom: typeof allRoomsAtom,
|
||||
directAtom: typeof mDirectAtom
|
||||
) => {
|
||||
const mDirects = useAtomValue(mDirectAtom);
|
||||
const mDirects = useAtomValue(directAtom);
|
||||
const selector = useCallback(
|
||||
(rooms: string[]) =>
|
||||
rooms.filter((roomId) => isRoom(mx.getRoom(roomId)) && mDirects.has(roomId)),
|
||||
[mx, mDirects]
|
||||
);
|
||||
return useAtomValue(selectAtom(allRoomsAtom, selector, compareRoomsEqual));
|
||||
return useAtomValue(selectAtom(roomsAtom, selector, compareRoomsEqual));
|
||||
};
|
||||
|
||||
export const useUnsupportedRooms = (
|
||||
mx: MatrixClient,
|
||||
allRoomsAtom: WritableAtom<string[], RoomsAction>
|
||||
) => {
|
||||
export const useUnsupportedRooms = (mx: MatrixClient, roomsAtom: typeof allRoomsAtom) => {
|
||||
const selector = useCallback(
|
||||
(rooms: string[]) => rooms.filter((roomId) => isUnsupportedRoom(mx.getRoom(roomId))),
|
||||
[mx]
|
||||
);
|
||||
return useAtomValue(selectAtom(allRoomsAtom, selector, compareRoomsEqual));
|
||||
return useAtomValue(selectAtom(roomsAtom, selector, compareRoomsEqual));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
import { atom, useAtomValue, useSetAtom, WritableAtom } from 'jotai';
|
||||
import { SetAtom } from 'jotai/core/atom';
|
||||
import { atom, useAtomValue, useSetAtom } from 'jotai';
|
||||
import { selectAtom } from 'jotai/utils';
|
||||
import { useMemo } from 'react';
|
||||
import { Settings } from '../settings';
|
||||
import { Settings, settingsAtom as sAtom } from '../settings';
|
||||
|
||||
export const useSetSetting = <K extends keyof Settings>(
|
||||
settingsAtom: WritableAtom<Settings, Settings>,
|
||||
key: K
|
||||
) => {
|
||||
export type SettingSetter<K extends keyof Settings> =
|
||||
| Settings[K]
|
||||
| ((s: Settings[K]) => Settings[K]);
|
||||
|
||||
export const useSetSetting = <K extends keyof Settings>(settingsAtom: typeof sAtom, key: K) => {
|
||||
const setterAtom = useMemo(
|
||||
() =>
|
||||
atom<null, Settings[K] | ((s: Settings[K]) => Settings[K])>(null, (get, set, value) => {
|
||||
atom<null, [SettingSetter<K>], undefined>(null, (get, set, value) => {
|
||||
const s = { ...get(settingsAtom) };
|
||||
s[key] = typeof value === 'function' ? value(s[key]) : value;
|
||||
set(settingsAtom, s);
|
||||
|
|
@ -22,9 +22,9 @@ export const useSetSetting = <K extends keyof Settings>(
|
|||
};
|
||||
|
||||
export const useSetting = <K extends keyof Settings>(
|
||||
settingsAtom: WritableAtom<Settings, Settings>,
|
||||
settingsAtom: typeof sAtom,
|
||||
key: K
|
||||
): [Settings[K], SetAtom<Settings[K] | ((s: Settings[K]) => Settings[K]), void>] => {
|
||||
): [Settings[K], ReturnType<typeof useSetSetting<K>>] => {
|
||||
const selector = useMemo(() => (s: Settings) => s[key], [key]);
|
||||
const setting = useAtomValue(selectAtom(settingsAtom, selector));
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { Membership } from '../../types/matrix/room';
|
|||
import { RoomsAction, useBindRoomsWithMembershipsAtom } from './utils';
|
||||
|
||||
const baseRoomsAtom = atom<string[]>([]);
|
||||
export const allInvitesAtom = atom<string[], RoomsAction>(
|
||||
export const allInvitesAtom = atom<string[], [RoomsAction], undefined>(
|
||||
(get) => get(baseRoomsAtom),
|
||||
(get, set, action) => {
|
||||
if (action.type === 'INITIALIZE') {
|
||||
|
|
@ -22,7 +22,7 @@ export const allInvitesAtom = atom<string[], RoomsAction>(
|
|||
|
||||
export const useBindAllInvitesAtom = (
|
||||
mx: MatrixClient,
|
||||
allRooms: WritableAtom<string[], RoomsAction>
|
||||
allRooms: WritableAtom<string[], [RoomsAction], undefined>
|
||||
) => {
|
||||
useBindRoomsWithMembershipsAtom(
|
||||
mx,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export type ListAction<T> =
|
|||
|
||||
export const createListAtom = <T>() => {
|
||||
const baseListAtom = atom<T[]>([]);
|
||||
return atom<T[], ListAction<T>>(
|
||||
return atom<T[], [ListAction<T>], undefined>(
|
||||
(get) => get(baseListAtom),
|
||||
(get, set, action) => {
|
||||
const items = get(baseListAtom);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { atom, useSetAtom, WritableAtom } from 'jotai';
|
||||
import { atom, useSetAtom } from 'jotai';
|
||||
import { ClientEvent, MatrixClient, MatrixEvent } from 'matrix-js-sdk';
|
||||
import { useEffect } from 'react';
|
||||
import { AccountDataEvent } from '../../types/matrix/accountData';
|
||||
|
|
@ -10,17 +10,14 @@ export type MDirectAction = {
|
|||
};
|
||||
|
||||
const baseMDirectAtom = atom(new Set<string>());
|
||||
export const mDirectAtom = atom<Set<string>, MDirectAction>(
|
||||
export const mDirectAtom = atom<Set<string>, [MDirectAction], undefined>(
|
||||
(get) => get(baseMDirectAtom),
|
||||
(get, set, action) => {
|
||||
set(baseMDirectAtom, action.rooms);
|
||||
}
|
||||
);
|
||||
|
||||
export const useBindMDirectAtom = (
|
||||
mx: MatrixClient,
|
||||
mDirect: WritableAtom<Set<string>, MDirectAction>
|
||||
) => {
|
||||
export const useBindMDirectAtom = (mx: MatrixClient, mDirect: typeof mDirectAtom) => {
|
||||
const setMDirect = useSetAtom(mDirect);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { atom, WritableAtom, useSetAtom } from 'jotai';
|
||||
import { atom, useSetAtom } from 'jotai';
|
||||
import { ClientEvent, IPushRule, IPushRules, MatrixClient, MatrixEvent } from 'matrix-js-sdk';
|
||||
import { useEffect } from 'react';
|
||||
import { MuteChanges } from '../../types/matrix/room';
|
||||
|
|
@ -21,7 +21,7 @@ export const muteChangesAtom = atom<MuteChanges>({
|
|||
});
|
||||
|
||||
const baseMutedRoomsAtom = atom(new Set<string>());
|
||||
export const mutedRoomsAtom = atom<Set<string>, MutedRoomsUpdate>(
|
||||
export const mutedRoomsAtom = atom<Set<string>, [MutedRoomsUpdate], undefined>(
|
||||
(get) => get(baseMutedRoomsAtom),
|
||||
(get, set, action) => {
|
||||
const mutedRooms = new Set([...get(mutedRoomsAtom)]);
|
||||
|
|
@ -45,10 +45,7 @@ export const mutedRoomsAtom = atom<Set<string>, MutedRoomsUpdate>(
|
|||
}
|
||||
);
|
||||
|
||||
export const useBindMutedRoomsAtom = (
|
||||
mx: MatrixClient,
|
||||
mutedAtom: WritableAtom<Set<string>, MutedRoomsUpdate>
|
||||
) => {
|
||||
export const useBindMutedRoomsAtom = (mx: MatrixClient, mutedAtom: typeof mutedRoomsAtom) => {
|
||||
const setMuted = useSetAtom(mutedAtom);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { atom, WritableAtom } from 'jotai';
|
||||
import { atom } from 'jotai';
|
||||
import { MatrixClient } from 'matrix-js-sdk';
|
||||
import { useMemo } from 'react';
|
||||
import { Membership } from '../../types/matrix/room';
|
||||
import { RoomsAction, useBindRoomsWithMembershipsAtom } from './utils';
|
||||
|
||||
const baseRoomsAtom = atom<string[]>([]);
|
||||
export const allRoomsAtom = atom<string[], RoomsAction>(
|
||||
export const allRoomsAtom = atom<string[], [RoomsAction], undefined>(
|
||||
(get) => get(baseRoomsAtom),
|
||||
(get, set, action) => {
|
||||
if (action.type === 'INITIALIZE') {
|
||||
|
|
@ -19,10 +19,7 @@ export const allRoomsAtom = atom<string[], RoomsAction>(
|
|||
});
|
||||
}
|
||||
);
|
||||
export const useBindAllRoomsAtom = (
|
||||
mx: MatrixClient,
|
||||
allRooms: WritableAtom<string[], RoomsAction>
|
||||
) => {
|
||||
export const useBindAllRoomsAtom = (mx: MatrixClient, allRooms: typeof allRoomsAtom) => {
|
||||
useBindRoomsWithMembershipsAtom(
|
||||
mx,
|
||||
allRooms,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import produce from 'immer';
|
||||
import { atom, useSetAtom, WritableAtom } from 'jotai';
|
||||
import { atom, useSetAtom } from 'jotai';
|
||||
import {
|
||||
ClientEvent,
|
||||
MatrixClient,
|
||||
|
|
@ -34,7 +34,7 @@ export type RoomToParentsAction =
|
|||
};
|
||||
|
||||
const baseRoomToParents = atom<RoomToParents>(new Map());
|
||||
export const roomToParentsAtom = atom<RoomToParents, RoomToParentsAction>(
|
||||
export const roomToParentsAtom = atom<RoomToParents, [RoomToParentsAction], undefined>(
|
||||
(get) => get(baseRoomToParents),
|
||||
(get, set, action) => {
|
||||
if (action.type === 'INITIALIZE') {
|
||||
|
|
@ -69,7 +69,7 @@ export const roomToParentsAtom = atom<RoomToParents, RoomToParentsAction>(
|
|||
|
||||
export const useBindRoomToParentsAtom = (
|
||||
mx: MatrixClient,
|
||||
roomToParents: WritableAtom<RoomToParents, RoomToParentsAction>
|
||||
roomToParents: typeof roomToParentsAtom
|
||||
) => {
|
||||
const setRoomToParents = useSetAtom(roomToParents);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import produce from 'immer';
|
||||
import { atom, useSetAtom, PrimitiveAtom, WritableAtom, useAtomValue } from 'jotai';
|
||||
import { atom, useSetAtom, PrimitiveAtom, useAtomValue } from 'jotai';
|
||||
import { IRoomTimelineData, MatrixClient, MatrixEvent, Room, RoomEvent } from 'matrix-js-sdk';
|
||||
import { ReceiptContent, ReceiptType } from 'matrix-js-sdk/lib/@types/read_receipts';
|
||||
import { useEffect } from 'react';
|
||||
|
|
@ -82,7 +82,7 @@ const deleteUnreadInfo = (roomToUnread: RoomToUnread, allParents: Set<string>, r
|
|||
};
|
||||
|
||||
const baseRoomToUnread = atom<RoomToUnread>(new Map());
|
||||
export const roomToUnreadAtom = atom<RoomToUnread, RoomToUnreadAction>(
|
||||
export const roomToUnreadAtom = atom<RoomToUnread, [RoomToUnreadAction], undefined>(
|
||||
(get) => get(baseRoomToUnread),
|
||||
(get, set, action) => {
|
||||
if (action.type === 'RESET') {
|
||||
|
|
@ -127,7 +127,7 @@ export const roomToUnreadAtom = atom<RoomToUnread, RoomToUnreadAction>(
|
|||
|
||||
export const useBindRoomToUnreadAtom = (
|
||||
mx: MatrixClient,
|
||||
unreadAtom: WritableAtom<RoomToUnread, RoomToUnreadAction>,
|
||||
unreadAtom: typeof roomToUnreadAtom,
|
||||
muteChangesAtom: PrimitiveAtom<MuteChanges>
|
||||
) => {
|
||||
const setUnreadAtom = useSetAtom(unreadAtom);
|
||||
|
|
|
|||
129
src/app/state/sessions.ts
Normal file
129
src/app/state/sessions.ts
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
import { atom } from 'jotai';
|
||||
import {
|
||||
atomWithLocalStorage,
|
||||
getLocalStorageItem,
|
||||
setLocalStorageItem,
|
||||
} from './utils/atomWithLocalStorage';
|
||||
|
||||
export type Session = {
|
||||
baseUrl: string;
|
||||
userId: string;
|
||||
deviceId: string;
|
||||
accessToken: string;
|
||||
expiresInMs?: number;
|
||||
refreshToken?: string;
|
||||
fallbackSdkStores?: boolean;
|
||||
};
|
||||
|
||||
export type Sessions = Session[];
|
||||
export type SessionStoreName = {
|
||||
sync: string;
|
||||
crypto: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Migration code for old session
|
||||
*/
|
||||
const FALLBACK_STORE_NAME: SessionStoreName = {
|
||||
sync: 'web-sync-store',
|
||||
crypto: 'crypto-store',
|
||||
} as const;
|
||||
|
||||
const removeFallbackSession = () => {
|
||||
localStorage.removeItem('cinny_hs_base_url');
|
||||
localStorage.removeItem('cinny_user_id');
|
||||
localStorage.removeItem('cinny_device_id');
|
||||
localStorage.removeItem('cinny_access_token');
|
||||
};
|
||||
const getFallbackSession = (): Session | undefined => {
|
||||
const baseUrl = localStorage.getItem('cinny_hs_base_url');
|
||||
const userId = localStorage.getItem('cinny_user_id');
|
||||
const deviceId = localStorage.getItem('cinny_device_id');
|
||||
const accessToken = localStorage.getItem('cinny_access_token');
|
||||
|
||||
if (baseUrl && userId && deviceId && accessToken) {
|
||||
const session: Session = {
|
||||
baseUrl,
|
||||
userId,
|
||||
deviceId,
|
||||
accessToken,
|
||||
fallbackSdkStores: true,
|
||||
};
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
/**
|
||||
* End of migration code for old session
|
||||
*/
|
||||
|
||||
export const getSessionStoreName = (session: Session): SessionStoreName => {
|
||||
if (session.fallbackSdkStores) {
|
||||
return FALLBACK_STORE_NAME;
|
||||
}
|
||||
|
||||
return {
|
||||
sync: `sync${session.userId}`,
|
||||
crypto: `crypto${session.userId}`,
|
||||
};
|
||||
};
|
||||
|
||||
export const MATRIX_SESSIONS_KEY = 'matrixSessions';
|
||||
const baseSessionsAtom = atomWithLocalStorage<Sessions>(
|
||||
MATRIX_SESSIONS_KEY,
|
||||
(key) => {
|
||||
const defaultSessions: Sessions = [];
|
||||
const sessions = getLocalStorageItem(key, defaultSessions);
|
||||
|
||||
// Before multi account support session was stored
|
||||
// as multiple item in local storage.
|
||||
// So we need these migration code.
|
||||
const fallbackSession = getFallbackSession();
|
||||
if (fallbackSession) {
|
||||
removeFallbackSession();
|
||||
sessions.push(fallbackSession);
|
||||
setLocalStorageItem(key, sessions);
|
||||
}
|
||||
return sessions;
|
||||
},
|
||||
(key, value) => {
|
||||
setLocalStorageItem(key, value);
|
||||
}
|
||||
);
|
||||
|
||||
export type SessionsAction =
|
||||
| {
|
||||
type: 'PUT';
|
||||
session: Session;
|
||||
}
|
||||
| {
|
||||
type: 'DELETE';
|
||||
session: Session;
|
||||
};
|
||||
|
||||
export const sessionsAtom = atom<Sessions, [SessionsAction], undefined>(
|
||||
(get) => get(baseSessionsAtom),
|
||||
(get, set, action) => {
|
||||
if (action.type === 'PUT') {
|
||||
const sessions = [...get(baseSessionsAtom)];
|
||||
const sessionIndex = sessions.findIndex(
|
||||
(session) => session.userId === action.session.userId
|
||||
);
|
||||
if (sessionIndex === -1) {
|
||||
sessions.push(action.session);
|
||||
} else {
|
||||
sessions.splice(sessionIndex, 1, action.session);
|
||||
}
|
||||
set(baseSessionsAtom, sessions);
|
||||
return;
|
||||
}
|
||||
if (action.type === 'DELETE') {
|
||||
const sessions = get(baseSessionsAtom).filter(
|
||||
(session) => session.userId !== action.session.userId
|
||||
);
|
||||
set(baseSessionsAtom, sessions);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
@ -64,7 +64,7 @@ export const setSettings = (settings: Settings) => {
|
|||
};
|
||||
|
||||
const baseSettings = atom<Settings>(getSettings());
|
||||
export const settingsAtom = atom<Settings, Settings>(
|
||||
export const settingsAtom = atom<Settings, [Settings], undefined>(
|
||||
(get) => get(baseSettings),
|
||||
(get, set, update) => {
|
||||
set(baseSettings, update);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ type TabToRoomAction = {
|
|||
};
|
||||
|
||||
const baseTabToRoom = atom<TabToRoom>(new Map());
|
||||
export const tabToRoomAtom = atom<TabToRoom, TabToRoomAction>(
|
||||
export const tabToRoomAtom = atom<TabToRoom, [TabToRoomAction], undefined>(
|
||||
(get) => get(baseTabToRoom),
|
||||
(get, set, action) => {
|
||||
if (action.type === 'PUT') {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,11 @@ export type IRoomIdToTypingMembersAction =
|
|||
};
|
||||
|
||||
const baseRoomIdToTypingMembersAtom = atom<IRoomIdToTypingMembers>(new Map());
|
||||
export const roomIdToTypingMembersAtom = atom<IRoomIdToTypingMembers, IRoomIdToTypingMembersAction>(
|
||||
export const roomIdToTypingMembersAtom = atom<
|
||||
IRoomIdToTypingMembers,
|
||||
[IRoomIdToTypingMembersAction],
|
||||
undefined
|
||||
>(
|
||||
(get) => get(baseRoomIdToTypingMembersAtom),
|
||||
(get, set, action) => {
|
||||
const roomIdToTypingMembers = get(baseRoomIdToTypingMembersAtom);
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ export const createUploadAtom = (file: TUploadContent) => {
|
|||
file,
|
||||
status: UploadStatus.Idle,
|
||||
});
|
||||
return atom<Upload, UploadAtomAction>(
|
||||
return atom<Upload, [UploadAtomAction], undefined>(
|
||||
(get) => get(baseUploadAtom),
|
||||
(get, set, update) => {
|
||||
const uploadState = get(baseUploadAtom);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export type RoomsAction =
|
|||
|
||||
export const useBindRoomsWithMembershipsAtom = (
|
||||
mx: MatrixClient,
|
||||
roomsAtom: WritableAtom<string[], RoomsAction>,
|
||||
roomsAtom: WritableAtom<string[], [RoomsAction], undefined>,
|
||||
memberships: Membership[]
|
||||
) => {
|
||||
const setRoomsAtom = useSetAtom(roomsAtom);
|
||||
|
|
|
|||
51
src/app/state/utils/atomWithLocalStorage.ts
Normal file
51
src/app/state/utils/atomWithLocalStorage.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { atom } from 'jotai';
|
||||
|
||||
export const getLocalStorageItem = <T>(key: string, defaultValue: T): T => {
|
||||
const item = localStorage.getItem(key);
|
||||
if (item === null) return defaultValue;
|
||||
if (item === 'undefined') return undefined as T;
|
||||
try {
|
||||
return JSON.parse(item) as T;
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
};
|
||||
|
||||
export const setLocalStorageItem = <T>(key: string, value: T) => {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
};
|
||||
|
||||
export type GetLocalStorageItem<T> = (key: string) => T;
|
||||
export type SetLocalStorageItem<T> = (key: string, value: T) => void;
|
||||
|
||||
export const atomWithLocalStorage = <T>(
|
||||
key: string,
|
||||
getItem: GetLocalStorageItem<T>,
|
||||
setItem: SetLocalStorageItem<T>
|
||||
) => {
|
||||
const value = getItem(key);
|
||||
|
||||
const baseAtom = atom<T>(value);
|
||||
|
||||
baseAtom.onMount = (setAtom) => {
|
||||
const handleChange = (evt: StorageEvent) => {
|
||||
if (evt.key !== key) return;
|
||||
setAtom(getItem(key));
|
||||
};
|
||||
|
||||
window.addEventListener('storage', handleChange);
|
||||
return () => {
|
||||
window.removeEventListener('storage', handleChange);
|
||||
};
|
||||
};
|
||||
|
||||
const localStorageAtom = atom<T, [T], undefined>(
|
||||
(get) => get(baseAtom),
|
||||
(get, set, newValue) => {
|
||||
set(baseAtom, newValue);
|
||||
setItem(key, newValue);
|
||||
}
|
||||
);
|
||||
|
||||
return localStorageAtom;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue