save and restore last navigation path on space select

This commit is contained in:
Ajay Bura 2024-04-10 20:35:19 +05:30
parent ebf9639b22
commit fca81bbb34
12 changed files with 133 additions and 19 deletions

View file

@ -0,0 +1,18 @@
import { useEffect } from 'react';
import { useSetAtom } from 'jotai';
import { useLocation } from 'react-router-dom';
import { navToActivePathAtom } from '../state/navToActivePath';
export const useNavToActivePathMapper = (navId: string) => {
const location = useLocation();
const setNavToActivePath = useSetAtom(navToActivePathAtom);
useEffect(() => {
const { pathname, search, hash } = location;
setNavToActivePath({
type: 'PUT',
navId,
path: { pathname, search, hash },
});
}, [location, setNavToActivePath, navId]);
};

View file

@ -1,9 +1,9 @@
import { useSetAtom } from 'jotai';
import { MouseEventHandler } from 'react';
import { closedNavCategories } from '../state/closedNavCategories';
import { closedNavCategoriesAtom } from '../state/closedNavCategories';
export const useNavCategoryHandler = (closed: (categoryId: string) => boolean) => {
const setClosedCategory = useSetAtom(closedNavCategories);
const setClosedCategory = useSetAtom(closedNavCategoriesAtom);
const handleCategoryClick: MouseEventHandler<HTMLButtonElement> = (evt) => {
const categoryId = evt.currentTarget.getAttribute('data-category-id');

View file

@ -42,7 +42,7 @@ import { ClientLayout, ClientRoot } from './client';
import { Home, HomeSearch } from './client/home';
import { RoomViewer } from '../organisms/room/Room';
import { Direct } from './client/direct';
import { RouteSpaceProvider, Space, SpaceSearch } from './client/space';
import { RouteSpaceProvider, Space, SpaceIndexRedirect, SpaceSearch } from './client/space';
import { Explore, ExploreRedirect, FeaturedRooms, PublicRooms } from './client/explore';
import { Notifications, Inbox, InboxRedirect, Invites } from './client/inbox';
import { setAfterLoginRedirectPath } from './afterLoginRedirectPath';
@ -99,7 +99,7 @@ const createRouter = (clientConfig: ClientConfig) => {
</Route>
<Route path={SPACE_PATH} element={<RouteSpaceProvider />}>
<Route element={<Space />}>
<Route index element={<p>welcome</p>} />
<Route index element={<SpaceIndexRedirect />} />
<Route path={_LOBBY_PATH} element={<p>lobby</p>} />
<Route path={_SEARCH_PATH} element={<SpaceSearch />} />
<Route path={_ROOM_PATH} element={<RoomViewer />} />

View file

@ -28,7 +28,7 @@ import { useDirectCreateSelected } from '../../../hooks/router/useDirectSelected
import { VirtualTile } from '../../../components/virtualizer';
import { RoomNavCategoryButton, RoomNavItem } from '../../../features/room-nav';
import { muteChangesAtom } from '../../../state/room-list/mutedRoomList';
import { closedNavCategories, makeNavCategoryId } from '../../../state/closedNavCategories';
import { closedNavCategoriesAtom, makeNavCategoryId } from '../../../state/closedNavCategories';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
import { useNavCategoryHandler } from '../../../hooks/useNavCategoryHandler';
@ -72,7 +72,7 @@ export function Direct() {
const selectedRoomId = useSelectedRoom();
const createSelected = useDirectCreateSelected();
const noRoomToDisplay = directs.length === 0;
const closedCategories = useAtomValue(closedNavCategories);
const closedCategories = useAtomValue(closedNavCategoriesAtom);
const sortedDirects = useMemo(() => {
const items = Array.from(directs).sort(factoryRoomIdByActivity(mx));

View file

@ -36,7 +36,7 @@ import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { VirtualTile } from '../../../components/virtualizer';
import { RoomNavCategoryButton, RoomNavItem } from '../../../features/room-nav';
import { muteChangesAtom } from '../../../state/room-list/mutedRoomList';
import { closedNavCategories, makeNavCategoryId } from '../../../state/closedNavCategories';
import { closedNavCategoriesAtom, makeNavCategoryId } from '../../../state/closedNavCategories';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
import { useNavCategoryHandler } from '../../../hooks/useNavCategoryHandler';
@ -95,7 +95,7 @@ export function Home() {
const joinSelected = useHomeJoinSelected();
const searchSelected = useHomeSearchSelected();
const noRoomToDisplay = rooms.length === 0;
const closedCategories = useAtomValue(closedNavCategories);
const closedCategories = useAtomValue(closedNavCategoriesAtom);
const sortedRooms = useMemo(() => {
const items = Array.from(rooms).sort(factoryRoomIdByAtoZ(mx));

View file

@ -6,19 +6,21 @@ import { useOrphanSpaces } from '../../../state/hooks/roomList';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { roomToParentsAtom } from '../../../state/room/roomToParents';
import { allRoomsAtom } from '../../../state/room-list/roomList';
import { getSpacePath, getSpaceRoomPath } from '../../pathUtils';
import { getSpacePath, getSpaceRoomPath, joinPathComponent } from '../../pathUtils';
import { SidebarAvatar } from '../../../components/sidebar';
import { NotificationBadge, UnreadMenu } from './NotificationBadge';
import { RoomUnreadProvider } from '../../../components/RoomUnreadProvider';
import colorMXID from '../../../../util/colorMXID';
import { useSelectedSpace } from '../../../hooks/router/useSelectedSpace';
import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
import { navToActivePathAtom } from '../../../state/navToActivePath';
export function SpaceTabs() {
const navigate = useNavigate();
const mx = useMatrixClient();
const roomToParents = useAtomValue(roomToParentsAtom);
const orphanSpaces = useOrphanSpaces(mx, allRoomsAtom, roomToParents);
const navToActivePath = useAtomValue(navToActivePathAtom);
const selectedSpaceId = useSelectedSpace();
@ -30,6 +32,12 @@ export function SpaceTabs() {
const targetSpaceId = target.getAttribute('data-id');
if (!targetSpaceId) return;
const activePath = navToActivePath.get(targetSpaceId);
if (activePath) {
navigate(joinPathComponent(activePath));
return;
}
const targetSpaceAlias = mx.getRoom(targetSpaceId)?.getCanonicalAlias();
navigate(getSpacePath(targetSpaceAlias ?? targetSpaceId));
};

View file

@ -28,13 +28,15 @@ import { VirtualTile } from '../../../components/virtualizer';
import { useSpaceHierarchy } from './useSpaceHierarchy';
import { RoomNavCategoryButton, RoomNavItem } from '../../../features/room-nav';
import { muteChangesAtom } from '../../../state/room-list/mutedRoomList';
import { closedNavCategories, makeNavCategoryId } from '../../../state/closedNavCategories';
import { closedNavCategoriesAtom, makeNavCategoryId } from '../../../state/closedNavCategories';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
import { useNavCategoryHandler } from '../../../hooks/useNavCategoryHandler';
import { useNavToActivePathMapper } from '../../../hooks/navToActivePathMapper';
export function Space() {
const mx = useMatrixClient();
const space = useSpace();
useNavToActivePathMapper(space.roomId);
const spaceIdOrAlias = getCanonicalAliasOrRoomId(mx, space.roomId);
const scrollRef = useRef<HTMLDivElement>(null);
const mDirects = useAtomValue(mDirectAtom);
@ -46,7 +48,7 @@ export function Space() {
const lobbySelected = useSpaceLobbySelected(spaceIdOrAlias);
const searchSelected = useSpaceSearchSelected(spaceIdOrAlias);
const closedCategories = useAtomValue(closedNavCategories);
const closedCategories = useAtomValue(closedNavCategoriesAtom);
const hierarchy = useSpaceHierarchy(
space.roomId,
useCallback(

View file

@ -0,0 +1,26 @@
import { useAtomValue } from 'jotai';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { navToActivePathAtom } from '../../../state/navToActivePath';
import { useSpace } from '../../../hooks/useSpace';
import { getSpaceLobbyPath, joinPathComponent } from '../../pathUtils';
import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
export function SpaceIndexRedirect() {
const navigate = useNavigate();
const mx = useMatrixClient();
const space = useSpace();
const navToActivePath = useAtomValue(navToActivePathAtom);
useEffect(() => {
const activePath = navToActivePath.get(space.roomId);
if (activePath) {
navigate(joinPathComponent(activePath), { replace: true });
} else {
navigate(getSpaceLobbyPath(getCanonicalAliasOrRoomId(mx, space.roomId)), { replace: true });
}
}, [navigate, mx, space.roomId, navToActivePath]);
return null;
}

View file

@ -1,3 +1,4 @@
export * from './SpaceProvider';
export * from './Space';
export * from './SpaceIndexRedirect';
export * from './Search';

View file

@ -1,4 +1,4 @@
import { generatePath } from 'react-router-dom';
import { generatePath, Path } from 'react-router-dom';
import {
DIRECT_CREATE_PATH,
DIRECT_PATH,
@ -25,6 +25,8 @@ import {
} from './paths';
import { trimTrailingSlash } from '../utils/common';
export const joinPathComponent = (path: Path): string => path.pathname + path.search + path.hash;
export const withSearchParam = <T extends Record<string, string>>(
path: string,
searchParam: T

View file

@ -8,7 +8,7 @@ import {
const CLOSED_NAV_CATEGORY = 'closedNavCategories';
const baseClosedNavCategories = atomWithLocalStorage<Set<string>>(
const baseClosedNavCategoriesAtom = atomWithLocalStorage<Set<string>>(
CLOSED_NAV_CATEGORY,
(key) => {
const arrayValue = getLocalStorageItem<string[]>(key, []);
@ -30,13 +30,13 @@ type ClosedNavCategoriesAction =
categoryId: string;
};
export const closedNavCategories = atom<Set<string>, [ClosedNavCategoriesAction], undefined>(
(get) => get(baseClosedNavCategories),
export const closedNavCategoriesAtom = atom<Set<string>, [ClosedNavCategoriesAction], undefined>(
(get) => get(baseClosedNavCategoriesAtom),
(get, set, action) => {
if (action.type === 'DELETE') {
set(
baseClosedNavCategories,
produce(get(baseClosedNavCategories), (draft) => {
baseClosedNavCategoriesAtom,
produce(get(baseClosedNavCategoriesAtom), (draft) => {
draft.delete(action.categoryId);
})
);
@ -44,8 +44,8 @@ export const closedNavCategories = atom<Set<string>, [ClosedNavCategoriesAction]
}
if (action.type === 'PUT') {
set(
baseClosedNavCategories,
produce(get(baseClosedNavCategories), (draft) => {
baseClosedNavCategoriesAtom,
produce(get(baseClosedNavCategoriesAtom), (draft) => {
draft.add(action.categoryId);
})
);

View file

@ -0,0 +1,57 @@
import { atom } from 'jotai';
import produce from 'immer';
import { Path } from 'react-router-dom';
import {
atomWithLocalStorage,
getLocalStorageItem,
setLocalStorageItem,
} from './utils/atomWithLocalStorage';
const NAV_TO_ACTIVE_PATH = 'navToActivePath';
type NavToActivePath = Map<string, Path>;
const baseNavToActivePathAtom = atomWithLocalStorage<NavToActivePath>(
NAV_TO_ACTIVE_PATH,
(key) => {
const obj: Record<string, Path> = getLocalStorageItem(key, {});
return new Map(Object.entries(obj));
},
(key, value) => {
const obj: Record<string, Path> = Object.fromEntries(value);
setLocalStorageItem(key, obj);
}
);
type NavToActivePathAction =
| {
type: 'PUT';
navId: string;
path: Path;
}
| {
type: 'DELETE';
navId: string;
};
export const navToActivePathAtom = atom<NavToActivePath, [NavToActivePathAction], undefined>(
(get) => get(baseNavToActivePathAtom),
(get, set, action) => {
if (action.type === 'DELETE') {
set(
baseNavToActivePathAtom,
produce(get(baseNavToActivePathAtom), (draft) => {
draft.delete(action.navId);
})
);
return;
}
if (action.type === 'PUT') {
set(
baseNavToActivePathAtom,
produce(get(baseNavToActivePathAtom), (draft) => {
draft.set(action.navId, action.path);
})
);
}
}
);