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

View File

@ -1,38 +1,48 @@
import {EventEmitter} from 'events'; import {BaseStore} from './BaseStore';
import dispatcher, {IEvent} from './dispatcher'; import axios from 'axios';
import * as config from '../config';
import {action} from 'mobx';
import SnackManager, {SnackReporter} from './SnackManager';
class AppStore extends EventEmitter { class NewAppStore extends BaseStore<IApplication> {
private apps: IApplication[] = []; public constructor(private readonly snack: SnackReporter) {
super();
public get(): IApplication[] {
return this.apps;
} }
public getById(id: number): IApplication { protected requestItems = (): Promise<IApplication[]> => {
const app = this.getByIdOrUndefined(id); return axios
if (!app) { .get<IApplication[]>(`${config.get('url')}application`)
throw new Error('app is required to exist'); .then((response) => response.data);
} };
return app;
}
public getName(id: number): string { protected requestDelete = (id: number): Promise<void> => {
const app = this.getByIdOrUndefined(id); 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'; 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(); export default new NewAppStore(SnackManager.snack);
dispatcher.register(store.handle.bind(store));
export default store;