refactor: router and shared

pull/13/head
moonrailgun 4 years ago
parent 7b7063307d
commit 46ba5c094d

@ -0,0 +1,106 @@
import 'regenerator-runtime/runtime'; // react-native-storage 需要, 确保其存在
import Storage, { NotFoundError } from 'react-native-storage';
import _isNil from 'lodash/isNil';
/**
*
*/
export function buildStorage(backend: any) {
const storage = new Storage({
// 最大容量默认值1000条数据循环存储
size: 1000,
// 存储引擎对于RN使用AsyncStorage对于web使用window.localStorage
// 如果不指定则数据只会保存在内存中,重启后即丢失
// storageBackend:
// config.platform === 'app'
// ? require('react-native').AsyncStorage
// : window.localStorage,
storageBackend: backend,
// 数据过期时间默认一整天1000 * 3600 * 24 毫秒设为null则永不过期
defaultExpires: 1000 * 3600 * 24,
// 读写时在内存中缓存数据。默认启用。
enableCache: true,
// 如果storage中没有相应数据或数据已过期
// 则会调用相应的sync方法无缝返回最新数据。
// sync方法的具体说明会在后文提到
// 你可以在构造函数这里就写好sync的方法
// 或是在任何时候直接对storage.sync进行赋值修改
// 或是写到另一个文件里这里require引入
// sync: require('你可以另外写一个文件专门处理sync')
});
const rnStorage = {
set: async (key: string, data: any) => {
try {
if (!!key && typeof key === 'string' && !_isNil(data)) {
await storage.save({ key, data });
}
} catch (e) {
console.error(e);
}
return data;
},
/**
*
* set1
*/
setWithExpires: async (key: string, data: any, expires: number) => {
try {
if (!!key && typeof key === 'string' && !_isNil(data)) {
await storage.save({ key, data, expires });
}
} catch (e) {
console.error(e);
}
return data;
},
get: async (key: string, defaultVal?: any) => {
let res: any;
try {
res = await storage.load({
key,
autoSync: true,
syncInBackground: false,
});
} catch (e) {
if (!(e instanceof NotFoundError)) {
// 过滤NotFoundError
console.log(`get key ${key} error:`, e.toString());
}
res = _isNil(defaultVal) ? null : defaultVal;
}
return res;
},
remove: async (key) => {
await storage.remove({ key });
},
/**
* ,
* TODO: key
*/
save: async (key: string, data: any) => {
try {
if (!!key && typeof key === 'string' && !_isNil(data)) {
await storage.save({
key,
data,
expires: null,
});
}
} catch (e) {
console.error(e);
}
return data!;
},
};
return rnStorage;
}

@ -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];
}

@ -0,0 +1,56 @@
import { buildRegFn } from './buildRegFn';
import { useCallback, useLayoutEffect, useState } from 'react';
export interface StorageObject {
/**
* NOTICE: save set 1
*/
set: (key: string, data: any) => Promise<void>;
get: (key: string, defaultVal?: any) => Promise<any>;
remove: (key: string) => Promise<void>;
save: (key: string, data: any) => Promise<void>;
}
/**
*
*/
export const [getStorage, setStorage] =
buildRegFn<() => StorageObject>('storage');
/**
*
* @param key
* @param defaultValue
*/
export function useStorage<T>(
key: string,
defaultValue?: T
): [T, (v: T) => void] {
const [value, setValue] = useState<T>(defaultValue);
useLayoutEffect(() => {
getStorage()
.get(key)
.then((data: T) => {
setValue(data);
});
}, [key]);
const set = useCallback(
(newVal: T) => {
setValue(newVal);
getStorage().set(key, newVal);
},
[key]
);
const save = useCallback(
(newVal: T) => {
setValue(newVal);
getStorage().save(key, newVal);
},
[key]
);
return [value, { set, save }];
}

@ -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"
}
}

@ -7,17 +7,22 @@
"license": "GPLv3",
"private": true,
"scripts": {
"build": "webpack",
"dev": "NODE_ENV=development webpack serve"
"build": "TS_NODE_PROJECT='tsconfig.node.json' webpack",
"dev": "TS_NODE_PROJECT='tsconfig.node.json' NODE_ENV=development webpack serve"
},
"dependencies": {
"@iconify/iconify": "^2.0.2",
"@iconify/react": "^3.0.0-alpha.1",
"@loadable/component": "^5.15.0",
"antd": "^19.9.89",
"clsx": "^1.1.1",
"p-min-delay": "^4.0.0",
"pawchat-shared": "*",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-use": "^17.2.4",
"socket.io-client": "^4.1.2",
"tailwindcss": "^2.2.4"
},
@ -26,6 +31,8 @@
"@types/node": "^15.12.5",
"@types/react": "^17.0.11",
"@types/react-dom": "^17.0.8",
"@types/react-router": "^5.1.15",
"@types/react-router-dom": "^5.1.7",
"@types/webpack": "^5.28.0",
"@types/webpack-dev-server": "^3.11.4",
"autoprefixer": "^10.2.6",
@ -37,6 +44,7 @@
"postcss-loader": "^6.1.0",
"style-loader": "^3.0.0",
"ts-node": "^10.0.0",
"tsconfig-paths": "^3.9.0",
"typescript": "^4.3.4",
"webpack": "^5.41.0",
"webpack-cli": "^4.7.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",
}
}

@ -33,6 +33,7 @@ const config: Configuration = {
},
devServer: {
port: 11011,
historyApiFallback: true,
},
module: {
rules: [

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save