Browse Source

Lots of improvements

develop
Alex Mikhalev 6 years ago
parent
commit
f328e5c2e2
  1. 6
      client/App.tsx
  2. 6
      client/components/DeviceView.tsx
  3. 2
      client/components/MessagesView.tsx
  4. 2
      client/components/RunSectionForm.tsx
  5. 6
      client/pages/DevicesPage.tsx
  6. 8
      client/pages/ProgramPage.tsx
  7. 9
      client/sprinklersRpc/WebSocketRpcClient.ts
  8. 2
      client/state/AppState.ts
  9. 18
      client/state/UserStore.ts
  10. 10
      client/state/reactContext.tsx
  11. 7
      client/styles/DeviceView.scss
  12. 2
      client/styles/DurationView.scss
  13. 6
      client/styles/ProgramSequenceView.scss
  14. 2
      common/jsonRpc/index.ts
  15. 2
      common/sprinklersRpc/mqtt/index.ts
  16. 4
      common/sprinklersRpc/schema/requests.ts
  17. 7
      server/Database.ts
  18. 6
      server/commands/device.ts
  19. 6
      server/express/api/devices.ts

6
client/App.tsx

@ -1,6 +1,6 @@ @@ -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() { @@ -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() { @@ -39,3 +39,5 @@ export default function App() {
</Switch>
);
}
export default withRouter(App);

6
client/components/DeviceView.tsx

@ -2,7 +2,7 @@ import * as classNames from "classnames"; @@ -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( @@ -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> { @@ -189,4 +189,4 @@ class DeviceView extends React.Component<DeviceViewProps> {
}
}
export default injectState(observer(DeviceView));
export default withRouter(injectState(observer(DeviceView)));

2
client/components/MessagesView.tsx

@ -30,7 +30,7 @@ class MessageView extends React.Component<{ @@ -30,7 +30,7 @@ class MessageView extends React.Component<{
if (message.onDismiss) {
message.onDismiss(event, data);
}
uiStore.messages.remove(message);
uiStore.removeMessage(message);
};
}

2
client/components/RunSectionForm.tsx

@ -1,6 +1,6 @@ @@ -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";

6
client/pages/DevicesPage.tsx

@ -1,6 +1,6 @@ @@ -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 }> { @@ -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 }> { @@ -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>
);

8
client/pages/ProgramPage.tsx

@ -2,7 +2,7 @@ import { assign } from "lodash"; @@ -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> { @@ -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> { @@ -271,5 +271,5 @@ class ProgramPage extends React.Component<ProgramPageProps> {
}
}
const DecoratedProgramPage = injectState(observer(ProgramPage));
const DecoratedProgramPage = injectState(withRouter(observer(ProgramPage)));
export default DecoratedProgramPage;

9
client/sprinklersRpc/WebSocketRpcClient.ts

@ -191,11 +191,16 @@ export class WebSocketRpcClient extends s.SprinklersRPC { @@ -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));

2
client/state/AppState.ts

@ -47,7 +47,7 @@ export default class AppState extends TypedEventEmitter<AppEvents> { @@ -47,7 +47,7 @@ export default class AppState extends TypedEventEmitter<AppEvents> {
async start() {
configure({
enforceActions: true
enforceActions: "observed"
});
syncHistoryWithStore(this.history, this.routerStore);

18
client/state/UserStore.ts

@ -1,20 +1,24 @@ @@ -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
);
}

10
client/state/reactContext.tsx

@ -31,12 +31,6 @@ export function ConsumeState({ children }: ConsumeStateProps) { @@ -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 }>( @@ -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>;
}

7
client/styles/DeviceView.scss

@ -81,3 +81,10 @@ $connected-color: #13d213; @@ -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;
}
}

2
client/styles/DurationView.scss

@ -4,7 +4,7 @@ $durationInput-labelWidth: 2.5em; @@ -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;

6
client/styles/ProgramSequenceView.scss

@ -8,12 +8,16 @@ @@ -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;

2
common/jsonRpc/index.ts

@ -133,7 +133,7 @@ export type IResponseHandler< @@ -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,

2
common/sprinklersRpc/mqtt/index.ts

@ -218,7 +218,7 @@ class MqttSprinklersDevice extends s.SprinklersDevice { @@ -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 {

4
common/sprinklersRpc/schema/requests.ts

@ -36,8 +36,8 @@ export const updateProgram: ModelSchema< @@ -36,8 +36,8 @@ export const updateProgram: ModelSchema<
deserializer: (json, done) => {
done(null, json);
},
beforeDeserialize: () => {},
afterDeserialize: () => {},
beforeDeserialize: undefined as any,
afterDeserialize: undefined as any,
}
});

7
server/Database.ts

@ -47,7 +47,7 @@ export class Database { @@ -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 { @@ -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);

6
server/commands/device.ts

@ -70,7 +70,7 @@ export default class DeviceCommand extends ManageCommand { @@ -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 { @@ -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 { @@ -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"]) {

6
server/express/api/devices.ts

@ -108,6 +108,12 @@ export function devices(state: ServerState) { @@ -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…
Cancel
Save