mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-02-23 13:43:07 +01:00
save and restore last navigation path on space select
This commit is contained in:
parent
ebf9639b22
commit
fca81bbb34
12 changed files with 133 additions and 19 deletions
18
src/app/hooks/navToActivePathMapper.ts
Normal file
18
src/app/hooks/navToActivePathMapper.ts
Normal 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]);
|
||||
};
|
|
@ -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');
|
||||
|
|
|
@ -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 />} />
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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));
|
||||
};
|
||||
|
|
|
@ -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(
|
||||
|
|
26
src/app/pages/client/space/SpaceIndexRedirect.tsx
Normal file
26
src/app/pages/client/space/SpaceIndexRedirect.tsx
Normal 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;
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
export * from './SpaceProvider';
|
||||
export * from './Space';
|
||||
export * from './SpaceIndexRedirect';
|
||||
export * from './Search';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
})
|
||||
);
|
||||
|
|
57
src/app/state/navToActivePath.ts
Normal file
57
src/app/state/navToActivePath.ts
Normal 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);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
Loading…
Reference in a new issue