Lots of improvements
This commit is contained in:
		
							parent
							
								
									6e95d091ae
								
							
						
					
					
						commit
						f328e5c2e2
					
				@ -1,6 +1,6 @@
 | 
			
		||||
// import DevTools from "mobx-react-devtools";
 | 
			
		||||
import * as React from "react";
 | 
			
		||||
import { Redirect, Route, Switch } from "react-router";
 | 
			
		||||
import { Redirect, Route, Switch, withRouter } from "react-router";
 | 
			
		||||
import { Container } from "semantic-ui-react";
 | 
			
		||||
 | 
			
		||||
import { MessagesView, NavBar } from "@client/components";
 | 
			
		||||
@ -30,7 +30,7 @@ function NavContainer() {
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function App() {
 | 
			
		||||
function App() {
 | 
			
		||||
  return (
 | 
			
		||||
    <Switch>
 | 
			
		||||
      <Route path={route.login} component={p.LoginPage} />
 | 
			
		||||
@ -39,3 +39,5 @@ export default function App() {
 | 
			
		||||
    </Switch>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default withRouter(App);
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ import * as classNames from "classnames";
 | 
			
		||||
import { observer } from "mobx-react";
 | 
			
		||||
import * as React from "react";
 | 
			
		||||
import { Link } from "react-router-dom";
 | 
			
		||||
import { Grid, Header, Icon, Item, SemanticICONS, Dimmer, Segment } from "semantic-ui-react";
 | 
			
		||||
import { Dimmer, Grid, Header, Icon, Item, SemanticICONS } from "semantic-ui-react";
 | 
			
		||||
 | 
			
		||||
import { DeviceImage } from "@client/components";
 | 
			
		||||
import * as p from "@client/pages";
 | 
			
		||||
@ -62,7 +62,7 @@ const ConnectionState = observer(
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
interface DeviceViewProps {
 | 
			
		||||
interface DeviceViewProps extends RouteComponentProps {
 | 
			
		||||
  deviceId: number;
 | 
			
		||||
  appState: AppState;
 | 
			
		||||
  inList?: boolean;
 | 
			
		||||
@ -189,4 +189,4 @@ class DeviceView extends React.Component<DeviceViewProps> {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default injectState(observer(DeviceView));
 | 
			
		||||
export default withRouter(injectState(observer(DeviceView)));
 | 
			
		||||
 | 
			
		||||
@ -30,7 +30,7 @@ class MessageView extends React.Component<{
 | 
			
		||||
    if (message.onDismiss) {
 | 
			
		||||
      message.onDismiss(event, data);
 | 
			
		||||
    }
 | 
			
		||||
    uiStore.messages.remove(message);
 | 
			
		||||
    uiStore.removeMessage(message);
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { observer } from "mobx-react";
 | 
			
		||||
import * as React from "react";
 | 
			
		||||
import { Form, Header, Icon, Segment, Popup } from "semantic-ui-react";
 | 
			
		||||
import { Form, Header, Icon, Popup, Segment } from "semantic-ui-react";
 | 
			
		||||
 | 
			
		||||
import { DurationView, SectionChooser } from "@client/components";
 | 
			
		||||
import { UiStore } from "@client/state";
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
import { observer } from "mobx-react";
 | 
			
		||||
import * as React from "react";
 | 
			
		||||
import { Item, Button } from "semantic-ui-react";
 | 
			
		||||
import { Button, Item } from "semantic-ui-react";
 | 
			
		||||
 | 
			
		||||
import { DeviceView } from "@client/components";
 | 
			
		||||
import { AppState, injectState } from "@client/state";
 | 
			
		||||
@ -12,7 +12,7 @@ class DevicesPage extends React.Component<{ appState: AppState }> {
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const { appState } = this.props;
 | 
			
		||||
    const { userData } = appState.userStore;
 | 
			
		||||
    const userData = appState.userStore.getUserData();
 | 
			
		||||
    let deviceNodes: React.ReactNode;
 | 
			
		||||
    if (!userData) {
 | 
			
		||||
      deviceNodes = <span>Not logged in</span>;
 | 
			
		||||
@ -25,7 +25,7 @@ class DevicesPage extends React.Component<{ appState: AppState }> {
 | 
			
		||||
    }
 | 
			
		||||
    return (
 | 
			
		||||
      <React.Fragment>
 | 
			
		||||
        <h1 className="devices-header">Devices <Button icon="refresh" onClick={this.refreshDevices}></Button></h1>
 | 
			
		||||
        <h1 className="devices-header">Devices <Button icon="refresh" onClick={this.refreshDevices} /></h1>
 | 
			
		||||
        <Item.Group>{deviceNodes}</Item.Group>
 | 
			
		||||
      </React.Fragment>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ import { assign } from "lodash";
 | 
			
		||||
import { observer } from "mobx-react";
 | 
			
		||||
import * as qs from "query-string";
 | 
			
		||||
import * as React from "react";
 | 
			
		||||
import { RouteComponentProps } from "react-router";
 | 
			
		||||
import { RouteComponentProps, withRouter } from "react-router";
 | 
			
		||||
import {
 | 
			
		||||
  Button,
 | 
			
		||||
  CheckboxProps,
 | 
			
		||||
@ -252,8 +252,8 @@ class ProgramPage extends React.Component<ProgramPageProps> {
 | 
			
		||||
 | 
			
		||||
  @action.bound
 | 
			
		||||
  private close() {
 | 
			
		||||
    const { deviceId } = this.props.match.params;
 | 
			
		||||
    this.props.history.push({ pathname: route.device(deviceId), search: "" });
 | 
			
		||||
    // this.props.history.goBack();
 | 
			
		||||
    this.props.appState.history.goBack();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @action.bound
 | 
			
		||||
@ -271,5 +271,5 @@ class ProgramPage extends React.Component<ProgramPageProps> {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const DecoratedProgramPage = injectState(observer(ProgramPage));
 | 
			
		||||
const DecoratedProgramPage = injectState(withRouter(observer(ProgramPage)));
 | 
			
		||||
export default DecoratedProgramPage;
 | 
			
		||||
 | 
			
		||||
@ -191,11 +191,16 @@ export class WebSocketRpcClient extends s.SprinklersRPC {
 | 
			
		||||
    const id = this.nextRequestId++;
 | 
			
		||||
    return new Promise<ws.IServerResponseTypes[Method]>((resolve, reject) => {
 | 
			
		||||
      let timeoutHandle: number;
 | 
			
		||||
      this.responseCallbacks[id] = response => {
 | 
			
		||||
      this.responseCallbacks[id] = (response: ws.ServerResponse) => {
 | 
			
		||||
        clearTimeout(timeoutHandle);
 | 
			
		||||
        delete this.responseCallbacks[id];
 | 
			
		||||
        if (response.result === "success") {
 | 
			
		||||
          resolve(response.data);
 | 
			
		||||
          if (response.method === method) {
 | 
			
		||||
            resolve(response.data as ws.IServerResponseTypes[Method]);
 | 
			
		||||
          } else {
 | 
			
		||||
            reject(new s.RpcError("Response method does not match request method", ErrorCode.Internal,
 | 
			
		||||
              { requestMethod: method, responseMethod: response.method }));
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          const { error } = response;
 | 
			
		||||
          reject(new s.RpcError(error.message, error.code, error.data));
 | 
			
		||||
 | 
			
		||||
@ -47,7 +47,7 @@ export default class AppState extends TypedEventEmitter<AppEvents> {
 | 
			
		||||
 | 
			
		||||
  async start() {
 | 
			
		||||
    configure({
 | 
			
		||||
      enforceActions: true
 | 
			
		||||
      enforceActions: "observed"
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    syncHistoryWithStore(this.history, this.routerStore);
 | 
			
		||||
 | 
			
		||||
@ -1,20 +1,24 @@
 | 
			
		||||
import { ISprinklersDevice, IUser } from "@common/httpApi";
 | 
			
		||||
import { action, observable } from "mobx";
 | 
			
		||||
import { action, IObservableValue, observable } from "mobx";
 | 
			
		||||
 | 
			
		||||
export class UserStore {
 | 
			
		||||
  @observable
 | 
			
		||||
  userData: IUser | null = null;
 | 
			
		||||
  userData: IObservableValue<IUser | null> = observable.box(null);
 | 
			
		||||
 | 
			
		||||
  @action.bound
 | 
			
		||||
  receiveUserData(userData: IUser) {
 | 
			
		||||
    this.userData = userData;
 | 
			
		||||
    this.userData.set(userData);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getUserData(): IUser | null {
 | 
			
		||||
    return this.userData.get();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  findDevice(id: number): ISprinklersDevice | null {
 | 
			
		||||
    const userData = this.userData.get();
 | 
			
		||||
    return (
 | 
			
		||||
      (this.userData &&
 | 
			
		||||
        this.userData.devices &&
 | 
			
		||||
        this.userData.devices.find(dev => dev.id === id)) ||
 | 
			
		||||
      (userData &&
 | 
			
		||||
        userData.devices &&
 | 
			
		||||
        userData.devices.find(dev => dev.id === id)) ||
 | 
			
		||||
      null
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -31,12 +31,6 @@ export function ConsumeState({ children }: ConsumeStateProps) {
 | 
			
		||||
  return <StateContext.Consumer>{consumeState}</StateContext.Consumer>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Diff<
 | 
			
		||||
  T extends string | number | symbol,
 | 
			
		||||
  U extends string | number | symbol
 | 
			
		||||
> = ({ [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 { appState: AppState }>(
 | 
			
		||||
  Component: React.ComponentType<P>
 | 
			
		||||
): React.ComponentClass<Omit<P, "appState">> {
 | 
			
		||||
@ -48,7 +42,9 @@ export function injectState<P extends { appState: AppState }>(
 | 
			
		||||
            "Component with injectState must be mounted inside ProvideState"
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
        return <Component {...this.props} appState={state} />;
 | 
			
		||||
        // tslint:disable-next-line:no-object-literal-type-assertion
 | 
			
		||||
        const allProps: Readonly<P> = {...this.props, appState: state} as Readonly<P>;
 | 
			
		||||
        return <Component {...allProps} />;
 | 
			
		||||
      };
 | 
			
		||||
      return <StateContext.Consumer>{consumeState}</StateContext.Consumer>;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -81,3 +81,10 @@ $connected-color: #13d213;
 | 
			
		||||
.ui.modal.programEditor > .header > .header.item .inline.fields {
 | 
			
		||||
  margin-bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.runSectionForm-runButton  {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
  &, .ui.disabled.button {
 | 
			
		||||
    pointer-events: auto !important;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ $durationInput-labelWidth: 2.5em;
 | 
			
		||||
 | 
			
		||||
.field .durationInputs {
 | 
			
		||||
  display: flex; // max-width: 100%;
 | 
			
		||||
  justify-content: start;
 | 
			
		||||
  justify-content: flex-start;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
  margin: -$durationInput-spacing / 2;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,12 +8,16 @@
 | 
			
		||||
.programSequence-item {
 | 
			
		||||
  list-style-type: none;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  margin-bottom: 0.5em;
 | 
			
		||||
  &.dragging {
 | 
			
		||||
    z-index: 1010;
 | 
			
		||||
  }
 | 
			
		||||
  .fields {
 | 
			
		||||
    margin: 0em 0em 1em !important;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    margin: 0em !important;
 | 
			
		||||
    padding: 0em !important;
 | 
			
		||||
  }
 | 
			
		||||
  .ui.icon.button {
 | 
			
		||||
    height: fit-content;
 | 
			
		||||
 | 
			
		||||
@ -133,7 +133,7 @@ export type IResponseHandler<
 | 
			
		||||
  ResponseTypes,
 | 
			
		||||
  ErrorType,
 | 
			
		||||
  Method extends keyof ResponseTypes = keyof ResponseTypes
 | 
			
		||||
> = (response: ResponseData<ResponseTypes, ErrorType, Method>) => void;
 | 
			
		||||
> = (response: Response<ResponseTypes, ErrorType, Method>) => void;
 | 
			
		||||
 | 
			
		||||
export interface ResponseHandlers<
 | 
			
		||||
  ResponseTypes = DefaultResponseTypes,
 | 
			
		||||
 | 
			
		||||
@ -218,7 +218,7 @@ class MqttSprinklersDevice extends s.SprinklersDevice {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  doUnsubscribe() {
 | 
			
		||||
    this.apiClient.client.unsubscribe(this.subscriptions, err => {
 | 
			
		||||
    this.apiClient.client.unsubscribe(this.subscriptions, (err: Error | undefined) => {
 | 
			
		||||
      if (err) {
 | 
			
		||||
        log.error({ err, id: this.id }, "error unsubscribing to device");
 | 
			
		||||
      } else {
 | 
			
		||||
 | 
			
		||||
@ -36,8 +36,8 @@ export const updateProgram: ModelSchema<
 | 
			
		||||
    deserializer: (json, done) => {
 | 
			
		||||
      done(null, json);
 | 
			
		||||
    },
 | 
			
		||||
    beforeDeserialize: () => {},
 | 
			
		||||
    afterDeserialize: () => {},
 | 
			
		||||
    beforeDeserialize: undefined as any, 
 | 
			
		||||
    afterDeserialize: undefined as any,
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -47,7 +47,7 @@ export class Database {
 | 
			
		||||
    const users: User[] = [];
 | 
			
		||||
    for (let i = 0; i < NUM; i++) {
 | 
			
		||||
      const username = "alex" + i;
 | 
			
		||||
      let user = await this.users.findByUsername(username);
 | 
			
		||||
      let user = await this.users.findByUsername(username, { devices: true });
 | 
			
		||||
      if (!user) {
 | 
			
		||||
        user = await this.users.create({
 | 
			
		||||
          name: "Alex Mikhalev" + i,
 | 
			
		||||
@ -74,7 +74,10 @@ export class Database {
 | 
			
		||||
      for (let j = 0; j < 5; j++) {
 | 
			
		||||
        const userIdx = (i + j * 10) % NUM;
 | 
			
		||||
        const user = users[userIdx];
 | 
			
		||||
        user.devices = (user.devices || []).concat([device]);
 | 
			
		||||
        if (!user.devices) {
 | 
			
		||||
          user.devices = [];
 | 
			
		||||
        }
 | 
			
		||||
        user.devices.push(device);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    await this.sprinklersDevices.save(devices);
 | 
			
		||||
 | 
			
		||||
@ -70,7 +70,7 @@ export default class DeviceCommand extends ManageCommand {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getFindConditions(flags: DeviceFlags, action: Action): FindConditions<SprinklersDevice> {
 | 
			
		||||
    let whereClause: FindConditions<SprinklersDevice> = {};
 | 
			
		||||
    const whereClause: FindConditions<SprinklersDevice> = {};
 | 
			
		||||
    if (flags.id) {
 | 
			
		||||
      whereClause.id = flags.id;
 | 
			
		||||
    }
 | 
			
		||||
@ -125,7 +125,7 @@ export default class DeviceCommand extends ManageCommand {
 | 
			
		||||
        query = query.where("user.username = :username", { username: flags.username });
 | 
			
		||||
      }
 | 
			
		||||
      const devices = await query.getMany();
 | 
			
		||||
      if (devices.length == 0) {
 | 
			
		||||
      if (devices.length === 0) {
 | 
			
		||||
        this.log("No sprinklers devices found");
 | 
			
		||||
        return 1;
 | 
			
		||||
      }
 | 
			
		||||
@ -146,7 +146,7 @@ export default class DeviceCommand extends ManageCommand {
 | 
			
		||||
      if (!user) {
 | 
			
		||||
        return this.error(`Could not find user with username '${flags.username}'`);
 | 
			
		||||
      }
 | 
			
		||||
      let query = this.database.sprinklersDevices.createQueryBuilder()
 | 
			
		||||
      const query = this.database.sprinklersDevices.createQueryBuilder()
 | 
			
		||||
        .relation("users")
 | 
			
		||||
        .of(device);
 | 
			
		||||
      if (flags["add-user"]) {
 | 
			
		||||
 | 
			
		||||
@ -108,6 +108,12 @@ export function devices(state: ServerState) {
 | 
			
		||||
    async (req, res) => {
 | 
			
		||||
      const token: DeviceToken = req.token! as any;
 | 
			
		||||
      const deviceId = token.aud;
 | 
			
		||||
      const devs = await state.database.sprinklersDevices.count({
 | 
			
		||||
        deviceId
 | 
			
		||||
      });
 | 
			
		||||
      if (devs === 0) {
 | 
			
		||||
        throw new ApiError("deviceId not found", ErrorCode.NotFound);
 | 
			
		||||
      }
 | 
			
		||||
      const clientId  = `device-${deviceId}`;
 | 
			
		||||
      res.send({
 | 
			
		||||
        mqttUrl: state.mqttUrl,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user