Use mobx actions on client side
This commit is contained in:
parent
e35f9bf0d9
commit
60cabb9e57
@ -8,6 +8,7 @@ import { DeviceImage } from "@client/components";
|
||||
import * as p from "@client/pages";
|
||||
import * as route from "@client/routePaths";
|
||||
import { AppState, injectState } from "@client/state";
|
||||
import { ISprinklersDevice } from "@common/httpApi";
|
||||
import { ConnectionState as ConState, SprinklersDevice } from "@common/sprinklersRpc";
|
||||
import { Route, RouteComponentProps, withRouter } from "react-router";
|
||||
import { ProgramTable, RunSectionForm, SectionRunnerView, SectionTable } from ".";
|
||||
@ -52,7 +53,7 @@ interface DeviceViewProps {
|
||||
}
|
||||
|
||||
class DeviceView extends React.Component<DeviceViewProps & RouteComponentProps<any>> {
|
||||
renderBody(device: SprinklersDevice) {
|
||||
renderBody(iDevice: ISprinklersDevice, device: SprinklersDevice) {
|
||||
const { inList, appState: { uiStore, routerStore } } = this.props;
|
||||
const { connectionState, sectionRunner, sections } = device;
|
||||
if (!connectionState.isAvailable || inList) {
|
||||
@ -71,7 +72,7 @@ class DeviceView extends React.Component<DeviceViewProps & RouteComponentProps<a
|
||||
<RunSectionForm device={device} uiStore={uiStore} />
|
||||
</Grid.Column>
|
||||
</Grid>
|
||||
<ProgramTable device={device} routerStore={routerStore} />
|
||||
<ProgramTable iDevice={iDevice} device={device} routerStore={routerStore} />
|
||||
<Route path={route.program(":deviceId", ":programId")} component={p.ProgramPage} />
|
||||
</React.Fragment>
|
||||
);
|
||||
@ -79,10 +80,7 @@ class DeviceView extends React.Component<DeviceViewProps & RouteComponentProps<a
|
||||
|
||||
render() {
|
||||
const { deviceId, inList, appState: { sprinklersRpc, userStore } } = this.props;
|
||||
const { userData } = userStore;
|
||||
const iDevice = userData &&
|
||||
userData.devices &&
|
||||
userData.devices.find((dev) => dev.id === deviceId);
|
||||
const iDevice = userStore.findDevice(deviceId);
|
||||
let itemContent: React.ReactNode;
|
||||
if (!iDevice || !iDevice.deviceId) {
|
||||
// TODO: better and link back to devices list
|
||||
@ -107,7 +105,7 @@ class DeviceView extends React.Component<DeviceViewProps & RouteComponentProps<a
|
||||
<Item.Meta>
|
||||
Raspberry Pi Grinklers Device
|
||||
</Item.Meta>
|
||||
{this.renderBody(device)}
|
||||
{this.renderBody(iDevice, device)}
|
||||
</Item.Content>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -9,6 +9,7 @@ import { Duration } from "@common/Duration";
|
||||
import { ProgramItem, Section } from "@common/sprinklersRpc";
|
||||
|
||||
import "@client/styles/ProgramSequenceView";
|
||||
import { action } from "mobx";
|
||||
|
||||
type ItemChangeHandler = (index: number, newItem: ProgramItem) => void;
|
||||
type ItemRemoveHandler = (index: number) => void;
|
||||
@ -147,15 +148,18 @@ class ProgramSequenceView extends React.Component<{
|
||||
);
|
||||
}
|
||||
|
||||
@action.bound
|
||||
private changeItem: ItemChangeHandler = (index, newItem) => {
|
||||
this.props.sequence[index] = newItem;
|
||||
}
|
||||
|
||||
@action.bound
|
||||
private removeItem: ItemRemoveHandler = (index) => {
|
||||
this.props.sequence.splice(index, 1);
|
||||
}
|
||||
|
||||
private addItem = () => {
|
||||
@action.bound
|
||||
private addItem() {
|
||||
let sectionId = 0;
|
||||
for (const section of this.props.sections) {
|
||||
const sectionNotIncluded = this.props.sequence
|
||||
@ -173,7 +177,8 @@ class ProgramSequenceView extends React.Component<{
|
||||
this.props.sequence.push(item);
|
||||
}
|
||||
|
||||
private onSortEnd = ({oldIndex, newIndex}: SortEnd) => {
|
||||
@action.bound
|
||||
private onSortEnd({oldIndex, newIndex}: SortEnd) {
|
||||
const { sequence: array } = this.props;
|
||||
if (newIndex >= array.length) {
|
||||
return;
|
||||
|
@ -6,22 +6,25 @@ import { Button, ButtonProps, Form, Icon, Table } from "semantic-ui-react";
|
||||
|
||||
import { ProgramSequenceView, ScheduleView } from "@client/components";
|
||||
import * as route from "@client/routePaths";
|
||||
import { ISprinklersDevice } from "@common/httpApi";
|
||||
import { Program, SprinklersDevice } from "@common/sprinklersRpc";
|
||||
|
||||
@observer
|
||||
class ProgramRows extends React.Component<{
|
||||
program: Program, device: SprinklersDevice,
|
||||
program: Program,
|
||||
iDevice: ISprinklersDevice,
|
||||
device: SprinklersDevice,
|
||||
routerStore: RouterStore,
|
||||
expanded: boolean, toggleExpanded: (program: Program) => void,
|
||||
}> {
|
||||
render() {
|
||||
const { program, device, expanded } = this.props;
|
||||
const { program, iDevice, device, expanded } = this.props;
|
||||
const { sections } = device;
|
||||
|
||||
const { name, running, enabled, schedule, sequence } = program;
|
||||
|
||||
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 = (
|
||||
<Button onClick={this.cancelOrRun} {...buttonStyle} positive={!running} negative={running}>
|
||||
@ -81,7 +84,7 @@ class ProgramRows extends React.Component<{
|
||||
|
||||
@observer
|
||||
export default class ProgramTable extends React.Component<{
|
||||
device: SprinklersDevice, routerStore: RouterStore,
|
||||
iDevice: ISprinklersDevice, device: SprinklersDevice, routerStore: RouterStore,
|
||||
}, {
|
||||
expandedPrograms: Program[],
|
||||
}> {
|
||||
@ -123,6 +126,7 @@ export default class ProgramTable extends React.Component<{
|
||||
return (
|
||||
<ProgramRows
|
||||
program={program}
|
||||
iDevice={this.props.iDevice}
|
||||
device={this.props.device}
|
||||
routerStore={this.props.routerStore}
|
||||
expanded={expanded}
|
||||
|
@ -8,6 +8,7 @@ import ScheduleTimes from "./ScheduleTimes";
|
||||
import WeekdaysView from "./WeekdaysView";
|
||||
|
||||
import "@client/styles/ScheduleView";
|
||||
import { action } from "mobx";
|
||||
|
||||
export interface ScheduleViewProps {
|
||||
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;
|
||||
}
|
||||
|
||||
private updateWeekdays = (newWeekdays: Weekday[]) => {
|
||||
@action.bound
|
||||
private updateWeekdays(newWeekdays: Weekday[]) {
|
||||
this.props.schedule.weekdays = newWeekdays;
|
||||
}
|
||||
|
||||
private updateFromDate = (newFromDate: DateOfYear | null) => {
|
||||
@action.bound
|
||||
private updateFromDate(newFromDate: DateOfYear | null) {
|
||||
this.props.schedule.from = newFromDate;
|
||||
}
|
||||
|
||||
private updateToDate = (newToDate: DateOfYear | null) => {
|
||||
@action.bound
|
||||
private updateToDate(newToDate: DateOfYear | null) {
|
||||
this.props.schedule.to = newToDate;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { computed, observable } from "mobx";
|
||||
import { action, computed, observable } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "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;
|
||||
}
|
||||
|
||||
onUsernameChange = (e: any, data: InputOnChangeData) => {
|
||||
@action.bound
|
||||
onUsernameChange(e: any, data: InputOnChangeData) {
|
||||
this.username = data.value;
|
||||
}
|
||||
|
||||
onPasswordChange = (e: any, data: InputOnChangeData) => {
|
||||
@action.bound
|
||||
onPasswordChange(e: any, data: InputOnChangeData) {
|
||||
this.password = data.value;
|
||||
}
|
||||
|
||||
@action.bound
|
||||
login(appState: AppState) {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
appState.httpApi.grantPassword(this.username, this.password)
|
||||
.then(() => {
|
||||
.then(action("loginSuccess", () => {
|
||||
this.loading = false;
|
||||
log.info("logged in");
|
||||
appState.history.push("/");
|
||||
})
|
||||
.catch((err) => {
|
||||
}))
|
||||
.catch(action("loginError", (err: any) => {
|
||||
this.loading = false;
|
||||
this.error = err.message;
|
||||
log.error({ err }, "login error");
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,8 +8,10 @@ import { Button, CheckboxProps, Form, Icon, Input, InputOnChangeData, Menu, Moda
|
||||
import { ProgramSequenceView, ScheduleView } from "@client/components";
|
||||
import * as route from "@client/routePaths";
|
||||
import { AppState, injectState } from "@client/state";
|
||||
import { ISprinklersDevice } from "@common/httpApi";
|
||||
import log from "@common/logger";
|
||||
import { Program, SprinklersDevice } from "@common/sprinklersRpc";
|
||||
import { action } from "mobx";
|
||||
|
||||
interface ProgramPageProps extends RouteComponentProps<{ deviceId: string, programId: string }> {
|
||||
appState: AppState;
|
||||
@ -21,6 +23,7 @@ class ProgramPage extends React.Component<ProgramPageProps> {
|
||||
return qs.parse(this.props.location.search).editing != null;
|
||||
}
|
||||
|
||||
iDevice!: ISprinklersDevice;
|
||||
device!: SprinklersDevice;
|
||||
program!: Program;
|
||||
programView: Program | null = null;
|
||||
@ -95,11 +98,16 @@ class ProgramPage extends React.Component<ProgramPageProps> {
|
||||
}
|
||||
|
||||
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);
|
||||
// tslint:disable-next-line:prefer-conditional-expression
|
||||
if (!this.device || this.device.id !== deviceId) {
|
||||
this.device = this.props.appState.sprinklersRpc.getDevice(deviceId);
|
||||
if (!this.iDevice || this.iDevice.id !== 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
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
this.program.running ? this.program.cancel() : this.program.run();
|
||||
}
|
||||
|
||||
private startEditing = () => {
|
||||
@action.bound
|
||||
private startEditing() {
|
||||
this.props.history.push({ search: qs.stringify({ editing: true }) });
|
||||
}
|
||||
|
||||
private save = () => {
|
||||
@action.bound
|
||||
private save() {
|
||||
if (!this.programView || !this.program) {
|
||||
return;
|
||||
}
|
||||
@ -183,22 +194,26 @@ class ProgramPage extends React.Component<ProgramPageProps> {
|
||||
this.stopEditing();
|
||||
}
|
||||
|
||||
private stopEditing = () => {
|
||||
@action.bound
|
||||
private stopEditing() {
|
||||
this.props.history.push({ search: "" });
|
||||
}
|
||||
|
||||
private close = () => {
|
||||
@action.bound
|
||||
private close() {
|
||||
const { deviceId } = this.props.match.params;
|
||||
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) {
|
||||
this.programView.name = p.value;
|
||||
}
|
||||
}
|
||||
|
||||
private onEnabledChange = (e: any, p: CheckboxProps) => {
|
||||
@action.bound
|
||||
private onEnabledChange(e: any, p: CheckboxProps) {
|
||||
if (this.programView) {
|
||||
this.programView.enabled = p.checked!;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { action, autorun, observable, when } from "mobx";
|
||||
import { action, autorun, observable, runInAction, when } from "mobx";
|
||||
import { update } from "serializr";
|
||||
|
||||
import { TokenStore } from "@client/state/TokenStore";
|
||||
@ -35,10 +35,7 @@ export class WSSprinklersDevice extends s.SprinklersDevice {
|
||||
this.api = api;
|
||||
this._id = id;
|
||||
|
||||
autorun(() => {
|
||||
this.connectionState.clientToServer = this.api.connectionState.clientToServer;
|
||||
this.connectionState.serverToBroker = this.api.connectionState.serverToBroker;
|
||||
});
|
||||
autorun(this.updateConnectionState);
|
||||
this.waitSubscribe();
|
||||
}
|
||||
|
||||
@ -46,20 +43,31 @@ export class WSSprinklersDevice extends s.SprinklersDevice {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
private updateConnectionState = () => {
|
||||
const { clientToServer, serverToBroker } = this.api.connectionState;
|
||||
runInAction("updateConnectionState", () => {
|
||||
Object.assign(this.connectionState, { clientToServer, serverToBroker });
|
||||
});
|
||||
}
|
||||
|
||||
async subscribe() {
|
||||
const subscribeRequest: ws.IDeviceSubscribeRequest = {
|
||||
deviceId: this.id,
|
||||
};
|
||||
try {
|
||||
await this.api.makeRequest("deviceSubscribe", subscribeRequest);
|
||||
this.connectionState.brokerToDevice = true;
|
||||
runInAction("deviceSubscribeSuccess", () => {
|
||||
this.connectionState.brokerToDevice = true;
|
||||
});
|
||||
} catch (err) {
|
||||
this.connectionState.brokerToDevice = false;
|
||||
if ((err as ws.IError).code === ErrorCode.NoPermission) {
|
||||
this.connectionState.hasPermission = false;
|
||||
} else {
|
||||
log.error({ err });
|
||||
}
|
||||
runInAction("deviceSubscribeError", () => {
|
||||
this.connectionState.brokerToDevice = false;
|
||||
if ((err as ws.IError).code === ErrorCode.NoPermission) {
|
||||
this.connectionState.hasPermission = false;
|
||||
} else {
|
||||
log.error({ err });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,7 +151,7 @@ export class WebSocketRpcClient implements s.SprinklersRPC {
|
||||
const res = await this.authenticate(this.tokenStore.accessToken.token!);
|
||||
this.authenticated = res.authenticated;
|
||||
logger.info({ user: res.user }, "authenticated websocket connection");
|
||||
this.userStore.userData = res.user;
|
||||
this.userStore.receiveUserData(res.user);
|
||||
} catch (err) {
|
||||
logger.error({ err }, "error authenticating websocket connection");
|
||||
// TODO message?
|
||||
@ -288,7 +296,7 @@ export class WebSocketRpcClient implements s.SprinklersRPC {
|
||||
try {
|
||||
rpc.handleNotification(this.notificationHandlers, data);
|
||||
} 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 = {
|
||||
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");
|
||||
},
|
||||
};
|
||||
private notificationHandlers = new WSClientNotificationHandlers(this);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { createBrowserHistory, History } from "history";
|
||||
import { computed } from "mobx";
|
||||
import { computed, configure } from "mobx";
|
||||
import { RouterStore, syncHistoryWithStore } from "mobx-react-router";
|
||||
|
||||
import { WebSocketRpcClient } from "@client/sprinklersRpc/WebSocketRpcClient";
|
||||
@ -24,6 +24,10 @@ export default class AppState {
|
||||
}
|
||||
|
||||
async start() {
|
||||
configure({
|
||||
enforceActions: true,
|
||||
});
|
||||
|
||||
syncHistoryWithStore(this.history, this.routerStore);
|
||||
this.tokenStore.loadLocalStorage();
|
||||
|
||||
|
@ -3,6 +3,7 @@ import ApiError from "@common/ApiError";
|
||||
import { ErrorCode } from "@common/ErrorCode";
|
||||
import { TokenGrantPasswordRequest, TokenGrantRefreshRequest, TokenGrantResponse } from "@common/httpApi";
|
||||
import log from "@common/logger";
|
||||
import { runInAction } from "mobx";
|
||||
|
||||
export { ApiError };
|
||||
|
||||
@ -58,9 +59,11 @@ export default class HttpApi {
|
||||
const response: TokenGrantResponse = await this.makeRequest("/token/grant", {
|
||||
method: "POST",
|
||||
}, request);
|
||||
this.tokenStore.accessToken.token = response.access_token;
|
||||
this.tokenStore.refreshToken.token = response.refresh_token;
|
||||
this.tokenStore.saveLocalStorage();
|
||||
runInAction("grantPasswordSuccess", () => {
|
||||
this.tokenStore.accessToken.token = response.access_token;
|
||||
this.tokenStore.refreshToken.token = response.refresh_token;
|
||||
this.tokenStore.saveLocalStorage();
|
||||
});
|
||||
const { accessToken } = this.tokenStore;
|
||||
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", {
|
||||
method: "POST",
|
||||
}, request);
|
||||
this.tokenStore.accessToken.token = response.access_token;
|
||||
this.tokenStore.refreshToken.token = response.refresh_token;
|
||||
this.tokenStore.saveLocalStorage();
|
||||
runInAction("grantRefreshSuccess", () => {
|
||||
this.tokenStore.accessToken.token = response.access_token;
|
||||
this.tokenStore.refreshToken.token = response.refresh_token;
|
||||
this.tokenStore.saveLocalStorage();
|
||||
});
|
||||
const { accessToken } = this.tokenStore;
|
||||
log.debug({ aud: accessToken.claims!.aud }, "got refresh grant tokens");
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { observable } from "mobx";
|
||||
import { action, observable } from "mobx";
|
||||
|
||||
import { Token } from "@client/state/Token";
|
||||
import { AccessToken, RefreshToken } from "@common/TokenClaims";
|
||||
@ -9,16 +9,19 @@ export class TokenStore {
|
||||
@observable accessToken: Token<AccessToken> = new Token();
|
||||
@observable refreshToken: Token<RefreshToken> = new Token();
|
||||
|
||||
@action
|
||||
clear() {
|
||||
this.accessToken.token = null;
|
||||
this.refreshToken.token = null;
|
||||
this.saveLocalStorage();
|
||||
}
|
||||
|
||||
@action
|
||||
saveLocalStorage() {
|
||||
window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(this.toJSON()));
|
||||
}
|
||||
|
||||
@action
|
||||
loadLocalStorage() {
|
||||
const data = window.localStorage.getItem(LOCAL_STORAGE_KEY);
|
||||
if (data) {
|
||||
@ -31,6 +34,7 @@ export class TokenStore {
|
||||
return { accessToken: this.accessToken.toJSON(), refreshToken: this.refreshToken.toJSON() };
|
||||
}
|
||||
|
||||
@action
|
||||
updateFromJson(json: any) {
|
||||
this.accessToken.token = json.accessToken;
|
||||
this.refreshToken.token = json.refreshToken;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { IObservableArray, observable } from "mobx";
|
||||
import { action, IObservableArray, observable } from "mobx";
|
||||
import { MessageProps } from "semantic-ui-react";
|
||||
|
||||
import { getRandomId } from "@common/utils";
|
||||
@ -14,7 +14,8 @@ export interface UiMessageProps extends MessageProps {
|
||||
export class UiStore {
|
||||
messages: IObservableArray<UiMessage> = observable.array();
|
||||
|
||||
addMessage(message: UiMessageProps) {
|
||||
@action
|
||||
addMessage(message: UiMessageProps): UiMessage {
|
||||
const { timeout, ...otherProps } = message;
|
||||
const msg = observable({
|
||||
...otherProps,
|
||||
@ -22,7 +23,15 @@ export class UiStore {
|
||||
});
|
||||
this.messages.push(msg);
|
||||
if (timeout) {
|
||||
setTimeout(() => this.messages.remove(msg), timeout);
|
||||
setTimeout(() => {
|
||||
this.removeMessage(msg);
|
||||
}, timeout);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
@action
|
||||
removeMessage(message: UiMessage) {
|
||||
return this.messages.remove(message);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,17 @@
|
||||
import { IUser } from "@common/httpApi";
|
||||
import { observable } from "mobx";
|
||||
import { ISprinklersDevice, IUser } from "@common/httpApi";
|
||||
import { action, observable } from "mobx";
|
||||
|
||||
export class UserStore {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +107,8 @@ function formatLevel(value: any): ColoredString {
|
||||
}
|
||||
|
||||
const logger: pino.Logger = pino({
|
||||
browser: { write },
|
||||
serializers: pino.stdSerializers,
|
||||
browser: { serialize: true, write },
|
||||
level: "trace",
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user