feat: #120 add group nav item sortable feature

data will saving into user settings `groupOrderList`
pull/146/head
moonrailgun 2 years ago
parent 0d21980e40
commit 8f3ec1c996

@ -9,6 +9,7 @@ import {
import { useAsyncRequest } from '../useAsyncRequest';
import { useMemoizedFn } from '../useMemoizedFn';
import _without from 'lodash/without';
import { useState } from 'react';
/**
* hooks
@ -25,6 +26,8 @@ export function useUserSettings() {
const [{ loading: saveLoading }, setSettings] = useAsyncRequest(
async (settings: UserSettings) => {
client.setQueryData([CacheKey], () => settings); // 让配置能够立即生效, 防止依赖配置的行为出现跳变(如GroupNav)
const newSettings = await setUserSettings(settings);
client.setQueryData([CacheKey], () => newSettings);

@ -40,6 +40,11 @@ export interface UserSettings {
*/
messageNotificationMuteList?: string[];
/**
* , id
*/
groupOrderList?: string[];
/**
*
*/

@ -34,6 +34,7 @@
"@use-gesture/react": "^10.2.24",
"ahooks": "^3.7.4",
"antd": "^4.24.8",
"array-move": "3.0.1",
"axios": "^0.21.4",
"bytemd": "^1.21.0",
"clsx": "^1.2.1",
@ -59,6 +60,7 @@
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "18.2.0",
"react-easy-crop": "^5.0.0",
"react-easy-sort": "^1.5.1",
"react-helmet": "^6.1.0",
"react-is": "^18.2.0",
"react-markdown": "^8.0.5",

@ -1,20 +1,24 @@
import { Avatar, Icon } from 'tailchat-design';
import { openModal } from '@/components/Modal';
import { ModalCreateGroup } from '@/components/modals/CreateGroup';
import React, { useCallback, useMemo } from 'react';
import React, { useMemo } from 'react';
import {
GroupInfo,
showSuccessToasts,
t,
useAppSelector,
useEvent,
useGlobalConfigStore,
useGroupAck,
useSingleUserSetting,
} from 'tailchat-shared';
import { NavbarNavItem } from './NavItem';
import { Dropdown } from 'antd';
import { useGroupUnreadState } from '@/hooks/useGroupUnreadState';
import { pluginCustomPanel } from '@/plugin/common';
import { NavbarCustomNavItem } from './CustomNavItem';
import SortableList, { SortableItem } from 'react-easy-sort';
import arrayMove from 'array-move';
/**
*
@ -63,20 +67,45 @@ const GroupNavItem: React.FC<{ group: GroupInfo }> = React.memo(({ group }) => {
});
GroupNavItem.displayName = 'GroupNavItem';
function useGroups(): GroupInfo[] {
function useGroupList() {
const groups = useAppSelector((state) => state.group.groups);
return useMemo(
() => Object.entries(groups).map(([_, group]) => group),
[groups]
const { value: groupOrderList = [], setValue: setGroupOrderList } =
useSingleUserSetting('groupOrderList', []);
const handleSortEnd = useEvent((oldIndex: number, newIndex: number) => {
setGroupOrderList(
arrayMove(
groupList.map((item) => item._id),
oldIndex,
newIndex
)
);
});
const groupList = useMemo(
() =>
Object.values(groups).sort((a, b) => {
const aIndex = groupOrderList.findIndex((item) => item === a._id);
const bIndex = groupOrderList.findIndex((item) => item === b._id);
// 两种情况,在排序列表中则按照排序列表排序
// 不在排序列表中则放在最前面
return aIndex - bIndex;
}),
[groups, groupOrderList]
);
return {
handleSortEnd,
groupList,
};
}
export const GroupNav: React.FC = React.memo(() => {
const groups = useGroups();
const { groupList, handleSortEnd } = useGroupList();
const handleCreateGroup = useCallback(() => {
const handleCreateGroup = useEvent(() => {
openModal(<ModalCreateGroup />);
}, []);
});
const { disableCreateGroup } = useGlobalConfigStore((state) => ({
disableCreateGroup: state.disableCreateGroup,
@ -84,12 +113,17 @@ export const GroupNav: React.FC = React.memo(() => {
return (
<div className="space-y-2" data-tc-role="navbar-groups">
{Array.isArray(groups) &&
groups.map((group) => (
<div key={group._id}>
<GroupNavItem group={group} />
</div>
))}
{Array.isArray(groupList) && (
<SortableList className="space-y-2" onSortEnd={handleSortEnd}>
{groupList.map((group) => (
<SortableItem key={group._id}>
<div>
<GroupNavItem group={group} />
</div>
</SortableItem>
))}
</SortableList>
)}
{!disableCreateGroup && (
<NavbarNavItem

@ -2,8 +2,8 @@ import { Tooltip, Badge, BadgeProps } from 'antd';
import type { ClassValue } from 'clsx';
import clsx from 'clsx';
import React, { PropsWithChildren } from 'react';
import { useLocation } from 'react-router';
import { Link } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router';
import { useEvent } from 'tailchat-shared';
export const NavbarNavItem: React.FC<
PropsWithChildren<{
@ -20,6 +20,14 @@ export const NavbarNavItem: React.FC<
const { name, className, to, showPill = false, badge = false } = props;
const location = useLocation();
const isActive = typeof to === 'string' && location.pathname.startsWith(to);
const navigate = useNavigate();
const handleClick = useEvent(() => {
if (typeof to === 'string') {
navigate(to);
}
props.onClick?.();
});
let inner = (
<Tooltip
@ -37,7 +45,7 @@ export const NavbarNavItem: React.FC<
'rounded-lg': isActive,
}
)}
onClick={props.onClick}
onClick={handleClick}
data-testid={props['data-testid']}
>
{props.children}
@ -45,10 +53,6 @@ export const NavbarNavItem: React.FC<
</Tooltip>
);
if (typeof to === 'string') {
inner = <Link to={to}>{inner}</Link>;
}
if (badge === true) {
inner = (
<Badge status="error" dot={true} offset={[0, 44]} {...props.badgeProps}>

@ -627,6 +627,9 @@ importers:
antd:
specifier: ^4.24.8
version: 4.24.8(react-dom@18.2.0)(react@18.2.0)
array-move:
specifier: 3.0.1
version: 3.0.1
axios:
specifier: ^0.21.4
version: 0.21.4
@ -702,6 +705,9 @@ importers:
react-easy-crop:
specifier: ^5.0.0
version: 5.0.0(react-dom@18.2.0)(react@18.2.0)
react-easy-sort:
specifier: ^1.5.1
version: 1.5.1(react-dom@18.2.0)(react@18.2.0)
react-helmet:
specifier: ^6.1.0
version: 6.1.0(react@18.2.0)
@ -13770,6 +13776,11 @@ packages:
is-string: 1.0.7
dev: true
/array-move@3.0.1:
resolution: {integrity: sha512-H3Of6NIn2nNU1gsVDqDnYKY/LCdWvCMMOWifNGhKcVQgiZ6nOek39aESOvro6zmueP07exSl93YLvkN4fZOkSg==}
engines: {node: '>=10'}
dev: false
/array-slice@1.1.0:
resolution: {integrity: sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==}
engines: {node: '>=0.10.0'}
@ -28874,6 +28885,18 @@ packages:
tslib: 2.0.1
dev: false
/react-easy-sort@1.5.1(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-VKwFO2pHJgz75zEZkxoZYSr0jvQfbS2BUqCQPR3hCRxUfLl9M7xTrXK4rZeFlA+83RxOQCZnzrqtuX0sbtKKlQ==}
peerDependencies:
react: '>=16.4.0'
react-dom: '>=16.4.0'
dependencies:
array-move: 3.0.1
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
tslib: 2.0.1
dev: false
/react-element-to-jsx-string@14.3.4(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-t4ZwvV6vwNxzujDQ+37bspnLwA4JlgUPWhLjBJWsNIDceAf6ZKUTCjdm08cN6WeZ5pTMKiCJkmAYnpmR4Bm+dg==}
peerDependencies:

Loading…
Cancel
Save