Added better state injection
This commit is contained in:
parent
2ef895f950
commit
dfbff5ee88
@ -3,23 +3,22 @@ import DevTools from "mobx-react-devtools";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Item } from "semantic-ui-react";
|
import { Item } from "semantic-ui-react";
|
||||||
|
|
||||||
import { UiStore } from "@app/ui";
|
import { DevicesView, MessagesView } from "@app/components";
|
||||||
import { SprinklersDevice } from "@common/sprinklers";
|
|
||||||
import { DeviceView, MessagesView } from ".";
|
|
||||||
|
|
||||||
import "@app/styles/app.css";
|
import "@app/styles/app.css";
|
||||||
import "font-awesome/css/font-awesome.css";
|
import "font-awesome/css/font-awesome.css";
|
||||||
import "semantic-ui-css/semantic.css";
|
import "semantic-ui-css/semantic.css";
|
||||||
|
|
||||||
@observer
|
class App extends React.Component {
|
||||||
export default class App extends React.Component<{ device: SprinklersDevice, uiStore: UiStore }> {
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Item.Group divided>
|
<Item.Group divided>
|
||||||
<MessagesView uiStore={this.props.uiStore} />
|
<MessagesView />
|
||||||
<DeviceView device={this.props.device} />
|
<DevicesView />
|
||||||
<DevTools />
|
<DevTools />
|
||||||
</Item.Group>
|
</Item.Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default observer(App);
|
||||||
|
@ -21,8 +21,7 @@ const ConnectionState = ({ connected }: { connected: boolean }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@observer
|
class DeviceView extends React.Component<{ device: SprinklersDevice }> {
|
||||||
export default class DeviceView extends React.Component<{ device: SprinklersDevice }> {
|
|
||||||
render() {
|
render() {
|
||||||
const { id, connected, sections, programs, sectionRunner } = this.props.device;
|
const { id, connected, sections, programs, sectionRunner } = this.props.device;
|
||||||
return (
|
return (
|
||||||
@ -45,3 +44,5 @@ export default class DeviceView extends React.Component<{ device: SprinklersDevi
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default observer(DeviceView);
|
||||||
|
14
app/components/DevicesView.tsx
Normal file
14
app/components/DevicesView.tsx
Normal file
@ -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 <DeviceView device={device} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default injectState(observer(DevicesView));
|
@ -2,8 +2,9 @@ import { observer } from "mobx-react";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Message, MessageProps, TransitionGroup } from "semantic-ui-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<{
|
class MessageView extends React.Component<{
|
||||||
uiStore: UiStore,
|
uiStore: UiStore,
|
||||||
message: UiMessage,
|
message: UiMessage,
|
||||||
@ -20,7 +21,7 @@ class MessageView extends React.Component<{
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private dismiss = (event: React.MouseEvent<HTMLElement>, data: MessageProps) => {
|
private dismiss: MessageProps["onDismiss"] = (event, data) => {
|
||||||
const { uiStore, index } = this.props;
|
const { uiStore, index } = this.props;
|
||||||
uiStore.messages.splice(index, 1);
|
uiStore.messages.splice(index, 1);
|
||||||
if (this.props.message.onDismiss) {
|
if (this.props.message.onDismiss) {
|
||||||
@ -29,18 +30,20 @@ class MessageView extends React.Component<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
class MessagesView extends React.Component<{ state: State }> {
|
||||||
export default class MessagesView extends React.Component<{ uiStore: UiStore }> {
|
|
||||||
render() {
|
render() {
|
||||||
const messages = this.props.uiStore.messages.map((message, index) => (
|
const { uiStore } = this.props.state;
|
||||||
<MessageView key={message.id} uiStore={this.props.uiStore} message={message} index={index} />
|
const messages = uiStore.messages.map((message, index) => (
|
||||||
|
<MessageView key={message.id} uiStore={uiStore} message={message} index={index} />
|
||||||
));
|
));
|
||||||
return (
|
return (
|
||||||
<div className="messages" >
|
<div className="messages" >
|
||||||
<TransitionGroup animation="scale" duration={200}>
|
<TransitionGroup animation="scale" duration={200}>
|
||||||
{messages}
|
{messages}
|
||||||
</TransitionGroup>
|
</TransitionGroup>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default injectState(observer(MessagesView));
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
export {default as App} from "./App";
|
export { default as App } from "./App";
|
||||||
export {default as DeviceView} from "./DeviceView";
|
export { default as DevicesView } from "./DevicesView";
|
||||||
export {default as DurationInput} from "./DurationInput";
|
export { default as DeviceView } from "./DeviceView";
|
||||||
export {default as MessagesView} from "./MessagesView";
|
export { default as DurationInput } from "./DurationInput";
|
||||||
export {default as ProgramTable} from "./ProgramTable";
|
export { default as MessagesView } from "./MessagesView";
|
||||||
export {default as RunSectionForm} from "./RunSectionForm";
|
export { default as ProgramTable } from "./ProgramTable";
|
||||||
export {default as SectionRunnerView} from "./SectionRunnerView";
|
export { default as RunSectionForm } from "./RunSectionForm";
|
||||||
export {default as SectionTable} from "./SectionTable";
|
export { default as SectionRunnerView } from "./SectionRunnerView";
|
||||||
|
export { default as SectionTable } from "./SectionTable";
|
||||||
|
16
app/di.ts
16
app/di.ts
@ -1,16 +0,0 @@
|
|||||||
import * as PropTypes from "prop-types";
|
|
||||||
import * as React from "react";
|
|
||||||
|
|
||||||
export class Provider<T extends {}> extends React.Component<T> {
|
|
||||||
static childContextTypes = {
|
|
||||||
injected: any,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function inject<P, T extends React.ComponentType<P>>(...names: string[]): (base: T) => React.ComponentClass<T> {
|
|
||||||
return (Component) => {
|
|
||||||
return class extends React.Component<T> {
|
|
||||||
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
@ -3,13 +3,19 @@ import * as ReactDOM from "react-dom";
|
|||||||
import { AppContainer } from "react-hot-loader";
|
import { AppContainer } from "react-hot-loader";
|
||||||
|
|
||||||
import App from "@app/components/App";
|
import App from "@app/components/App";
|
||||||
|
import { ProvideState, State } from "@app/state";
|
||||||
|
|
||||||
|
const state = new State();
|
||||||
|
state.start();
|
||||||
|
|
||||||
const rootElem = document.getElementById("app");
|
const rootElem = document.getElementById("app");
|
||||||
|
|
||||||
const doRender = (Component: typeof App) => {
|
const doRender = (Component: React.ComponentType) => {
|
||||||
ReactDOM.render((
|
ReactDOM.render((
|
||||||
<AppContainer>
|
<AppContainer>
|
||||||
<Component device={device} uiStore={uiStore} />
|
<ProvideState state={state}>
|
||||||
|
<Component />
|
||||||
|
</ProvideState>
|
||||||
</AppContainer>
|
</AppContainer>
|
||||||
), rootElem);
|
), rootElem);
|
||||||
};
|
};
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import { MqttApiClient } from "@app/mqtt";
|
import { MqttApiClient } from "@app/mqtt";
|
||||||
import { Message, UiStore } from "@app/ui";
|
|
||||||
import { ISprinklersApi, SprinklersDevice } from "@common/sprinklers";
|
import { ISprinklersApi, SprinklersDevice } from "@common/sprinklers";
|
||||||
|
|
||||||
|
import { UiMessage, UiStore } from "./ui";
|
||||||
|
export { UiMessage, UiStore };
|
||||||
|
export * from "./inject";
|
||||||
|
|
||||||
export class State {
|
export class State {
|
||||||
client: ISprinklersApi = new MqttApiClient();
|
client: ISprinklersApi = new MqttApiClient();
|
||||||
device: SprinklersDevice;
|
device: SprinklersDevice;
|
||||||
uiStore = new UiStore();
|
uiStore = new UiStore();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const device = this.client.getDevice("grinklers");
|
this.device = this.client.getDevice("grinklers");
|
||||||
this.uiStore.addMessage({ header: "asdf", content: "boo!", error: true });
|
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;
|
||||||
|
43
app/state/inject.tsx
Normal file
43
app/state/inject.tsx
Normal file
@ -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<any> = {
|
||||||
|
providedState: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ProvideState extends React.Component<{
|
||||||
|
state: State,
|
||||||
|
}> implements React.ChildContextProvider<IProvidedStateContext> {
|
||||||
|
static childContextTypes = providedStateContextTypes;
|
||||||
|
|
||||||
|
getChildContext(): IProvidedStateContext {
|
||||||
|
return {
|
||||||
|
providedState: this.props.state,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return React.Children.only(this.props.children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T];
|
||||||
|
type Omit<T, K extends keyof T> = {[P in Diff<keyof T, K>]: T[P]};
|
||||||
|
|
||||||
|
export function injectState<P extends { "state": State }, T extends React.ComponentClass<P>>(Component: T) {
|
||||||
|
return class extends React.Component<Omit<P, "state">> {
|
||||||
|
static contextTypes = providedStateContextTypes;
|
||||||
|
context: IProvidedStateContext;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const state = this.context.providedState;
|
||||||
|
return <Component {...this.props} state={state} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
@ -3,13 +3,12 @@ import { MessageProps } from "semantic-ui-react";
|
|||||||
|
|
||||||
import { getRandomId } from "@common/utils";
|
import { getRandomId } from "@common/utils";
|
||||||
|
|
||||||
export interface Message extends MessageProps {
|
export interface UiMessage extends MessageProps {
|
||||||
id: number;
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UiStore {
|
export class UiStore {
|
||||||
@observable
|
messages: IObservableArray<UiMessage> = observable.array();
|
||||||
messages: IObservableArray<Message> = observable.array();
|
|
||||||
|
|
||||||
addMessage(message: MessageProps) {
|
addMessage(message: MessageProps) {
|
||||||
this.messages.push(observable({
|
this.messages.push(observable({
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
|
"experimentalDecorators": true,
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"experimentalDecorators": true,
|
|
||||||
"target": "es5"
|
"target": "es5"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,7 @@
|
|||||||
"description": "A frontend for mqtt based IoT sprinklers systems",
|
"description": "A frontend for mqtt based IoT sprinklers systems",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"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:app": "webpack --config ./app/webpack/prod.config.js",
|
||||||
"build:server": "tsc --project server",
|
"build:server": "tsc --project server",
|
||||||
"watch:app": "yarn build:app --watch",
|
"watch:app": "yarn build:app --watch",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user