This commit is contained in:
crusader 2017-07-20 18:46:42 +09:00
parent e0f91c03a0
commit f7504d9951
11 changed files with 285 additions and 59 deletions

View File

@ -24,12 +24,12 @@
"@types/history": "^4.6.0",
"@types/jest": "^19.2.4",
"@types/prop-types": "^15.5.1",
"@types/react": "^15.0.32",
"@types/react": "^15.0.38",
"@types/react-addons-test-utils": "^0.14.19",
"@types/react-dom": "^15.5.1",
"@types/react-redux": "^4.4.45",
"@types/react-router": "^4.0.12",
"@types/react-router-dom": "^4.0.5",
"@types/react-redux": "^4.4.46",
"@types/react-router": "^4.0.14",
"@types/react-router-dom": "^4.0.7",
"@types/react-tap-event-plugin": "^0.0.30",
"@types/redux": "^3.6.0",
"awesome-typescript-loader": "^3.1.3",
@ -71,8 +71,8 @@
"react-dom": "^15.6.1",
"react-immutable-proptypes": "^2.1.0",
"react-redux": "^5.0.5",
"react-router": "^4.1.1",
"react-router-dom": "^4.1.1",
"react-router": "^4.1.2",
"react-router-dom": "^4.1.2",
"react-router-redux": "next",
"react-tap-event-plugin": "^2.0.1",
"redux": "^3.7.1",

View File

@ -16,7 +16,7 @@
</head>
<body>
<div id="react-placeholder"></div>
<div id="appContainer"></div>
</body>
</html>

View File

@ -0,0 +1,29 @@
// Container Configuration
export interface ContainerConfig {
placeholderID: string;
}
const containerConfig: ContainerConfig = {
placeholderID: 'appContainer',
};
// RPC Server Configuration
export interface RPCConfig {
url: string;
}
const rpcConfig: RPCConfig = {
url: 'ws://127.0.0.1:18081/rpc',
};
// Configuration
export interface Config {
container: ContainerConfig;
rpc: RPCConfig;
}
const config: Config = {
container: containerConfig,
rpc: rpcConfig,
};
export default config;

View File

@ -5,11 +5,19 @@ import { fork } from 'redux-saga/effects';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import * as injectTapEventPlugin from 'react-tap-event-plugin';
import { Store } from 'redux';
import createSagaMiddleware, { SagaMiddleware } from 'redux-saga';
import Platform from '@overflow/commons/platform';
import AppContext from '@overflow/commons/context';
import * as AppContextLifecycleActions from '@overflow/commons/context/redux/action/lifecycle';
import WebSocketRPC from '@overflow/commons/websocket/WebSocketRPC';
import { History } from 'history';
import { store, sagaMiddleware, history } from './redux/store';
import appConfig, { Config } from './config';
import sagas from './redux/saga';
// import routes from './router';
@ -17,9 +25,76 @@ import {Layout} from './views/layout/Layout';
injectTapEventPlugin();
function* app(): any {
const appContainer: HTMLElement = yield Platform.getAppContainer('react-placeholder');
const rpcURL = 'ws://127.0.0.1:18081/rpc';
class App {
private config: Config;
private container: HTMLElement;
private context: AppContext;
private rpcClient: WebSocketRPC;
private store: Store<any>;
private sagaMiddleware: SagaMiddleware<any>;
private history: History;
public constructor() {
this.config = appConfig;
this.sagaMiddleware = sagaMiddleware;
this.store = store;
}
public async start(): Promise<void> {
try {
this.container = await Platform.getAppContainer(this.config.container.placeholderID);
this.displayLoading();
this.context = await this.initContext();
// this.rpcClient = await this.initRpcClient();
await this.initSagaEffect();
this.store.dispatch(AppContextLifecycleActions.initialized());
this.displayApp();
} catch (e) {
this.displayError(e);
}
}
public initContext(): Promise<AppContext> {
const appContext = new Promise<AppContext>(resolve => {
const context = AppContext.getContext();
resolve(context);
});
return appContext;
}
public initRpcClient(): Promise<WebSocketRPC> {
const rpcClient = new Promise<WebSocketRPC>((resolve, reject) => {
let client = new WebSocketRPC(this.config.rpc.url);
client.initialize()
.then(() => {
resolve(client);
})
.catch((err: any) => {
reject(err);
});
});
return rpcClient;
}
public initSagaEffect(): Promise<void> {
const rpcClient = new Promise<void>(resolve => {
this.sagaMiddleware.run(sagas);
resolve();
});
return rpcClient;
}
private displayLoading(): void {
ReactDOM.render(
<div style={{
width: '100vw',
@ -31,16 +106,36 @@ function* app(): any {
}}>
<h1>Loading...</h1>
</div>,
appContainer,
this.container,
);
}
sagaMiddleware.run(sagas);
private displayError(e: Error): void {
ReactDOM.render(
<div style={{
width: '100vw',
height: '100vh',
backgroundColor: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}>
<h1>{e.message}</h1>
</div>,
this.container,
);
}
private displayApp(): void {
ReactDOM.render(
<div><Layout /></div>,
appContainer,
this.container,
);
}
}
sagaMiddleware.run(app);
let app = new App();
app.start();

View File

@ -16,6 +16,11 @@ import { PWChange } from '@overflow/member/react/components/PWChange';
import { TableExampleSortable } from '@overflow/temp/react/components/TableExampleSortable';
interface AppRouter {
}
const routes = (
<Switch>
{/*<Route exact path='/'/>

View File

@ -15,12 +15,23 @@ class AppContext {
return null;
}
public static put<T>(instance: T): void {
return null;
}
public static getContext(): AppContext {
if (null === AppContext.context) {
AppContext.context = new AppContext();
}
return AppContext.context;
}
public static destroy(): void {
console.log('AppContext has been destroyed.');
}
}
export default AppContext;

View File

@ -0,0 +1,24 @@
import Action from '@overflow/commons/redux/Action';
// Action Type
export type INITIALIZED = '@overflow/commons/context/INITIALIZED';
export type WILL_DESTROY = '@overflow/commons/context/WILL_DESTROY';
export const INITIALIZED: INITIALIZED = '@overflow/commons/context/INITIALIZED';
export const WILL_DESTROY: WILL_DESTROY = '@overflow/commons/context/WILL_DESTROY';
// Action Creater
export type initialized = () => Action;
export type willDestroy = () => Action;
export const initialized: initialized = (): Action => {
return {
type: INITIALIZED,
};
};
export const willDestroy: willDestroy = (): Action => {
return {
type: WILL_DESTROY,
};
};

View File

@ -0,0 +1,25 @@
import { SagaIterator } from 'redux-saga';
import { call, fork, take } from 'redux-saga/effects';
import * as LifecycleActions from '../action/lifecycle';
import AppContext from '../../';
function _destroy(): Promise<void> {
return new Promise(resolve => {
AppContext.destroy();
resolve();
});
}
function* destroy(): SagaIterator {
yield take(LifecycleActions.WILL_DESTROY);
yield call(_destroy);
}
export default function* root(): SagaIterator {
yield fork(destroy);
}

View File

@ -1,7 +1,9 @@
import AppContext from '../context';
abstract class Platform {
public static * getAppContainer(containerId: string): IterableIterator<Promise<HTMLElement>> {
const appContainer = yield new Promise<HTMLElement>(resolve => {
public static getAppContainer(containerId: string): Promise<HTMLElement> {
const appContainer = new Promise<HTMLElement>(resolve => {
document.addEventListener('DOMContentLoaded', () => {
resolve(document.getElementById(containerId));
});
@ -9,6 +11,16 @@ abstract class Platform {
return appContainer;
}
public static * getAppContext(): IterableIterator<Promise<AppContext>> {
const appContext = yield new Promise<AppContext>(resolve => {
const context = AppContext.getContext();
resolve(context);
});
return appContext;
}
}
export default Platform;

View File

@ -9,9 +9,7 @@ import {
RPCRequest,
RPCResponse,
} from './protocol/rpc';
import injectable from '../context/decorator/injectable';
export type OnConnectFunc = () => void;
export type OnDisconnectFunc = () => void;
export type OnResponseFunc = (response: any) => void;
@ -22,28 +20,42 @@ interface RequestQueue {
reject: (reason?: any) => void;
}
@injectable()
enum WebSocketStatus {
NOT_CONNECT = 1,
WILL_CONNECT,
CONNECTED,
WILL_DISCONNECT,
}
enum WebSocketReadyState {
CONNECTING = 0,
OPEN = 1,
CLOSING = 2,
CLOSED = 3,
}
export default class WebSocketRPC {
private url: string;
private connStatus: WebSocketStatus;
private conn: WebSocket;
private isReady: boolean;
private requestID: ID_TYPE = 0;
private onConnectListeners: OnConnectFunc[] = [];
private onDisconnectListeners: OnDisconnectFunc[] = [];
private onResponseListeners: Map<string, OnResponseFunc[]>;
private requestQueue: Map<ID_TYPE, RequestQueue>;
/**
* name
*/
public constructor(url: string) {
this.isReady = false;
this.connStatus = WebSocketStatus.NOT_CONNECT;
this.url = url;
this.onResponseListeners = new Map();
this.requestQueue = new Map();
this.initialize();
}
public Call(method: string, params: any[]): Promise<any> {
@ -59,33 +71,49 @@ export default class WebSocketRPC {
});
}
public OnConnect(cb: OnConnectFunc): void {
if (this.isReady) {
cb();
}
this.onConnectListeners.push(cb);
}
public OnDisconnect(cb: OnDisconnectFunc): void {
this.onDisconnectListeners.push(cb);
}
private initialize(): void {
public initialize(): Promise<void> {
return new Promise<void>((resolve, reject) => {
try {
this.conn = new WebSocket(this.url);
this.conn.onopen = (e: Event): any => {
this.fireOnConnect();
} catch(e) {
console.log(e);
}
this.connStatus = WebSocketStatus.WILL_CONNECT;
this.conn.onopen = (ev: Event): any => {
this.connStatus = WebSocketStatus.CONNECTED;
this.attachCallback();
resolve();
this.isReady = true;
return null;
};
this.conn.onclose = (e: CloseEvent): any => {
this.conn.onerror = (ev: Event): any => {
this.connStatus = WebSocketStatus.NOT_CONNECT;
reject(new Error('Cannot connect to API Server.'));
return null;
};
});
}
private attachCallback(): void {
this.conn.onopen = (ev: Event): any => {
this.connStatus = WebSocketStatus.CONNECTED;
this.isReady = true;
return null;
};
this.conn.onclose = (ev: CloseEvent): any => {
this.connStatus = WebSocketStatus.NOT_CONNECT;
this.fireOnDisconnect();
return null;
};
this.conn.onmessage = (e: MessageEvent): any => {
let response: ProtocolResponse<ID_TYPE> = JSON.parse(e.data);
this.conn.onmessage = (ev: MessageEvent): any => {
let response: ProtocolResponse<ID_TYPE> = JSON.parse(ev.data);
let res: RPCResponse = JSON.parse(response.getBody());
const requestID = response.getID();
const error = response.getError();
@ -105,16 +133,15 @@ export default class WebSocketRPC {
// perhaps sever side send message
}
this.fireOnMessage(e);
this.fireOnMessage(ev);
};
this.conn.onerror = (ev: Event): any => {
console.log(ev);
return null;
};
}
private fireOnConnect(): void {
for (let i = 0; i < this.onConnectListeners.length; i++) {
this.onConnectListeners[i]();
}
}
private fireOnDisconnect(): void {
for (let i = 0; i < this.onDisconnectListeners.length; i++) {

View File

@ -9,7 +9,6 @@ import { Button,
import { TargetTable } from '@overflow/temp/react/components/Targets';
import Probe from '@overflow/probe/api/model/Probe';
export interface StateProps {
probe: Probe;
}
@ -45,7 +44,6 @@ export class ProbeDetail extends React.Component<Props, State> {
}
}
export class ProbeBasicInfo extends React.Component<Props, State> {
constructor(props: Props, context: State) {