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