mirror of https://github.com/msgbyte/tailchat
refactor: router and shared
parent
7b7063307d
commit
46ba5c094d
@ -0,0 +1,2 @@
|
||||
export { buildStorage } from './api/buildStorage';
|
||||
export { getStorage, setStorage, useStorage } from './manager/storage';
|
@ -0,0 +1,66 @@
|
||||
import _isFunction from 'lodash/isFunction';
|
||||
import _isEqual from 'lodash/isEqual';
|
||||
/**
|
||||
* 构建一对get set 方法
|
||||
* 用于在不同平台拥有统一方式调用体验
|
||||
*/
|
||||
|
||||
export function buildRegFn<F extends (...args: any[]) => any>(
|
||||
name: string,
|
||||
defaultFunc?: F
|
||||
) {
|
||||
let func: F;
|
||||
|
||||
const get = (...args: Parameters<F>): ReturnType<F> => {
|
||||
if (!func) {
|
||||
if (_isFunction(defaultFunc)) {
|
||||
return defaultFunc(...args);
|
||||
}
|
||||
|
||||
throw new Error(`${name} not regist`);
|
||||
}
|
||||
return func(...args);
|
||||
};
|
||||
|
||||
const set = (fn: F): void => {
|
||||
func = fn;
|
||||
};
|
||||
|
||||
return [get, set] as const;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存版本的buildRegFn
|
||||
*/
|
||||
export function buildCachedRegFn<F extends (...args: any) => any>(
|
||||
name: string,
|
||||
defaultFunc?: F
|
||||
) {
|
||||
const [get, set] = buildRegFn(name, defaultFunc);
|
||||
|
||||
let _result: any = null; // 缓存的返回值
|
||||
let _lastArgs: any;
|
||||
|
||||
const cachedGet = (...args: any) => {
|
||||
if (_result !== null && _isEqual(args, _lastArgs)) {
|
||||
// 当有缓存的返回值且两次参数一致
|
||||
return _result;
|
||||
} else {
|
||||
const result = get(...args);
|
||||
_result = result ?? null;
|
||||
_lastArgs = args;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
const refreshCache = () => {
|
||||
_result = null;
|
||||
};
|
||||
|
||||
const cachedSet = (fn: F) => {
|
||||
set(fn);
|
||||
refreshCache();
|
||||
};
|
||||
|
||||
return [cachedGet, cachedSet, refreshCache];
|
||||
}
|
@ -1,9 +1,20 @@
|
||||
{
|
||||
"name": "pawchat-shared",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"main": "index.tsx",
|
||||
"repository": "https://github.com/pawchat/pawchat.git",
|
||||
"author": "moonrailgun <moonrailgun@gmail.com>",
|
||||
"license": "GPLv3",
|
||||
"private": true
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21",
|
||||
"react-native-storage": "npm:@trpgengine/react-native-storage@^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.170"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,50 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Icon } from '@iconify/react';
|
||||
import clsx, { ClassValue } from 'clsx';
|
||||
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { useStorage } from 'pawchat-shared';
|
||||
import clsx from 'clsx';
|
||||
import { Loadable } from './components/Loadable';
|
||||
|
||||
const NavItem: React.FC<{
|
||||
className?: ClassValue;
|
||||
}> = React.memo((props) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'w-10 h-10 hover:rounded-sm bg-gray-300 mb-2 transition-all rounded-1/2 cursor-pointer flex items-center justify-center',
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
const MainRoute = Loadable(() =>
|
||||
import('./routes/Main').then((module) => module.MainRoute)
|
||||
);
|
||||
|
||||
const EntryRoute = Loadable(() =>
|
||||
import('./routes/Entry').then((module) => module.EntryRoute)
|
||||
);
|
||||
|
||||
const AppProvider: React.FC = React.memo((props) => {
|
||||
return <BrowserRouter>{props.children}</BrowserRouter>;
|
||||
});
|
||||
AppProvider.displayName = 'AppProvider';
|
||||
|
||||
export const App: React.FC = React.memo(() => {
|
||||
const [darkMode] = useStorage('darkMode', true);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen w-screen">
|
||||
<div className="w-16 bg-gray-900 flex flex-col justify-start items-center pt-4 pb-4 p-1">
|
||||
{/* Navbar */}
|
||||
<div className="flex-1">
|
||||
<NavItem />
|
||||
<div className="h-px w-full bg-white mt-4 mb-4"></div>
|
||||
<NavItem />
|
||||
<NavItem />
|
||||
<NavItem />
|
||||
<NavItem />
|
||||
<NavItem className="bg-green-500">
|
||||
<Icon className="text-3xl text-white" icon="mdi-plus" />
|
||||
</NavItem>
|
||||
</div>
|
||||
<div>
|
||||
<Icon
|
||||
className="text-3xl text-white cursor-pointer"
|
||||
icon="mdi-dots-horizontal"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-56 bg-gray-800">
|
||||
{/* Sidebar */}
|
||||
<div className="w-full h-10 hover:bg-white bg-opacity-40">目标</div>
|
||||
</div>
|
||||
<div className="flex-auto bg-gray-700">{/* Main Content */}</div>
|
||||
<div
|
||||
className={clsx({
|
||||
dark: darkMode,
|
||||
})}
|
||||
>
|
||||
<AppProvider>
|
||||
<Switch>
|
||||
<Route path="/entry" component={EntryRoute} />
|
||||
<Route path="/main" component={MainRoute} />
|
||||
<Redirect to="/entry" />
|
||||
</Switch>
|
||||
</AppProvider>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import loadable, {
|
||||
DefaultComponent,
|
||||
LoadableComponent,
|
||||
} from '@loadable/component';
|
||||
import pMinDelay from 'p-min-delay';
|
||||
import { LoadingSpinner } from './LoadingSpinner';
|
||||
|
||||
/**
|
||||
* 用法: Loadable(() => import('xxxxxx'))
|
||||
* @param loader 需要懒加载的组件
|
||||
*/
|
||||
export function Loadable<Props>(
|
||||
loadFn: (props: Props) => Promise<DefaultComponent<Props>>
|
||||
): LoadableComponent<Props> {
|
||||
return loadable((props) => pMinDelay(loadFn(props), 200), {
|
||||
fallback: <LoadingSpinner />,
|
||||
});
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
import { Icon } from '@iconify/react';
|
||||
import React from 'react';
|
||||
|
||||
interface LoadingSpinnerProps {
|
||||
tip?: string;
|
||||
}
|
||||
export const LoadingSpinner: React.FC<LoadingSpinnerProps> = React.memo(
|
||||
(props) => {
|
||||
return (
|
||||
<div>
|
||||
<Icon className="animate-spin h-5 w-5 mr-3" icon="mdi-loading" />
|
||||
{props.tip ?? 'Processing'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
@ -1,7 +1,8 @@
|
||||
import './init';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { App } from './App';
|
||||
|
||||
import 'tailwindcss/tailwind.css';
|
||||
|
||||
ReactDOM.render(React.createElement(App), document.querySelector('#app'));
|
||||
ReactDOM.render(<App />, document.querySelector('#app'));
|
||||
|
@ -0,0 +1,4 @@
|
||||
import { buildStorage, setStorage } from 'pawchat-shared';
|
||||
|
||||
const webStorage = buildStorage(window.localStorage);
|
||||
setStorage(() => webStorage);
|
@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
export const LoginView: React.FC = React.memo(() => {
|
||||
return <div>Login</div>;
|
||||
});
|
||||
LoginView.displayName = 'LoginView';
|
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { LoginView } from './LoginView';
|
||||
|
||||
export const EntryRoute = React.memo(() => {
|
||||
return (
|
||||
<div>
|
||||
<Switch>
|
||||
<Route path="/entry/login" component={LoginView} />
|
||||
<Redirect to="/entry/login" />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
EntryRoute.displayName = 'EntryRoute';
|
@ -0,0 +1,53 @@
|
||||
import { Icon } from '@iconify/react';
|
||||
import clsx, { ClassValue } from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
const NavbarNavItem: React.FC<{
|
||||
className?: ClassValue;
|
||||
}> = React.memo((props) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'w-10 h-10 hover:rounded-sm bg-gray-300 transition-all rounded-1/2 cursor-pointer flex items-center justify-center',
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export const MainRoute: React.FC = React.memo(() => {
|
||||
return (
|
||||
<div className="flex h-screen w-screen">
|
||||
<div className="w-16 bg-gray-900 flex flex-col justify-start items-center pt-4 pb-4 p-1">
|
||||
{/* Navbar */}
|
||||
<div className="flex-1">
|
||||
<NavbarNavItem />
|
||||
<div className="h-px w-full bg-white mt-4 mb-4"></div>
|
||||
<div className="space-y-2">
|
||||
<NavbarNavItem />
|
||||
<NavbarNavItem />
|
||||
<NavbarNavItem />
|
||||
<NavbarNavItem />
|
||||
<NavbarNavItem className="bg-green-500">
|
||||
<Icon className="text-3xl text-white" icon="mdi-plus" />
|
||||
</NavbarNavItem>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Icon
|
||||
className="text-3xl text-white cursor-pointer"
|
||||
icon="mdi-dots-horizontal"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-56 bg-gray-800">
|
||||
{/* Sidebar */}
|
||||
<div className="w-full h-10 hover:bg-white bg-opacity-40">目标</div>
|
||||
</div>
|
||||
<div className="flex-auto bg-gray-700">{/* Main Content */}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
MainRoute.displayName = 'MainRoute';
|
@ -1,11 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"lib": ["DOM"],
|
||||
"jsx": "react",
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"typeRoots": ["./node_modules/@types", "./types"]
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue