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",
|
"name": "pawchat-shared",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "index.js",
|
"main": "index.tsx",
|
||||||
"repository": "https://github.com/pawchat/pawchat.git",
|
"repository": "https://github.com/pawchat/pawchat.git",
|
||||||
"author": "moonrailgun <moonrailgun@gmail.com>",
|
"author": "moonrailgun <moonrailgun@gmail.com>",
|
||||||
"license": "GPLv3",
|
"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 React from 'react';
|
||||||
import { Icon } from '@iconify/react';
|
import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom';
|
||||||
import clsx, { ClassValue } from 'clsx';
|
import { useStorage } from 'pawchat-shared';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { Loadable } from './components/Loadable';
|
||||||
|
|
||||||
const NavItem: React.FC<{
|
const MainRoute = Loadable(() =>
|
||||||
className?: ClassValue;
|
import('./routes/Main').then((module) => module.MainRoute)
|
||||||
}> = 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 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(() => {
|
export const App: React.FC = React.memo(() => {
|
||||||
|
const [darkMode] = useStorage('darkMode', true);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-screen w-screen">
|
<div
|
||||||
<div className="w-16 bg-gray-900 flex flex-col justify-start items-center pt-4 pb-4 p-1">
|
className={clsx({
|
||||||
{/* Navbar */}
|
dark: darkMode,
|
||||||
<div className="flex-1">
|
})}
|
||||||
<NavItem />
|
>
|
||||||
<div className="h-px w-full bg-white mt-4 mb-4"></div>
|
<AppProvider>
|
||||||
<NavItem />
|
<Switch>
|
||||||
<NavItem />
|
<Route path="/entry" component={EntryRoute} />
|
||||||
<NavItem />
|
<Route path="/main" component={MainRoute} />
|
||||||
<NavItem />
|
<Redirect to="/entry" />
|
||||||
<NavItem className="bg-green-500">
|
</Switch>
|
||||||
<Icon className="text-3xl text-white" icon="mdi-plus" />
|
</AppProvider>
|
||||||
</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>
|
</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 React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { App } from './App';
|
import { App } from './App';
|
||||||
|
|
||||||
import 'tailwindcss/tailwind.css';
|
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": {
|
"compilerOptions": {
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"moduleResolution": "node",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"lib": ["DOM"],
|
"lib": ["DOM"],
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
"typeRoots": ["./node_modules/@types", "./types"]
|
"typeRoots": ["./node_modules/@types", "./types"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "CommonJS",
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue