From 8f3ec1c996b1ebb86d88ae3c0fee3b95a08eb86b Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Thu, 10 Aug 2023 16:18:42 +0800 Subject: [PATCH] feat: #120 add group nav item sortable feature data will saving into user settings `groupOrderList` --- client/shared/hooks/model/useUserSettings.ts | 3 + client/shared/model/user.ts | 5 ++ client/web/package.json | 2 + .../web/src/routes/Main/Navbar/GroupNav.tsx | 62 ++++++++++++++----- client/web/src/routes/Main/Navbar/NavItem.tsx | 18 +++--- pnpm-lock.yaml | 23 +++++++ 6 files changed, 92 insertions(+), 21 deletions(-) diff --git a/client/shared/hooks/model/useUserSettings.ts b/client/shared/hooks/model/useUserSettings.ts index 2ca75aee..8e92f7fb 100644 --- a/client/shared/hooks/model/useUserSettings.ts +++ b/client/shared/hooks/model/useUserSettings.ts @@ -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); diff --git a/client/shared/model/user.ts b/client/shared/model/user.ts index 6b1a7848..9a1565d7 100644 --- a/client/shared/model/user.ts +++ b/client/shared/model/user.ts @@ -40,6 +40,11 @@ export interface UserSettings { */ messageNotificationMuteList?: string[]; + /** + * 群组排序, 内容为群组id + */ + groupOrderList?: string[]; + /** * 其他的设置项 */ diff --git a/client/web/package.json b/client/web/package.json index e0b38ff4..9583b98e 100644 --- a/client/web/package.json +++ b/client/web/package.json @@ -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", diff --git a/client/web/src/routes/Main/Navbar/GroupNav.tsx b/client/web/src/routes/Main/Navbar/GroupNav.tsx index b842556f..0e6a861d 100644 --- a/client/web/src/routes/Main/Navbar/GroupNav.tsx +++ b/client/web/src/routes/Main/Navbar/GroupNav.tsx @@ -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(); - }, []); + }); const { disableCreateGroup } = useGlobalConfigStore((state) => ({ disableCreateGroup: state.disableCreateGroup, @@ -84,12 +113,17 @@ export const GroupNav: React.FC = React.memo(() => { return (
- {Array.isArray(groups) && - groups.map((group) => ( -
- -
- ))} + {Array.isArray(groupList) && ( + + {groupList.map((group) => ( + +
+ +
+
+ ))} +
+ )} {!disableCreateGroup && ( { + if (typeof to === 'string') { + navigate(to); + } + props.onClick?.(); + }); let inner = ( {props.children} @@ -45,10 +53,6 @@ export const NavbarNavItem: React.FC< ); - if (typeof to === 'string') { - inner = {inner}; - } - if (badge === true) { inner = ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4843463..21335e1b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: