Migrate MessagesStore to mobx
And use new infinite list implementation
This commit is contained in:
parent
4af9b9686f
commit
9c232780c5
|
|
@ -1,93 +0,0 @@
|
||||||
import axios, {AxiosResponse} from 'axios';
|
|
||||||
import * as config from '../config';
|
|
||||||
import dispatcher from '../stores/dispatcher';
|
|
||||||
import {getToken} from './defaultAxios';
|
|
||||||
import {snack} from './GlobalAction';
|
|
||||||
import * as UserAction from './UserAction';
|
|
||||||
|
|
||||||
export function fetchMessagesApp(id: number, since: number) {
|
|
||||||
if (id === -1) {
|
|
||||||
return axios
|
|
||||||
.get(config.get('url') + 'message?since=' + since)
|
|
||||||
.then((resp: AxiosResponse<IPagedMessages>) => {
|
|
||||||
newMessages(-1, resp.data);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return axios
|
|
||||||
.get(config.get('url') + 'application/' + id + '/message?since=' + since)
|
|
||||||
.then((resp: AxiosResponse<IPagedMessages>) => {
|
|
||||||
newMessages(id, resp.data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function newMessages(id: number, data: IPagedMessages) {
|
|
||||||
dispatcher.dispatch({
|
|
||||||
type: 'UPDATE_MESSAGES',
|
|
||||||
payload: {
|
|
||||||
messages: data.messages,
|
|
||||||
hasMore: 'next' in data.paging,
|
|
||||||
nextSince: data.paging.since,
|
|
||||||
id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes all messages from the current user and an application.
|
|
||||||
* @param {int} id the application id
|
|
||||||
*/
|
|
||||||
export function deleteMessagesByApp(id: number) {
|
|
||||||
if (id === -1) {
|
|
||||||
axios.delete(config.get('url') + 'message').then(() => {
|
|
||||||
dispatcher.dispatch({type: 'DELETE_MESSAGES', payload: -1});
|
|
||||||
snack('Messages deleted');
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
axios.delete(config.get('url') + 'application/' + id + '/message').then(() => {
|
|
||||||
dispatcher.dispatch({type: 'DELETE_MESSAGES', payload: id});
|
|
||||||
snack('Deleted all messages from the application');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteMessage(msg: IMessage) {
|
|
||||||
axios.delete(config.get('url') + 'message/' + msg.id).then(() => {
|
|
||||||
dispatcher.dispatch({type: 'DELETE_MESSAGE', payload: msg});
|
|
||||||
snack('Message deleted');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let wsActive = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts listening to the stream for new messages.
|
|
||||||
*/
|
|
||||||
export function listenToWebSocket() {
|
|
||||||
if (!getToken() || wsActive) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
wsActive = true;
|
|
||||||
|
|
||||||
const wsUrl = config
|
|
||||||
.get('url')
|
|
||||||
.replace('http', 'ws')
|
|
||||||
.replace('https', 'wss');
|
|
||||||
const ws = new WebSocket(wsUrl + 'stream?token=' + getToken());
|
|
||||||
|
|
||||||
ws.onerror = (e) => {
|
|
||||||
wsActive = false;
|
|
||||||
console.log('WebSocket connection errored', e);
|
|
||||||
};
|
|
||||||
|
|
||||||
ws.onmessage = (data) =>
|
|
||||||
dispatcher.dispatch({type: 'ONE_MESSAGE', payload: JSON.parse(data.data) as IMessage});
|
|
||||||
|
|
||||||
ws.onclose = () => {
|
|
||||||
wsActive = false;
|
|
||||||
UserAction.tryAuthenticate().then(() => {
|
|
||||||
snack('WebSocket connection closed, trying again in 30 seconds.');
|
|
||||||
setTimeout(listenToWebSocket, 30000);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -3,24 +3,21 @@ import Grid from '@material-ui/core/Grid';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
import React, {Component} from 'react';
|
import React, {Component} from 'react';
|
||||||
import {RouteComponentProps} from 'react-router';
|
import {RouteComponentProps} from 'react-router';
|
||||||
import * as MessageAction from '../actions/MessageAction';
|
|
||||||
import DefaultPage from '../component/DefaultPage';
|
import DefaultPage from '../component/DefaultPage';
|
||||||
import ReactList from '../component/FixedReactList';
|
|
||||||
import Message from '../component/Message';
|
import Message from '../component/Message';
|
||||||
import AppStore from '../stores/AppStore';
|
import AppStore from '../stores/AppStore';
|
||||||
import MessageStore from '../stores/MessageStore';
|
import MessagesStore from '../stores/MessagesStore';
|
||||||
|
import {observer} from 'mobx-react';
|
||||||
|
// @ts-ignore
|
||||||
|
import InfiniteAnyHeight from 'react-infinite-any-height';
|
||||||
|
|
||||||
interface IProps extends RouteComponentProps<{id: string}> {}
|
interface IProps extends RouteComponentProps<{id: string}> {}
|
||||||
|
|
||||||
interface IState {
|
interface IState {
|
||||||
appId: number;
|
appId: number;
|
||||||
messages: IMessage[];
|
|
||||||
name: string;
|
|
||||||
hasMore: boolean;
|
|
||||||
nextSince?: number;
|
|
||||||
id?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
class Messages extends Component<IProps, IState> {
|
class Messages extends Component<IProps, IState> {
|
||||||
private static appId(props: IProps) {
|
private static appId(props: IProps) {
|
||||||
if (props === undefined) {
|
if (props === undefined) {
|
||||||
|
|
@ -30,47 +27,47 @@ class Messages extends Component<IProps, IState> {
|
||||||
return match.params.id !== undefined ? parseInt(match.params.id, 10) : -1;
|
return match.params.id !== undefined ? parseInt(match.params.id, 10) : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public state = {appId: -1, messages: [], name: 'unknown', hasMore: true};
|
public state = {appId: -1};
|
||||||
|
|
||||||
private list: ReactList | null = null;
|
private isLoadingMore = false;
|
||||||
|
|
||||||
public componentWillReceiveProps(nextProps: IProps) {
|
public componentWillReceiveProps(nextProps: IProps) {
|
||||||
this.updateAllWithProps(nextProps);
|
this.updateAllWithProps(nextProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillMount() {
|
public componentWillMount() {
|
||||||
MessageStore.on('change', this.updateAll);
|
window.onscroll = () => {
|
||||||
AppStore.on('change', this.updateAll);
|
if (
|
||||||
|
window.innerHeight + window.pageYOffset >=
|
||||||
|
document.body.offsetHeight - window.innerHeight * 2
|
||||||
|
) {
|
||||||
|
this.checkIfLoadMore();
|
||||||
|
}
|
||||||
|
};
|
||||||
this.updateAll();
|
this.updateAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
|
||||||
MessageStore.removeListener('change', this.updateAll);
|
|
||||||
AppStore.removeListener('change', this.updateAll);
|
|
||||||
}
|
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const {name, messages, hasMore, appId} = this.state;
|
const {appId} = this.state;
|
||||||
|
const messages = MessagesStore.get(appId);
|
||||||
|
const hasMore = MessagesStore.canLoadMore(appId);
|
||||||
|
const name = AppStore.getName(appId);
|
||||||
const hasMessages = messages.length !== 0;
|
const hasMessages = messages.length !== 0;
|
||||||
const deleteMessages = () => MessageAction.deleteMessagesByApp(appId);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultPage
|
<DefaultPage
|
||||||
title={name}
|
title={name}
|
||||||
buttonTitle="Delete All"
|
buttonTitle="Delete All"
|
||||||
buttonId="delete-all"
|
buttonId="delete-all"
|
||||||
fButton={deleteMessages}
|
fButton={() => MessagesStore.removeByApp(appId)}
|
||||||
buttonDisabled={!hasMessages}>
|
buttonDisabled={!hasMessages}>
|
||||||
{hasMessages ? (
|
{hasMessages ? (
|
||||||
<div style={{width: '100%'}} id="messages">
|
<div style={{width: '100%'}} id="messages">
|
||||||
<ReactList
|
<InfiniteAnyHeight
|
||||||
key={appId}
|
key={appId}
|
||||||
ref={(el: ReactList) => (this.list = el)}
|
list={messages.map(this.renderMessage)}
|
||||||
itemRenderer={this.renderMessage}
|
preloadAdditionalHeight={window.innerHeight * 2.5}
|
||||||
length={messages.length}
|
useWindowAsScrollContainer
|
||||||
threshold={1000}
|
|
||||||
pageSize={30}
|
|
||||||
type="variable"
|
|
||||||
/>
|
/>
|
||||||
{hasMore ? (
|
{hasMore ? (
|
||||||
<Grid item xs={12} style={{textAlign: 'center'}}>
|
<Grid item xs={12} style={{textAlign: 'center'}}>
|
||||||
|
|
@ -90,27 +87,21 @@ class Messages extends Component<IProps, IState> {
|
||||||
private updateAllWithProps = (props: IProps) => {
|
private updateAllWithProps = (props: IProps) => {
|
||||||
const appId = Messages.appId(props);
|
const appId = Messages.appId(props);
|
||||||
|
|
||||||
const reset = MessageStore.shouldReset(appId);
|
this.setState({appId});
|
||||||
if (reset !== false && this.list) {
|
if (!MessagesStore.exists(appId)) {
|
||||||
this.list.clearCacheFromIndex(reset);
|
MessagesStore.loadMore(appId);
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({...MessageStore.get(appId), appId, name: AppStore.getName(appId)});
|
|
||||||
if (!MessageStore.exists(appId)) {
|
|
||||||
MessageStore.loadNext(appId);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private updateAll = () => this.updateAllWithProps(this.props);
|
private updateAll = () => this.updateAllWithProps(this.props);
|
||||||
|
|
||||||
private deleteMessage = (message: IMessage) => () => MessageAction.deleteMessage(message);
|
private deleteMessage = (message: IMessage) => () => MessagesStore.removeSingle(message);
|
||||||
|
|
||||||
private renderMessage = (index: number, key: string) => {
|
private renderMessage = (message: IMessage) => {
|
||||||
this.checkIfLoadMore();
|
this.checkIfLoadMore();
|
||||||
const message: IMessage = this.state.messages[index];
|
|
||||||
return (
|
return (
|
||||||
<Message
|
<Message
|
||||||
key={key}
|
key={message.id}
|
||||||
fDelete={this.deleteMessage(message)}
|
fDelete={this.deleteMessage(message)}
|
||||||
title={message.title}
|
title={message.title}
|
||||||
date={message.date}
|
date={message.date}
|
||||||
|
|
@ -121,12 +112,10 @@ class Messages extends Component<IProps, IState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
private checkIfLoadMore() {
|
private checkIfLoadMore() {
|
||||||
const {hasMore, messages, appId} = this.state;
|
const {appId} = this.state;
|
||||||
if (hasMore) {
|
if (!this.isLoadingMore && MessagesStore.canLoadMore(appId)) {
|
||||||
const [, maxRenderedIndex] = (this.list && this.list.getVisibleRange()) || [0, 0];
|
this.isLoadingMore = true;
|
||||||
if (maxRenderedIndex > messages.length - 30) {
|
MessagesStore.loadMore(appId).then(() => (this.isLoadingMore = false));
|
||||||
MessageStore.loadNext(appId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
import {EventEmitter} from 'events';
|
|
||||||
import * as MessageAction from '../actions/MessageAction';
|
|
||||||
import AppStore from './AppStore';
|
|
||||||
import dispatcher, {IEvent} from './dispatcher';
|
|
||||||
|
|
||||||
class MessageStore extends EventEmitter {
|
|
||||||
private appToMessages: {[appId: number]: IAppMessages} = {};
|
|
||||||
private reset: false | number = false;
|
|
||||||
private resetOnAll: false | number = false;
|
|
||||||
private loading = false;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
AppStore.on('change', () => {
|
|
||||||
this.updateApps();
|
|
||||||
this.emit('change');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public shouldReset(appId: number): false | number {
|
|
||||||
const reset = appId === -1 ? this.resetOnAll : this.reset;
|
|
||||||
if (reset !== false) {
|
|
||||||
this.reset = false;
|
|
||||||
this.resetOnAll = false;
|
|
||||||
}
|
|
||||||
return reset;
|
|
||||||
}
|
|
||||||
|
|
||||||
public loadNext(id: number): void {
|
|
||||||
if (this.loading || !this.get(id).hasMore) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.loading = true;
|
|
||||||
MessageAction.fetchMessagesApp(id, this.get(id).nextSince).catch(
|
|
||||||
() => (this.loading = false)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get(id: number): IAppMessages {
|
|
||||||
if (this.exists(id)) {
|
|
||||||
return this.appToMessages[id];
|
|
||||||
} else {
|
|
||||||
return {messages: [], nextSince: 0, hasMore: true};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public exists(id: number): boolean {
|
|
||||||
return this.appToMessages[id] !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public handle(data: IEvent): void {
|
|
||||||
const {payload} = data;
|
|
||||||
if (data.type === 'UPDATE_MESSAGES') {
|
|
||||||
if (this.exists(payload.id)) {
|
|
||||||
payload.messages = this.get(payload.id).messages.concat(payload.messages);
|
|
||||||
}
|
|
||||||
this.appToMessages[payload.id] = payload;
|
|
||||||
this.updateApps();
|
|
||||||
this.loading = false;
|
|
||||||
this.emit('change');
|
|
||||||
} else if (data.type === 'ONE_MESSAGE') {
|
|
||||||
if (this.exists(payload.appid)) {
|
|
||||||
this.appToMessages[payload.appid].messages.unshift(payload);
|
|
||||||
this.reset = 0;
|
|
||||||
}
|
|
||||||
if (this.exists(-1)) {
|
|
||||||
this.appToMessages[-1].messages.unshift(payload);
|
|
||||||
this.resetOnAll = 0;
|
|
||||||
}
|
|
||||||
this.updateApps();
|
|
||||||
this.emit('change');
|
|
||||||
} else if (data.type === 'DELETE_MESSAGE') {
|
|
||||||
this.resetOnAll = this.removeFromList(this.appToMessages[-1], payload);
|
|
||||||
this.reset = this.removeFromList(this.appToMessages[payload.appid], payload);
|
|
||||||
this.emit('change');
|
|
||||||
} else if (data.type === 'DELETE_MESSAGES') {
|
|
||||||
const id = payload;
|
|
||||||
if (id === -1) {
|
|
||||||
this.appToMessages = {};
|
|
||||||
} else {
|
|
||||||
delete this.appToMessages[-1];
|
|
||||||
delete this.appToMessages[id];
|
|
||||||
}
|
|
||||||
this.reset = 0;
|
|
||||||
this.emit('change');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private removeFromList(messages: IAppMessages, messageToDelete: IMessage): false | number {
|
|
||||||
if (messages) {
|
|
||||||
const index = messages.messages.findIndex(
|
|
||||||
(message) => message.id === messageToDelete.id
|
|
||||||
);
|
|
||||||
if (index !== -1) {
|
|
||||||
messages.messages.splice(index, 1);
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateApps = (): void => {
|
|
||||||
const appToUrl: {[appId: number]: string} = {};
|
|
||||||
AppStore.get().forEach((app) => (appToUrl[app.id] = app.image));
|
|
||||||
Object.keys(this.appToMessages).forEach((key) => {
|
|
||||||
const appMessages: IAppMessages = this.appToMessages[key];
|
|
||||||
appMessages.messages.forEach((message) => (message.image = appToUrl[message.appid]));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const store = new MessageStore();
|
|
||||||
dispatcher.register(store.handle.bind(store));
|
|
||||||
export default store;
|
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
import {BaseStore} from './BaseStore';
|
||||||
|
import NewAppStore from './AppStore';
|
||||||
|
import {action, IObservableArray, observable, reaction} from 'mobx';
|
||||||
|
import axios, {AxiosResponse} from 'axios';
|
||||||
|
import * as config from '../config';
|
||||||
|
import {createTransformer} from 'mobx-utils';
|
||||||
|
import SnackManager, {SnackReporter} from './SnackManager';
|
||||||
|
|
||||||
|
const AllMessages = -1;
|
||||||
|
|
||||||
|
interface MessagesState {
|
||||||
|
messages: IObservableArray<IMessage>;
|
||||||
|
hasMore: boolean;
|
||||||
|
nextSince: number;
|
||||||
|
loaded: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessagesStore {
|
||||||
|
@observable
|
||||||
|
private state: Record<number, MessagesState> = {};
|
||||||
|
@observable
|
||||||
|
public lastNewMessageAppId = 0;
|
||||||
|
|
||||||
|
private loading = false;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private readonly appStore: BaseStore<IApplication>,
|
||||||
|
private readonly snack: SnackReporter
|
||||||
|
) {
|
||||||
|
reaction(() => appStore.getItems(), this.createEmptyStatesForApps);
|
||||||
|
}
|
||||||
|
|
||||||
|
private stateOf = (appId: number, create = true) => {
|
||||||
|
if (this.state[appId] || !create) {
|
||||||
|
return this.state[appId] || this.emptyState();
|
||||||
|
}
|
||||||
|
return (this.state[appId] = this.emptyState());
|
||||||
|
};
|
||||||
|
|
||||||
|
public canLoadMore = (appId: number) => this.stateOf(appId, /*create*/ false).hasMore;
|
||||||
|
|
||||||
|
@action
|
||||||
|
public loadMore = async (appId: number) => {
|
||||||
|
const state = this.stateOf(appId);
|
||||||
|
if (!state.hasMore || this.loading) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
const pagedResult = await this.fetchMessages(appId, state.nextSince).then(
|
||||||
|
(resp) => resp.data
|
||||||
|
);
|
||||||
|
|
||||||
|
state.messages.replace([...state.messages, ...pagedResult.messages]);
|
||||||
|
state.nextSince = pagedResult.paging.since || 0;
|
||||||
|
state.hasMore = 'next' in pagedResult.paging;
|
||||||
|
state.loaded = true;
|
||||||
|
this.loading = false;
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
public publishSingleMessage = (message: IMessage) => {
|
||||||
|
if (this.exists(AllMessages)) {
|
||||||
|
this.stateOf(AllMessages).messages.unshift(message);
|
||||||
|
}
|
||||||
|
if (this.exists(message.appid)) {
|
||||||
|
this.stateOf(message.appid).messages.unshift(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
public removeByApp = async (appId: number) => {
|
||||||
|
if (appId === AllMessages) {
|
||||||
|
await axios.delete(config.get('url') + 'message');
|
||||||
|
this.snack('Deleted all messages');
|
||||||
|
this.clearAll();
|
||||||
|
} else {
|
||||||
|
await axios.delete(config.get('url') + 'application/' + appId + '/message');
|
||||||
|
this.snack(`Deleted all messages from ${this.appStore.getByID(appId).name}`);
|
||||||
|
this.clear(AllMessages);
|
||||||
|
this.clear(appId);
|
||||||
|
}
|
||||||
|
await this.loadMore(appId);
|
||||||
|
};
|
||||||
|
|
||||||
|
@action
|
||||||
|
public removeSingle = async (message: IMessage) => {
|
||||||
|
await axios.delete(config.get('url') + 'message/' + message.id);
|
||||||
|
if (this.exists(AllMessages)) {
|
||||||
|
this.removeFromList(this.state[AllMessages].messages, message);
|
||||||
|
}
|
||||||
|
if (this.exists(message.appid)) {
|
||||||
|
this.removeFromList(this.state[message.appid].messages, message);
|
||||||
|
}
|
||||||
|
this.snack('Message deleted');
|
||||||
|
};
|
||||||
|
|
||||||
|
public exists = (id: number) => this.stateOf(id).loaded;
|
||||||
|
|
||||||
|
private removeFromList(messages: IMessage[], messageToDelete: IMessage): false | number {
|
||||||
|
if (messages) {
|
||||||
|
const index = messages.findIndex((message) => message.id === messageToDelete.id);
|
||||||
|
if (index !== -1) {
|
||||||
|
messages.splice(index, 1);
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearAll = () => {
|
||||||
|
this.state = {};
|
||||||
|
this.createEmptyStatesForApps(this.appStore.getItems());
|
||||||
|
};
|
||||||
|
|
||||||
|
private clear = (appId: number) => (this.state[appId] = this.emptyState());
|
||||||
|
|
||||||
|
private fetchMessages = (
|
||||||
|
appId: number,
|
||||||
|
since: number
|
||||||
|
): Promise<AxiosResponse<IPagedMessages>> => {
|
||||||
|
if (appId === AllMessages) {
|
||||||
|
return axios.get(config.get('url') + 'message?since=' + since);
|
||||||
|
} else {
|
||||||
|
return axios.get(
|
||||||
|
config.get('url') + 'application/' + appId + '/message?since=' + since
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private getUnCached = (appId: number): Array<IMessage & {image: string}> => {
|
||||||
|
const appToImage = this.appStore
|
||||||
|
.getItems()
|
||||||
|
.reduce((all, app) => ({...all, [app.id]: app.image}), {});
|
||||||
|
|
||||||
|
return this.stateOf(appId, false).messages.map((message: IMessage) => {
|
||||||
|
return {
|
||||||
|
...message,
|
||||||
|
image: appToImage[message.appid] || 'still loading',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
public get = createTransformer(this.getUnCached);
|
||||||
|
|
||||||
|
private clearCache = () => (this.get = createTransformer(this.getUnCached));
|
||||||
|
|
||||||
|
private createEmptyStatesForApps = (apps: IApplication[]) => {
|
||||||
|
apps.map((app) => app.id).forEach((id) => this.stateOf(id, /*create*/ true));
|
||||||
|
this.clearCache();
|
||||||
|
};
|
||||||
|
|
||||||
|
private emptyState = (): MessagesState => ({
|
||||||
|
messages: observable.array(),
|
||||||
|
hasMore: true,
|
||||||
|
nextSince: 0,
|
||||||
|
loaded: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new MessagesStore(NewAppStore, SnackManager.snack);
|
||||||
|
|
@ -200,7 +200,7 @@ describe('Messages', () => {
|
||||||
});
|
});
|
||||||
it('deletes a windows message', async () => {
|
it('deletes a windows message', async () => {
|
||||||
await navigate('Windows');
|
await navigate('Windows');
|
||||||
await page.click('#messages .message:nth-child(2) .delete');
|
await page.click('#messages span:nth-of-type(2) .message .delete');
|
||||||
await expectMessages({
|
await expectMessages({
|
||||||
all: [linux2, windows3, backup1, linux1, windows1],
|
all: [linux2, windows3, backup1, linux1, windows1],
|
||||||
windows: [windows3, windows1],
|
windows: [windows3, windows1],
|
||||||
|
|
@ -257,5 +257,12 @@ describe('Messages', () => {
|
||||||
backup: [backup3],
|
backup: [backup3],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('deletes all backup messages and navigates to all messages', async () => {
|
||||||
|
await navigate('Backup');
|
||||||
|
await page.click('#delete-all');
|
||||||
|
await navigate('All Messages');
|
||||||
|
await createMessage(backup3, backupServerToken);
|
||||||
|
expect(await extractMessages()).toEqual([backup3]);
|
||||||
|
});
|
||||||
it('does logout', async () => await auth.logout(page));
|
it('does logout', async () => await auth.logout(page));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue