Migrate AppStore to mobx

This commit is contained in:
Jannis Mattheis 2018-10-21 13:13:17 +02:00
parent 667648b0c9
commit 2871e22a61
4 changed files with 68 additions and 138 deletions

View File

@ -1,53 +0,0 @@
import axios, {AxiosResponse} from 'axios';
import * as config from '../config';
import dispatcher from '../stores/dispatcher';
import {snack} from './GlobalAction';
/** Fetches all applications. */
export function fetchApps() {
axios.get(config.get('url') + 'application').then((resp: AxiosResponse<IMessage[]>) => {
dispatcher.dispatch({type: 'UPDATE_APPS', payload: resp.data});
});
}
/**
* Delete an application by id.
* @param {int} id the application id
*/
export function deleteApp(id: number) {
axios
.delete(config.get('url') + 'application/' + id)
.then(() => {
fetchApps();
dispatcher.dispatch({type: 'DELETE_MESSAGES', payload: id});
})
.then(() => snack('Application deleted'));
}
/**
* Create an application.
* @param {string} name the application name
* @param {string} description the description of the application.
*/
export function createApp(name: string, description: string) {
axios
.post(config.get('url') + 'application', {name, description})
.then(fetchApps)
.then(() => snack('Application created'));
}
/**
* Upload an image for an application.
* @param {int} id the application id
* @param {Blob} file the description of the application.
*/
export function uploadImage(id: number, file: Blob) {
const formData = new FormData();
formData.append('file', file);
axios
.post(config.get('url') + 'application/' + id + '/image', formData, {
headers: {'content-type': 'multipart/form-data'},
})
.then(fetchApps)
.then(() => snack('Application image updated'));
}

View File

@ -6,6 +6,7 @@ import {StyleRules, Theme, WithStyles, withStyles} from '@material-ui/core/style
import React, {Component} from 'react';
import {Link} from 'react-router-dom';
import AppStore from '../stores/AppStore';
import {observer} from 'mobx-react';
const styles = (theme: Theme): StyleRules<'drawerPaper' | 'toolbar' | 'link'> => ({
drawerPaper: {
@ -27,24 +28,11 @@ interface IProps {
loggedIn: boolean;
}
interface IState {
apps: IApplication[];
}
class Navigation extends Component<IProps & Styles, IState> {
public state: IState = {apps: []};
public componentWillMount() {
AppStore.on('change', this.updateApps);
}
public componentWillUnmount() {
AppStore.removeListener('change', this.updateApps);
}
@observer
class Navigation extends Component<IProps & Styles> {
public render() {
const {classes, loggedIn} = this.props;
const {apps} = this.state;
const apps = AppStore.getItems();
const userApps =
apps.length === 0
@ -88,8 +76,6 @@ class Navigation extends Component<IProps & Styles, IState> {
</Drawer>
);
}
private updateApps = () => this.setState({apps: AppStore.get()});
}
export default withStyles(styles, {withTheme: true})<IProps>(Navigation);

View File

@ -10,42 +10,36 @@ import TableRow from '@material-ui/core/TableRow';
import Delete from '@material-ui/icons/Delete';
import Edit from '@material-ui/icons/Edit';
import React, {ChangeEvent, Component, SFC} from 'react';
import * as AppAction from '../actions/AppAction';
import ConfirmDialog from '../component/ConfirmDialog';
import DefaultPage from '../component/DefaultPage';
import ToggleVisibility from '../component/ToggleVisibility';
import AppStore from '../stores/AppStore';
import AddApplicationDialog from './dialog/AddApplicationDialog';
import AppStore from '../stores/AppStore';
import {observer} from 'mobx-react';
import {observable} from 'mobx';
interface IState {
apps: IApplication[];
createDialog: boolean;
deleteId: number;
}
@observer
class Applications extends Component {
@observable
private deleteId: number | false = false;
@observable
private createDialog = false;
class Applications extends Component<{}, IState> {
public state = {apps: [], createDialog: false, deleteId: -1};
private uploadId = -1;
private upload: HTMLInputElement | null = null;
public componentWillMount() {
AppStore.on('change', this.updateApps);
this.updateApps();
}
public componentWillUnmount() {
AppStore.removeListener('change', this.updateApps);
}
public componentDidMount = AppStore.refresh;
public render() {
const {apps, createDialog, deleteId} = this.state;
const {createDialog, deleteId} = this;
const apps = AppStore.getItems();
return (
<DefaultPage
title="Applications"
buttonTitle="Create Application"
buttonId="create-app"
maxWidth={1000}
fButton={this.showCreateDialog}>
fButton={() => (this.createDialog = true)}>
<Grid item xs={12}>
<Paper elevation={6}>
<Table id="app-table">
@ -68,7 +62,7 @@ class Applications extends Component<{}, IState> {
name={app.name}
value={app.token}
fUpload={() => this.uploadImage(app.id)}
fDelete={() => this.showCloseDialog(app.id)}
fDelete={() => (this.deleteId = app.id)}
/>
);
})}
@ -84,23 +78,22 @@ class Applications extends Component<{}, IState> {
</Grid>
{createDialog && (
<AddApplicationDialog
fClose={this.hideCreateDialog}
fOnSubmit={AppAction.createApp}
fClose={() => (this.createDialog = false)}
fOnSubmit={AppStore.create}
/>
)}
{deleteId !== -1 && (
{deleteId !== false && (
<ConfirmDialog
title="Confirm Delete"
text={'Delete ' + AppStore.getById(deleteId).name + '?'}
fClose={this.hideCloseDialog}
fOnSubmit={() => AppAction.deleteApp(deleteId)}
text={'Delete ' + AppStore.getByID(deleteId).name + '?'}
fClose={() => (this.deleteId = false)}
fOnSubmit={() => AppStore.remove(deleteId)}
/>
)}
</DefaultPage>
);
}
private updateApps = () => this.setState({...this.state, apps: AppStore.get()});
private uploadImage = (id: number) => {
this.uploadId = id;
if (this.upload) {
@ -114,17 +107,11 @@ class Applications extends Component<{}, IState> {
return;
}
if (['image/png', 'image/jpeg', 'image/gif'].indexOf(file.type) !== -1) {
AppAction.uploadImage(this.uploadId, file);
AppStore.uploadImage(this.uploadId, file);
} else {
alert('Uploaded file must be of type png, jpeg or gif.');
}
};
private showCreateDialog = () => this.setState({...this.state, createDialog: true});
private hideCreateDialog = () => this.setState({...this.state, createDialog: false});
private showCloseDialog = (deleteId: number) => this.setState({...this.state, deleteId});
private hideCloseDialog = () => this.setState({...this.state, deleteId: -1});
}
interface IRowProps {
@ -136,7 +123,7 @@ interface IRowProps {
fDelete: VoidFunction;
}
const Row: SFC<IRowProps> = ({name, value, description, fDelete, fUpload, image}) => (
const Row: SFC<IRowProps> = observer(({name, value, description, fDelete, fUpload, image}) => (
<TableRow>
<TableCell padding="checkbox">
<div style={{display: 'flex'}}>
@ -157,6 +144,6 @@ const Row: SFC<IRowProps> = ({name, value, description, fDelete, fUpload, image}
</IconButton>
</TableCell>
</TableRow>
);
));
export default Applications;

View File

@ -1,38 +1,48 @@
import {EventEmitter} from 'events';
import dispatcher, {IEvent} from './dispatcher';
import {BaseStore} from './BaseStore';
import axios from 'axios';
import * as config from '../config';
import {action} from 'mobx';
import SnackManager, {SnackReporter} from './SnackManager';
class AppStore extends EventEmitter {
private apps: IApplication[] = [];
public get(): IApplication[] {
return this.apps;
class NewAppStore extends BaseStore<IApplication> {
public constructor(private readonly snack: SnackReporter) {
super();
}
public getById(id: number): IApplication {
const app = this.getByIdOrUndefined(id);
if (!app) {
throw new Error('app is required to exist');
}
return app;
}
protected requestItems = (): Promise<IApplication[]> => {
return axios
.get<IApplication[]>(`${config.get('url')}application`)
.then((response) => response.data);
};
public getName(id: number): string {
const app = this.getByIdOrUndefined(id);
protected requestDelete = (id: number): Promise<void> => {
return axios
.delete(`${config.get('url')}application/${id}`)
.then(() => this.snack('Application deleted'));
};
@action
public uploadImage = async (id: number, file: Blob): Promise<void> => {
const formData = new FormData();
formData.append('file', file);
await axios.post(`${config.get('url')}application/${id}/image`, formData, {
headers: {'content-type': 'multipart/form-data'},
});
await this.refresh();
this.snack('Application image updated');
};
@action
public create = async (name: string, description: string): Promise<void> => {
await axios.post(`${config.get('url')}application`, {name, description});
await this.refresh();
this.snack('Application created');
};
public getName = (id: number): string => {
const app = this.getByIDOrUndefined(id);
return id === -1 ? 'All Messages' : app !== undefined ? app.name : 'unknown';
}
public handle(data: IEvent): void {
if (data.type === 'UPDATE_APPS') {
this.apps = data.payload;
this.emit('change');
}
}
private getByIdOrUndefined(id: number): IApplication | undefined {
return this.apps.find((a) => a.id === id);
}
};
}
const store = new AppStore();
dispatcher.register(store.handle.bind(store));
export default store;
export default new NewAppStore(SnackManager.snack);