mirror of https://github.com/msgbyte/tailchat
refactor: rebuild project struction
parent
bd441a45eb
commit
26fd3c6cdb
@ -0,0 +1,7 @@
|
|||||||
|
import { EffectCallback, useEffect } from 'react';
|
||||||
|
|
||||||
|
// Reference: https://github.com/streamich/react-use/blob/master/src/useEffectOnce.ts
|
||||||
|
|
||||||
|
export const useEffectOnce = (effect: EffectCallback) => {
|
||||||
|
useEffect(effect, []);
|
||||||
|
};
|
@ -0,0 +1,26 @@
|
|||||||
|
import { Dispatch, SetStateAction, useCallback, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { useUnmount } from './useUnmount';
|
||||||
|
|
||||||
|
// Reference: https://github.com/streamich/react-use/blob/master/src/useRafState.ts
|
||||||
|
|
||||||
|
export const useRafState = <S>(
|
||||||
|
initialState: S | (() => S)
|
||||||
|
): [S, Dispatch<SetStateAction<S>>] => {
|
||||||
|
const frame = useRef(0);
|
||||||
|
const [state, setState] = useState(initialState);
|
||||||
|
|
||||||
|
const setRafState = useCallback((value: S | ((prevState: S) => S)) => {
|
||||||
|
cancelAnimationFrame(frame.current);
|
||||||
|
|
||||||
|
frame.current = requestAnimationFrame(() => {
|
||||||
|
setState(value);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useUnmount(() => {
|
||||||
|
cancelAnimationFrame(frame.current);
|
||||||
|
});
|
||||||
|
|
||||||
|
return [state, setRafState];
|
||||||
|
};
|
@ -0,0 +1,13 @@
|
|||||||
|
import { useRef } from 'react';
|
||||||
|
import { useEffectOnce } from './useEffectOnce';
|
||||||
|
|
||||||
|
// Reference: https://github.com/streamich/react-use/blob/master/src/useUnmount.ts
|
||||||
|
|
||||||
|
export const useUnmount = (fn: () => any): void => {
|
||||||
|
const fnRef = useRef(fn);
|
||||||
|
|
||||||
|
// update the ref each render so if it change the newest callback will be invoked
|
||||||
|
fnRef.current = fn;
|
||||||
|
|
||||||
|
useEffectOnce(() => () => fnRef.current());
|
||||||
|
};
|
@ -0,0 +1,3 @@
|
|||||||
|
export const isBrowser = typeof window !== 'undefined';
|
||||||
|
|
||||||
|
export const isNavigator = typeof navigator !== 'undefined';
|
@ -0,0 +1,10 @@
|
|||||||
|
import { useWindowSize } from './useWindowSize';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判定是否为移动版网页
|
||||||
|
*/
|
||||||
|
export function useIsMobile(): boolean {
|
||||||
|
const { width } = useWindowSize();
|
||||||
|
|
||||||
|
return width < 768;
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
import { isBrowser, useRafState } from 'pawchat-shared';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
// Reference: https://github.com/streamich/react-use/blob/master/src/useWindowSize.ts
|
||||||
|
|
||||||
|
export const useWindowSize = (
|
||||||
|
initialWidth = Infinity,
|
||||||
|
initialHeight = Infinity
|
||||||
|
) => {
|
||||||
|
const [state, setState] = useRafState<{ width: number; height: number }>({
|
||||||
|
width: isBrowser ? window.innerWidth : initialWidth,
|
||||||
|
height: isBrowser ? window.innerHeight : initialHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect((): (() => void) | void => {
|
||||||
|
if (isBrowser) {
|
||||||
|
const handler = () => {
|
||||||
|
setState({
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handler);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handler);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return state;
|
||||||
|
};
|
@ -1,52 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { PillTabs, PillTabPane } from '../../components/PillTabs';
|
|
||||||
import { useAppSelector } from '../../hooks/useAppSelector';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主要内容组件
|
|
||||||
*/
|
|
||||||
export const Content: React.FC = React.memo(() => {
|
|
||||||
const friends = useAppSelector((state) => state.user.friends);
|
|
||||||
const friendRequests = useAppSelector((state) => state.user.friendRequests);
|
|
||||||
const userId = useAppSelector((state) => state.user.info?._id);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex-auto bg-gray-700">
|
|
||||||
<PillTabs>
|
|
||||||
<PillTabPane tab={'全部'} key="1">
|
|
||||||
<div className="py-2.5 px-5">
|
|
||||||
<div>好友列表</div>
|
|
||||||
<div>{JSON.stringify(friends)}</div>
|
|
||||||
</div>
|
|
||||||
</PillTabPane>
|
|
||||||
<PillTabPane tab={'已发送'} key="2">
|
|
||||||
<div className="py-2.5 px-5">
|
|
||||||
<div>发送的好友请求</div>
|
|
||||||
<div>
|
|
||||||
{JSON.stringify(
|
|
||||||
friendRequests.filter((item) => item.from === userId)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PillTabPane>
|
|
||||||
<PillTabPane tab={'待处理'} key="3">
|
|
||||||
<div className="py-2.5 px-5">
|
|
||||||
<div>接受的好友请求</div>
|
|
||||||
<div>
|
|
||||||
{JSON.stringify(
|
|
||||||
friendRequests.filter((item) => item.to === userId)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PillTabPane>
|
|
||||||
<PillTabPane
|
|
||||||
tab={<span className="text-green-400">添加好友</span>}
|
|
||||||
key="4"
|
|
||||||
>
|
|
||||||
添加好友
|
|
||||||
</PillTabPane>
|
|
||||||
</PillTabs>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
Content.displayName = 'Content';
|
|
@ -0,0 +1,50 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useAppSelector } from '@/hooks/useAppSelector';
|
||||||
|
import { PillTabPane, PillTabs } from '@/components/PillTabs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主要内容组件
|
||||||
|
*/
|
||||||
|
export const FriendPanel: React.FC = React.memo(() => {
|
||||||
|
const friends = useAppSelector((state) => state.user.friends);
|
||||||
|
const friendRequests = useAppSelector((state) => state.user.friendRequests);
|
||||||
|
const userId = useAppSelector((state) => state.user.info?._id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PillTabs>
|
||||||
|
<PillTabPane tab={'全部'} key="1">
|
||||||
|
<div className="py-2.5 px-5">
|
||||||
|
<div>好友列表</div>
|
||||||
|
<div>{JSON.stringify(friends)}</div>
|
||||||
|
</div>
|
||||||
|
</PillTabPane>
|
||||||
|
<PillTabPane tab={'已发送'} key="2">
|
||||||
|
<div className="py-2.5 px-5">
|
||||||
|
<div>发送的好友请求</div>
|
||||||
|
<div>
|
||||||
|
{JSON.stringify(
|
||||||
|
friendRequests.filter((item) => item.from === userId)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PillTabPane>
|
||||||
|
<PillTabPane tab={'待处理'} key="3">
|
||||||
|
<div className="py-2.5 px-5">
|
||||||
|
<div>接受的好友请求</div>
|
||||||
|
<div>
|
||||||
|
{JSON.stringify(
|
||||||
|
friendRequests.filter((item) => item.to === userId)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PillTabPane>
|
||||||
|
<PillTabPane
|
||||||
|
tab={<span className="text-green-400">添加好友</span>}
|
||||||
|
key="4"
|
||||||
|
>
|
||||||
|
添加好友
|
||||||
|
</PillTabPane>
|
||||||
|
</PillTabs>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
FriendPanel.displayName = 'FriendPanel';
|
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Redirect, Route, Switch } from 'react-router-dom';
|
||||||
|
import { PageContent } from '../PageContent';
|
||||||
|
import { FriendPanel } from './Friends';
|
||||||
|
import { Sidebar } from './Sidebar';
|
||||||
|
|
||||||
|
export const Personal: React.FC = React.memo(() => {
|
||||||
|
return (
|
||||||
|
<PageContent sidebar={<Sidebar />}>
|
||||||
|
<Switch>
|
||||||
|
<Route path="/main/personal/friends" component={FriendPanel} />
|
||||||
|
|
||||||
|
<Redirect to="/main/personal/friends" />
|
||||||
|
</Switch>
|
||||||
|
</PageContent>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Personal.displayName = 'Personal';
|
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Personal } from './Personal';
|
||||||
|
import { Route, Switch, Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
|
export const MainContent: React.FC = React.memo(() => {
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
<Route path="/main/personal" component={Personal} />
|
||||||
|
{/* <Route path="/main/group/add">
|
||||||
|
<AddGroup />
|
||||||
|
</Route>
|
||||||
|
<Route path="/main/group/:groupUUID">
|
||||||
|
<Group />
|
||||||
|
</Route> */}
|
||||||
|
<Redirect to="/main/personal" />
|
||||||
|
</Switch>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
MainContent.displayName = 'MainContent';
|
@ -0,0 +1,38 @@
|
|||||||
|
import React, { useContext, useState, useCallback } from 'react';
|
||||||
|
import _noop from 'lodash/noop';
|
||||||
|
|
||||||
|
interface SidebarContextProps {
|
||||||
|
showSidebar: boolean;
|
||||||
|
switchSidebar: () => void;
|
||||||
|
setShowSidebar: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
const SidebarContext = React.createContext<SidebarContextProps>({
|
||||||
|
showSidebar: true,
|
||||||
|
switchSidebar: _noop,
|
||||||
|
setShowSidebar: _noop,
|
||||||
|
});
|
||||||
|
SidebarContext.displayName = 'SidebarContext';
|
||||||
|
|
||||||
|
export const SidebarContextProvider: React.FC = React.memo((props) => {
|
||||||
|
const [showSidebar, setShowSidebar] = useState(true);
|
||||||
|
|
||||||
|
// 切换
|
||||||
|
const switchSidebar = useCallback(() => {
|
||||||
|
setShowSidebar(!showSidebar);
|
||||||
|
}, [showSidebar]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarContext.Provider
|
||||||
|
value={{ showSidebar, switchSidebar, setShowSidebar }}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</SidebarContext.Provider>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
SidebarContextProvider.displayName = 'SidebarContextProvider';
|
||||||
|
|
||||||
|
export function useSidebarContext(): SidebarContextProps {
|
||||||
|
const context = useContext(SidebarContext);
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
@ -1,3 +1,9 @@
|
|||||||
{
|
{
|
||||||
"extends": "../tsconfig.json"
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue