From dfbff5ee88fc87577a9d2a695aa20862bce3e897 Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Sun, 10 Sep 2017 12:30:23 -0600 Subject: [PATCH] Added better state injection --- app/components/App.tsx | 13 +++++----- app/components/DeviceView.tsx | 5 ++-- app/components/DevicesView.tsx | 14 +++++++++++ app/components/MessagesView.tsx | 21 +++++++++------- app/components/index.ts | 17 +++++++------ app/di.ts | 16 ------------ app/index.tsx | 10 ++++++-- app/state/index.ts | 11 ++++++--- app/state/inject.tsx | 43 +++++++++++++++++++++++++++++++++ app/{ => state}/ui.ts | 5 ++-- common/tsconfig.json | 2 +- package.json | 2 +- 12 files changed, 106 insertions(+), 53 deletions(-) create mode 100644 app/components/DevicesView.tsx delete mode 100644 app/di.ts create mode 100644 app/state/inject.tsx rename app/{ => state}/ui.ts (73%) diff --git a/app/components/App.tsx b/app/components/App.tsx index 0017f84..a49d062 100644 --- a/app/components/App.tsx +++ b/app/components/App.tsx @@ -3,23 +3,22 @@ import DevTools from "mobx-react-devtools"; import * as React from "react"; import { Item } from "semantic-ui-react"; -import { UiStore } from "@app/ui"; -import { SprinklersDevice } from "@common/sprinklers"; -import { DeviceView, MessagesView } from "."; +import { DevicesView, MessagesView } from "@app/components"; import "@app/styles/app.css"; import "font-awesome/css/font-awesome.css"; import "semantic-ui-css/semantic.css"; -@observer -export default class App extends React.Component<{ device: SprinklersDevice, uiStore: UiStore }> { +class App extends React.Component { render() { return ( - - + + ); } } + +export default observer(App); diff --git a/app/components/DeviceView.tsx b/app/components/DeviceView.tsx index 76510d2..d7d19c3 100644 --- a/app/components/DeviceView.tsx +++ b/app/components/DeviceView.tsx @@ -21,8 +21,7 @@ const ConnectionState = ({ connected }: { connected: boolean }) => { ); }; -@observer -export default class DeviceView extends React.Component<{ device: SprinklersDevice }> { +class DeviceView extends React.Component<{ device: SprinklersDevice }> { render() { const { id, connected, sections, programs, sectionRunner } = this.props.device; return ( @@ -45,3 +44,5 @@ export default class DeviceView extends React.Component<{ device: SprinklersDevi ); } } + +export default observer(DeviceView); diff --git a/app/components/DevicesView.tsx b/app/components/DevicesView.tsx new file mode 100644 index 0000000..5e37918 --- /dev/null +++ b/app/components/DevicesView.tsx @@ -0,0 +1,14 @@ +import { observer } from "mobx-react"; +import * as React from "react"; + +import DeviceView from "@app/components/DeviceView"; +import { injectState, State } from "@app/state"; + +class DevicesView extends React.Component<{ state: State }> { + render() { + const { device } = this.props.state; + return ; + } +} + +export default injectState(observer(DevicesView)); diff --git a/app/components/MessagesView.tsx b/app/components/MessagesView.tsx index 1a8726d..85beea6 100644 --- a/app/components/MessagesView.tsx +++ b/app/components/MessagesView.tsx @@ -2,8 +2,9 @@ import { observer } from "mobx-react"; import * as React from "react"; import { Message, MessageProps, TransitionGroup } from "semantic-ui-react"; -import { Message as UiMessage, UiStore } from "@app/ui"; +import { injectState, State, UiMessage, UiStore } from "@app/state/"; +@observer class MessageView extends React.Component<{ uiStore: UiStore, message: UiMessage, @@ -20,7 +21,7 @@ class MessageView extends React.Component<{ ); } - private dismiss = (event: React.MouseEvent, data: MessageProps) => { + private dismiss: MessageProps["onDismiss"] = (event, data) => { const { uiStore, index } = this.props; uiStore.messages.splice(index, 1); if (this.props.message.onDismiss) { @@ -29,18 +30,20 @@ class MessageView extends React.Component<{ } } -@observer -export default class MessagesView extends React.Component<{ uiStore: UiStore }> { +class MessagesView extends React.Component<{ state: State }> { render() { - const messages = this.props.uiStore.messages.map((message, index) => ( - + const { uiStore } = this.props.state; + const messages = uiStore.messages.map((message, index) => ( + )); return (
- - {messages} - + + {messages} +
); } } + +export default injectState(observer(MessagesView)); diff --git a/app/components/index.ts b/app/components/index.ts index 3b59144..ad43dc4 100644 --- a/app/components/index.ts +++ b/app/components/index.ts @@ -1,8 +1,9 @@ -export {default as App} from "./App"; -export {default as DeviceView} from "./DeviceView"; -export {default as DurationInput} from "./DurationInput"; -export {default as MessagesView} from "./MessagesView"; -export {default as ProgramTable} from "./ProgramTable"; -export {default as RunSectionForm} from "./RunSectionForm"; -export {default as SectionRunnerView} from "./SectionRunnerView"; -export {default as SectionTable} from "./SectionTable"; +export { default as App } from "./App"; +export { default as DevicesView } from "./DevicesView"; +export { default as DeviceView } from "./DeviceView"; +export { default as DurationInput } from "./DurationInput"; +export { default as MessagesView } from "./MessagesView"; +export { default as ProgramTable } from "./ProgramTable"; +export { default as RunSectionForm } from "./RunSectionForm"; +export { default as SectionRunnerView } from "./SectionRunnerView"; +export { default as SectionTable } from "./SectionTable"; diff --git a/app/di.ts b/app/di.ts deleted file mode 100644 index fa38655..0000000 --- a/app/di.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as PropTypes from "prop-types"; -import * as React from "react"; - -export class Provider extends React.Component { - static childContextTypes = { - injected: any, - }; -} - -export function inject>(...names: string[]): (base: T) => React.ComponentClass { - return (Component) => { - return class extends React.Component { - - }; - }; -} diff --git a/app/index.tsx b/app/index.tsx index 1fb4765..2b8e6cd 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -3,13 +3,19 @@ import * as ReactDOM from "react-dom"; import { AppContainer } from "react-hot-loader"; import App from "@app/components/App"; +import { ProvideState, State } from "@app/state"; + +const state = new State(); +state.start(); const rootElem = document.getElementById("app"); -const doRender = (Component: typeof App) => { +const doRender = (Component: React.ComponentType) => { ReactDOM.render(( - + + + ), rootElem); }; diff --git a/app/state/index.ts b/app/state/index.ts index 91ff165..7ce8922 100644 --- a/app/state/index.ts +++ b/app/state/index.ts @@ -1,14 +1,17 @@ import { MqttApiClient } from "@app/mqtt"; -import { Message, UiStore } from "@app/ui"; import { ISprinklersApi, SprinklersDevice } from "@common/sprinklers"; +import { UiMessage, UiStore } from "./ui"; +export { UiMessage, UiStore }; +export * from "./inject"; + export class State { client: ISprinklersApi = new MqttApiClient(); device: SprinklersDevice; uiStore = new UiStore(); constructor() { - const device = this.client.getDevice("grinklers"); + this.device = this.client.getDevice("grinklers"); this.uiStore.addMessage({ header: "asdf", content: "boo!", error: true }); } @@ -17,6 +20,6 @@ export class State { } } -const state = new State(); +// const state = new State(); -export default state; +// export default state; diff --git a/app/state/inject.tsx b/app/state/inject.tsx new file mode 100644 index 0000000..0196872 --- /dev/null +++ b/app/state/inject.tsx @@ -0,0 +1,43 @@ +import * as PropTypes from "prop-types"; +import * as React from "react"; + +import { State } from "@app/state"; + +interface IProvidedStateContext { + providedState: State; +} + +const providedStateContextTypes: PropTypes.ValidationMap = { + providedState: PropTypes.object, +}; + +export class ProvideState extends React.Component<{ + state: State, +}> implements React.ChildContextProvider { + static childContextTypes = providedStateContextTypes; + + getChildContext(): IProvidedStateContext { + return { + providedState: this.props.state, + }; + } + + render() { + return React.Children.only(this.props.children); + } +} + +type Diff = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T]; +type Omit = {[P in Diff]: T[P]}; + +export function injectState

>(Component: T) { + return class extends React.Component> { + static contextTypes = providedStateContextTypes; + context: IProvidedStateContext; + + render() { + const state = this.context.providedState; + return ; + } + }; +} diff --git a/app/ui.ts b/app/state/ui.ts similarity index 73% rename from app/ui.ts rename to app/state/ui.ts index d865132..7a5a067 100644 --- a/app/ui.ts +++ b/app/state/ui.ts @@ -3,13 +3,12 @@ import { MessageProps } from "semantic-ui-react"; import { getRandomId } from "@common/utils"; -export interface Message extends MessageProps { +export interface UiMessage extends MessageProps { id: number; } export class UiStore { - @observable - messages: IObservableArray = observable.array(); + messages: IObservableArray = observable.array(); addMessage(message: MessageProps) { this.messages.push(observable({ diff --git a/common/tsconfig.json b/common/tsconfig.json index aad4391..97737e9 100644 --- a/common/tsconfig.json +++ b/common/tsconfig.json @@ -1,6 +1,6 @@ { + "experimentalDecorators": true, "compilerOptions": { - "experimentalDecorators": true, "target": "es5" } } \ No newline at end of file diff --git a/package.json b/package.json index 79fa44a..06b9722 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "description": "A frontend for mqtt based IoT sprinklers systems", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "clean": "rm -rf ./dist ./build", + "clean": "rm -rf ./dist ./build ./public", "build:app": "webpack --config ./app/webpack/prod.config.js", "build:server": "tsc --project server", "watch:app": "yarn build:app --watch",