Browse Source

Use mobx actions on client side

update-deps
Alex Mikhalev 7 years ago
parent
commit
60cabb9e57
  1. 12
      client/components/DeviceView.tsx
  2. 9
      client/components/ProgramSequenceView.tsx
  3. 12
      client/components/ProgramTable.tsx
  4. 13
      client/components/ScheduleView/index.tsx
  5. 17
      client/pages/LoginPage.tsx
  6. 35
      client/pages/ProgramPage.tsx
  7. 78
      client/sprinklersRpc/WebSocketRpcClient.ts
  8. 6
      client/state/AppState.ts
  9. 17
      client/state/HttpApi.ts
  10. 6
      client/state/TokenStore.ts
  11. 15
      client/state/UiStore.ts
  12. 15
      client/state/UserStore.ts
  13. 3
      common/logger.ts

12
client/components/DeviceView.tsx

@ -8,6 +8,7 @@ import { DeviceImage } from "@client/components";
import * as p from "@client/pages"; import * as p from "@client/pages";
import * as route from "@client/routePaths"; import * as route from "@client/routePaths";
import { AppState, injectState } from "@client/state"; import { AppState, injectState } from "@client/state";
import { ISprinklersDevice } from "@common/httpApi";
import { ConnectionState as ConState, SprinklersDevice } from "@common/sprinklersRpc"; import { ConnectionState as ConState, SprinklersDevice } from "@common/sprinklersRpc";
import { Route, RouteComponentProps, withRouter } from "react-router"; import { Route, RouteComponentProps, withRouter } from "react-router";
import { ProgramTable, RunSectionForm, SectionRunnerView, SectionTable } from "."; import { ProgramTable, RunSectionForm, SectionRunnerView, SectionTable } from ".";
@ -52,7 +53,7 @@ interface DeviceViewProps {
} }
class DeviceView extends React.Component<DeviceViewProps & RouteComponentProps<any>> { class DeviceView extends React.Component<DeviceViewProps & RouteComponentProps<any>> {
renderBody(device: SprinklersDevice) { renderBody(iDevice: ISprinklersDevice, device: SprinklersDevice) {
const { inList, appState: { uiStore, routerStore } } = this.props; const { inList, appState: { uiStore, routerStore } } = this.props;
const { connectionState, sectionRunner, sections } = device; const { connectionState, sectionRunner, sections } = device;
if (!connectionState.isAvailable || inList) { if (!connectionState.isAvailable || inList) {
@ -71,7 +72,7 @@ class DeviceView extends React.Component<DeviceViewProps & RouteComponentProps<a
<RunSectionForm device={device} uiStore={uiStore} /> <RunSectionForm device={device} uiStore={uiStore} />
</Grid.Column> </Grid.Column>
</Grid> </Grid>
<ProgramTable device={device} routerStore={routerStore} /> <ProgramTable iDevice={iDevice} device={device} routerStore={routerStore} />
<Route path={route.program(":deviceId", ":programId")} component={p.ProgramPage} /> <Route path={route.program(":deviceId", ":programId")} component={p.ProgramPage} />
</React.Fragment> </React.Fragment>
); );
@ -79,10 +80,7 @@ class DeviceView extends React.Component<DeviceViewProps & RouteComponentProps<a
render() { render() {
const { deviceId, inList, appState: { sprinklersRpc, userStore } } = this.props; const { deviceId, inList, appState: { sprinklersRpc, userStore } } = this.props;
const { userData } = userStore; const iDevice = userStore.findDevice(deviceId);
const iDevice = userData &&
userData.devices &&
userData.devices.find((dev) => dev.id === deviceId);
let itemContent: React.ReactNode; let itemContent: React.ReactNode;
if (!iDevice || !iDevice.deviceId) { if (!iDevice || !iDevice.deviceId) {
// TODO: better and link back to devices list // TODO: better and link back to devices list
@ -107,7 +105,7 @@ class DeviceView extends React.Component<DeviceViewProps & RouteComponentProps<a
<Item.Meta> <Item.Meta>
Raspberry Pi Grinklers Device Raspberry Pi Grinklers Device
</Item.Meta> </Item.Meta>
{this.renderBody(device)} {this.renderBody(iDevice, device)}
</Item.Content> </Item.Content>
</React.Fragment> </React.Fragment>
); );

9
client/components/ProgramSequenceView.tsx

@ -9,6 +9,7 @@ import { Duration } from "@common/Duration";
import { ProgramItem, Section } from "@common/sprinklersRpc"; import { ProgramItem, Section } from "@common/sprinklersRpc";
import "@client/styles/ProgramSequenceView"; import "@client/styles/ProgramSequenceView";
import { action } from "mobx";
type ItemChangeHandler = (index: number, newItem: ProgramItem) => void; type ItemChangeHandler = (index: number, newItem: ProgramItem) => void;
type ItemRemoveHandler = (index: number) => void; type ItemRemoveHandler = (index: number) => void;
@ -147,15 +148,18 @@ class ProgramSequenceView extends React.Component<{
); );
} }
@action.bound
private changeItem: ItemChangeHandler = (index, newItem) => { private changeItem: ItemChangeHandler = (index, newItem) => {
this.props.sequence[index] = newItem; this.props.sequence[index] = newItem;
} }
@action.bound
private removeItem: ItemRemoveHandler = (index) => { private removeItem: ItemRemoveHandler = (index) => {
this.props.sequence.splice(index, 1); this.props.sequence.splice(index, 1);
} }
private addItem = () => { @action.bound
private addItem() {
let sectionId = 0; let sectionId = 0;
for (const section of this.props.sections) { for (const section of this.props.sections) {
const sectionNotIncluded = this.props.sequence const sectionNotIncluded = this.props.sequence
@ -173,7 +177,8 @@ class ProgramSequenceView extends React.Component<{
this.props.sequence.push(item); this.props.sequence.push(item);
} }
private onSortEnd = ({oldIndex, newIndex}: SortEnd) => { @action.bound
private onSortEnd({oldIndex, newIndex}: SortEnd) {
const { sequence: array } = this.props; const { sequence: array } = this.props;
if (newIndex >= array.length) { if (newIndex >= array.length) {
return; return;

12
client/components/ProgramTable.tsx

@ -6,22 +6,25 @@ import { Button, ButtonProps, Form, Icon, Table } from "semantic-ui-react";
import { ProgramSequenceView, ScheduleView } from "@client/components"; import { ProgramSequenceView, ScheduleView } from "@client/components";
import * as route from "@client/routePaths"; import * as route from "@client/routePaths";
import { ISprinklersDevice } from "@common/httpApi";
import { Program, SprinklersDevice } from "@common/sprinklersRpc"; import { Program, SprinklersDevice } from "@common/sprinklersRpc";
@observer @observer
class ProgramRows extends React.Component<{ class ProgramRows extends React.Component<{
program: Program, device: SprinklersDevice, program: Program,
iDevice: ISprinklersDevice,
device: SprinklersDevice,
routerStore: RouterStore, routerStore: RouterStore,
expanded: boolean, toggleExpanded: (program: Program) => void, expanded: boolean, toggleExpanded: (program: Program) => void,
}> { }> {
render() { render() {
const { program, device, expanded } = this.props; const { program, iDevice, device, expanded } = this.props;
const { sections } = device; const { sections } = device;
const { name, running, enabled, schedule, sequence } = program; const { name, running, enabled, schedule, sequence } = program;
const buttonStyle: ButtonProps = { size: "small", compact: false }; const buttonStyle: ButtonProps = { size: "small", compact: false };
const detailUrl = route.program(device.id, program.id); const detailUrl = route.program(iDevice.id, program.id);
const stopStartButton = ( const stopStartButton = (
<Button onClick={this.cancelOrRun} {...buttonStyle} positive={!running} negative={running}> <Button onClick={this.cancelOrRun} {...buttonStyle} positive={!running} negative={running}>
@ -81,7 +84,7 @@ class ProgramRows extends React.Component<{
@observer @observer
export default class ProgramTable extends React.Component<{ export default class ProgramTable extends React.Component<{
device: SprinklersDevice, routerStore: RouterStore, iDevice: ISprinklersDevice, device: SprinklersDevice, routerStore: RouterStore,
}, { }, {
expandedPrograms: Program[], expandedPrograms: Program[],
}> { }> {
@ -123,6 +126,7 @@ export default class ProgramTable extends React.Component<{
return ( return (
<ProgramRows <ProgramRows
program={program} program={program}
iDevice={this.props.iDevice}
device={this.props.device} device={this.props.device}
routerStore={this.props.routerStore} routerStore={this.props.routerStore}
expanded={expanded} expanded={expanded}

13
client/components/ScheduleView/index.tsx

@ -8,6 +8,7 @@ import ScheduleTimes from "./ScheduleTimes";
import WeekdaysView from "./WeekdaysView"; import WeekdaysView from "./WeekdaysView";
import "@client/styles/ScheduleView"; import "@client/styles/ScheduleView";
import { action } from "mobx";
export interface ScheduleViewProps { export interface ScheduleViewProps {
label?: string | React.ReactNode | undefined; label?: string | React.ReactNode | undefined;
@ -39,19 +40,23 @@ export default class ScheduleView extends React.Component<ScheduleViewProps> {
); );
} }
private updateTimes = (newTimes: TimeOfDay[]) => { @action.bound
private updateTimes(newTimes: TimeOfDay[]) {
this.props.schedule.times = newTimes; this.props.schedule.times = newTimes;
} }
private updateWeekdays = (newWeekdays: Weekday[]) => { @action.bound
private updateWeekdays(newWeekdays: Weekday[]) {
this.props.schedule.weekdays = newWeekdays; this.props.schedule.weekdays = newWeekdays;
} }
private updateFromDate = (newFromDate: DateOfYear | null) => { @action.bound
private updateFromDate(newFromDate: DateOfYear | null) {
this.props.schedule.from = newFromDate; this.props.schedule.from = newFromDate;
} }
private updateToDate = (newToDate: DateOfYear | null) => { @action.bound
private updateToDate(newToDate: DateOfYear | null) {
this.props.schedule.to = newToDate; this.props.schedule.to = newToDate;
} }
} }

17
client/pages/LoginPage.tsx

@ -1,4 +1,4 @@
import { computed, observable } from "mobx"; import { action, computed, observable } from "mobx";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import * as React from "react"; import * as React from "react";
import { Container, Dimmer, Form, Header, InputOnChangeData, Loader, Message, Segment } from "semantic-ui-react"; import { Container, Dimmer, Form, Header, InputOnChangeData, Loader, Message, Segment } from "semantic-ui-react";
@ -19,28 +19,31 @@ class LoginPageState {
return this.username.length > 0 && this.password.length > 0; return this.username.length > 0 && this.password.length > 0;
} }
onUsernameChange = (e: any, data: InputOnChangeData) => { @action.bound
onUsernameChange(e: any, data: InputOnChangeData) {
this.username = data.value; this.username = data.value;
} }
onPasswordChange = (e: any, data: InputOnChangeData) => { @action.bound
onPasswordChange(e: any, data: InputOnChangeData) {
this.password = data.value; this.password = data.value;
} }
@action.bound
login(appState: AppState) { login(appState: AppState) {
this.loading = true; this.loading = true;
this.error = null; this.error = null;
appState.httpApi.grantPassword(this.username, this.password) appState.httpApi.grantPassword(this.username, this.password)
.then(() => { .then(action("loginSuccess", () => {
this.loading = false; this.loading = false;
log.info("logged in"); log.info("logged in");
appState.history.push("/"); appState.history.push("/");
}) }))
.catch((err) => { .catch(action("loginError", (err: any) => {
this.loading = false; this.loading = false;
this.error = err.message; this.error = err.message;
log.error({ err }, "login error"); log.error({ err }, "login error");
}); }));
} }
} }

35
client/pages/ProgramPage.tsx

@ -8,8 +8,10 @@ import { Button, CheckboxProps, Form, Icon, Input, InputOnChangeData, Menu, Moda
import { ProgramSequenceView, ScheduleView } from "@client/components"; import { ProgramSequenceView, ScheduleView } from "@client/components";
import * as route from "@client/routePaths"; import * as route from "@client/routePaths";
import { AppState, injectState } from "@client/state"; import { AppState, injectState } from "@client/state";
import { ISprinklersDevice } from "@common/httpApi";
import log from "@common/logger"; import log from "@common/logger";
import { Program, SprinklersDevice } from "@common/sprinklersRpc"; import { Program, SprinklersDevice } from "@common/sprinklersRpc";
import { action } from "mobx";
interface ProgramPageProps extends RouteComponentProps<{ deviceId: string, programId: string }> { interface ProgramPageProps extends RouteComponentProps<{ deviceId: string, programId: string }> {
appState: AppState; appState: AppState;
@ -21,6 +23,7 @@ class ProgramPage extends React.Component<ProgramPageProps> {
return qs.parse(this.props.location.search).editing != null; return qs.parse(this.props.location.search).editing != null;
} }
iDevice!: ISprinklersDevice;
device!: SprinklersDevice; device!: SprinklersDevice;
program!: Program; program!: Program;
programView: Program | null = null; programView: Program | null = null;
@ -95,11 +98,16 @@ class ProgramPage extends React.Component<ProgramPageProps> {
} }
render() { render() {
const { deviceId, programId: pid } = this.props.match.params; const { deviceId: did, programId: pid } = this.props.match.params;
const { userStore, sprinklersRpc } = this.props.appState;
const deviceId = Number(did);
const programId = Number(pid); const programId = Number(pid);
// tslint:disable-next-line:prefer-conditional-expression // tslint:disable-next-line:prefer-conditional-expression
if (!this.device || this.device.id !== deviceId) { if (!this.iDevice || this.iDevice.id !== deviceId) {
this.device = this.props.appState.sprinklersRpc.getDevice(deviceId); this.iDevice = userStore.findDevice(deviceId)!;
}
if (this.iDevice && this.iDevice.deviceId && (!this.device || this.device.id !== this.iDevice.deviceId)) {
this.device = sprinklersRpc.getDevice(this.iDevice.deviceId);
} }
// tslint:disable-next-line:prefer-conditional-expression // tslint:disable-next-line:prefer-conditional-expression
if (!this.program || this.program.id !== programId) { if (!this.program || this.program.id !== programId) {
@ -158,18 +166,21 @@ class ProgramPage extends React.Component<ProgramPageProps> {
); );
} }
private cancelOrRun = () => { @action.bound
private cancelOrRun() {
if (!this.program) { if (!this.program) {
return; return;
} }
this.program.running ? this.program.cancel() : this.program.run(); this.program.running ? this.program.cancel() : this.program.run();
} }
private startEditing = () => { @action.bound
private startEditing() {
this.props.history.push({ search: qs.stringify({ editing: true }) }); this.props.history.push({ search: qs.stringify({ editing: true }) });
} }
private save = () => { @action.bound
private save() {
if (!this.programView || !this.program) { if (!this.programView || !this.program) {
return; return;
} }
@ -183,22 +194,26 @@ class ProgramPage extends React.Component<ProgramPageProps> {
this.stopEditing(); this.stopEditing();
} }
private stopEditing = () => { @action.bound
private stopEditing() {
this.props.history.push({ search: "" }); this.props.history.push({ search: "" });
} }
private close = () => { @action.bound
private close() {
const { deviceId } = this.props.match.params; const { deviceId } = this.props.match.params;
this.props.history.push({ pathname: route.device(deviceId), search: "" }); this.props.history.push({ pathname: route.device(deviceId), search: "" });
} }
private onNameChange = (e: any, p: InputOnChangeData) => { @action.bound
private onNameChange(e: any, p: InputOnChangeData) {
if (this.programView) { if (this.programView) {
this.programView.name = p.value; this.programView.name = p.value;
} }
} }
private onEnabledChange = (e: any, p: CheckboxProps) => { @action.bound
private onEnabledChange(e: any, p: CheckboxProps) {
if (this.programView) { if (this.programView) {
this.programView.enabled = p.checked!; this.programView.enabled = p.checked!;
} }

78
client/sprinklersRpc/WebSocketRpcClient.ts

@ -1,4 +1,4 @@
import { action, autorun, observable, when } from "mobx"; import { action, autorun, observable, runInAction, when } from "mobx";
import { update } from "serializr"; import { update } from "serializr";
import { TokenStore } from "@client/state/TokenStore"; import { TokenStore } from "@client/state/TokenStore";
@ -35,10 +35,7 @@ export class WSSprinklersDevice extends s.SprinklersDevice {
this.api = api; this.api = api;
this._id = id; this._id = id;
autorun(() => { autorun(this.updateConnectionState);
this.connectionState.clientToServer = this.api.connectionState.clientToServer;
this.connectionState.serverToBroker = this.api.connectionState.serverToBroker;
});
this.waitSubscribe(); this.waitSubscribe();
} }
@ -46,20 +43,31 @@ export class WSSprinklersDevice extends s.SprinklersDevice {
return this._id; return this._id;
} }
private updateConnectionState = () => {
const { clientToServer, serverToBroker } = this.api.connectionState;
runInAction("updateConnectionState", () => {
Object.assign(this.connectionState, { clientToServer, serverToBroker });
});
}
async subscribe() { async subscribe() {
const subscribeRequest: ws.IDeviceSubscribeRequest = { const subscribeRequest: ws.IDeviceSubscribeRequest = {
deviceId: this.id, deviceId: this.id,
}; };
try { try {
await this.api.makeRequest("deviceSubscribe", subscribeRequest); await this.api.makeRequest("deviceSubscribe", subscribeRequest);
this.connectionState.brokerToDevice = true; runInAction("deviceSubscribeSuccess", () => {
this.connectionState.brokerToDevice = true;
});
} catch (err) { } catch (err) {
this.connectionState.brokerToDevice = false; runInAction("deviceSubscribeError", () => {
if ((err as ws.IError).code === ErrorCode.NoPermission) { this.connectionState.brokerToDevice = false;
this.connectionState.hasPermission = false; if ((err as ws.IError).code === ErrorCode.NoPermission) {
} else { this.connectionState.hasPermission = false;
log.error({ err }); } else {
} log.error({ err });
}
});
} }
} }
@ -143,7 +151,7 @@ export class WebSocketRpcClient implements s.SprinklersRPC {
const res = await this.authenticate(this.tokenStore.accessToken.token!); const res = await this.authenticate(this.tokenStore.accessToken.token!);
this.authenticated = res.authenticated; this.authenticated = res.authenticated;
logger.info({ user: res.user }, "authenticated websocket connection"); logger.info({ user: res.user }, "authenticated websocket connection");
this.userStore.userData = res.user; this.userStore.receiveUserData(res.user);
} catch (err) { } catch (err) {
logger.error({ err }, "error authenticating websocket connection"); logger.error({ err }, "error authenticating websocket connection");
// TODO message? // TODO message?
@ -288,7 +296,7 @@ export class WebSocketRpcClient implements s.SprinklersRPC {
try { try {
rpc.handleNotification(this.notificationHandlers, data); rpc.handleNotification(this.notificationHandlers, data);
} catch (err) { } catch (err) {
logger.error({ err }, "error handling server notification"); logger.error(err, "error handling server notification");
} }
} }
@ -300,19 +308,31 @@ export class WebSocketRpcClient implements s.SprinklersRPC {
} }
} }
private notificationHandlers: ws.ServerNotificationHandlers = { private notificationHandlers = new WSClientNotificationHandlers(this);
brokerConnectionUpdate: (data: ws.IBrokerConnectionUpdate) => {
this.connectionState.serverToBroker = data.brokerConnected;
},
deviceUpdate: (data: ws.IDeviceUpdate) => {
const device = this.devices.get(data.deviceId);
if (!device) {
return log.warn({ data }, "invalid deviceUpdate received");
}
update(schema.sprinklersDevice, device, data.data);
},
error: (data: ws.IError) => {
log.warn({ err: data }, "server error");
},
};
} }
class WSClientNotificationHandlers implements ws.ServerNotificationHandlers {
client: WebSocketRpcClient;
constructor(client: WebSocketRpcClient) {
this.client = client;
}
@action.bound
brokerConnectionUpdate(data: ws.IBrokerConnectionUpdate) {
this.client.connectionState.serverToBroker = data.brokerConnected;
}
@action.bound
deviceUpdate(data: ws.IDeviceUpdate) {
const device = this.client.devices.get(data.deviceId);
if (!device) {
return log.warn({ data }, "invalid deviceUpdate received");
}
update(schema.sprinklersDevice, device, data.data);
}
error(data: ws.IError) {
log.warn({ err: data }, "server error");
}
};

6
client/state/AppState.ts

@ -1,5 +1,5 @@
import { createBrowserHistory, History } from "history"; import { createBrowserHistory, History } from "history";
import { computed } from "mobx"; import { computed, configure } from "mobx";
import { RouterStore, syncHistoryWithStore } from "mobx-react-router"; import { RouterStore, syncHistoryWithStore } from "mobx-react-router";
import { WebSocketRpcClient } from "@client/sprinklersRpc/WebSocketRpcClient"; import { WebSocketRpcClient } from "@client/sprinklersRpc/WebSocketRpcClient";
@ -24,6 +24,10 @@ export default class AppState {
} }
async start() { async start() {
configure({
enforceActions: true,
});
syncHistoryWithStore(this.history, this.routerStore); syncHistoryWithStore(this.history, this.routerStore);
this.tokenStore.loadLocalStorage(); this.tokenStore.loadLocalStorage();

17
client/state/HttpApi.ts

@ -3,6 +3,7 @@ import ApiError from "@common/ApiError";
import { ErrorCode } from "@common/ErrorCode"; import { ErrorCode } from "@common/ErrorCode";
import { TokenGrantPasswordRequest, TokenGrantRefreshRequest, TokenGrantResponse } from "@common/httpApi"; import { TokenGrantPasswordRequest, TokenGrantRefreshRequest, TokenGrantResponse } from "@common/httpApi";
import log from "@common/logger"; import log from "@common/logger";
import { runInAction } from "mobx";
export { ApiError }; export { ApiError };
@ -58,9 +59,11 @@ export default class HttpApi {
const response: TokenGrantResponse = await this.makeRequest("/token/grant", { const response: TokenGrantResponse = await this.makeRequest("/token/grant", {
method: "POST", method: "POST",
}, request); }, request);
this.tokenStore.accessToken.token = response.access_token; runInAction("grantPasswordSuccess", () => {
this.tokenStore.refreshToken.token = response.refresh_token; this.tokenStore.accessToken.token = response.access_token;
this.tokenStore.saveLocalStorage(); this.tokenStore.refreshToken.token = response.refresh_token;
this.tokenStore.saveLocalStorage();
});
const { accessToken } = this.tokenStore; const { accessToken } = this.tokenStore;
log.debug({ aud: accessToken.claims!.aud }, "got password grant tokens"); log.debug({ aud: accessToken.claims!.aud }, "got password grant tokens");
} }
@ -76,9 +79,11 @@ export default class HttpApi {
const response: TokenGrantResponse = await this.makeRequest("/token/grant", { const response: TokenGrantResponse = await this.makeRequest("/token/grant", {
method: "POST", method: "POST",
}, request); }, request);
this.tokenStore.accessToken.token = response.access_token; runInAction("grantRefreshSuccess", () => {
this.tokenStore.refreshToken.token = response.refresh_token; this.tokenStore.accessToken.token = response.access_token;
this.tokenStore.saveLocalStorage(); this.tokenStore.refreshToken.token = response.refresh_token;
this.tokenStore.saveLocalStorage();
});
const { accessToken } = this.tokenStore; const { accessToken } = this.tokenStore;
log.debug({ aud: accessToken.claims!.aud }, "got refresh grant tokens"); log.debug({ aud: accessToken.claims!.aud }, "got refresh grant tokens");
} }

6
client/state/TokenStore.ts

@ -1,4 +1,4 @@
import { observable } from "mobx"; import { action, observable } from "mobx";
import { Token } from "@client/state/Token"; import { Token } from "@client/state/Token";
import { AccessToken, RefreshToken } from "@common/TokenClaims"; import { AccessToken, RefreshToken } from "@common/TokenClaims";
@ -9,16 +9,19 @@ export class TokenStore {
@observable accessToken: Token<AccessToken> = new Token(); @observable accessToken: Token<AccessToken> = new Token();
@observable refreshToken: Token<RefreshToken> = new Token(); @observable refreshToken: Token<RefreshToken> = new Token();
@action
clear() { clear() {
this.accessToken.token = null; this.accessToken.token = null;
this.refreshToken.token = null; this.refreshToken.token = null;
this.saveLocalStorage(); this.saveLocalStorage();
} }
@action
saveLocalStorage() { saveLocalStorage() {
window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(this.toJSON())); window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(this.toJSON()));
} }
@action
loadLocalStorage() { loadLocalStorage() {
const data = window.localStorage.getItem(LOCAL_STORAGE_KEY); const data = window.localStorage.getItem(LOCAL_STORAGE_KEY);
if (data) { if (data) {
@ -31,6 +34,7 @@ export class TokenStore {
return { accessToken: this.accessToken.toJSON(), refreshToken: this.refreshToken.toJSON() }; return { accessToken: this.accessToken.toJSON(), refreshToken: this.refreshToken.toJSON() };
} }
@action
updateFromJson(json: any) { updateFromJson(json: any) {
this.accessToken.token = json.accessToken; this.accessToken.token = json.accessToken;
this.refreshToken.token = json.refreshToken; this.refreshToken.token = json.refreshToken;

15
client/state/UiStore.ts

@ -1,4 +1,4 @@
import { IObservableArray, observable } from "mobx"; import { action, IObservableArray, observable } from "mobx";
import { MessageProps } from "semantic-ui-react"; import { MessageProps } from "semantic-ui-react";
import { getRandomId } from "@common/utils"; import { getRandomId } from "@common/utils";
@ -14,7 +14,8 @@ export interface UiMessageProps extends MessageProps {
export class UiStore { export class UiStore {
messages: IObservableArray<UiMessage> = observable.array(); messages: IObservableArray<UiMessage> = observable.array();
addMessage(message: UiMessageProps) { @action
addMessage(message: UiMessageProps): UiMessage {
const { timeout, ...otherProps } = message; const { timeout, ...otherProps } = message;
const msg = observable({ const msg = observable({
...otherProps, ...otherProps,
@ -22,7 +23,15 @@ export class UiStore {
}); });
this.messages.push(msg); this.messages.push(msg);
if (timeout) { if (timeout) {
setTimeout(() => this.messages.remove(msg), timeout); setTimeout(() => {
this.removeMessage(msg);
}, timeout);
} }
return msg;
}
@action
removeMessage(message: UiMessage) {
return this.messages.remove(message);
} }
} }

15
client/state/UserStore.ts

@ -1,6 +1,17 @@
import { IUser } from "@common/httpApi"; import { ISprinklersDevice, IUser } from "@common/httpApi";
import { observable } from "mobx"; import { action, observable } from "mobx";
export class UserStore { export class UserStore {
@observable userData: IUser | null = null; @observable userData: IUser | null = null;
@action
receiveUserData(userData: IUser) {
this.userData = userData;
}
findDevice(id: number): ISprinklersDevice | null {
return this.userData &&
this.userData.devices &&
this.userData.devices.find((dev) => dev.id === id) || null;
}
} }

3
common/logger.ts

@ -107,7 +107,8 @@ function formatLevel(value: any): ColoredString {
} }
const logger: pino.Logger = pino({ const logger: pino.Logger = pino({
browser: { write }, serializers: pino.stdSerializers,
browser: { serialize: true, write },
level: "trace", level: "trace",
}); });

Loading…
Cancel
Save