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