import { setCurrentTokenDialogApiHost } from '~/store/current-token-dialog/current-token-dialog-actions';
import { processResourceActionSet } from './views-components/context-menu/action-sets/process-resource-action-set';
import { progressIndicatorActions } from '~/store/progress-indicator/progress-indicator-actions';
+import { snackbarActions } from "~/store/snackbar/snackbar-actions";
const getBuildNumber = () => "BN-" + (process.env.REACT_APP_BUILD_NUMBER || "dev");
const getGitCommit = () => "GIT-" + (process.env.REACT_APP_GIT_COMMIT || "latest").substr(0, 7);
fetchConfig()
.then(({ config, apiHost }) => {
const history = createBrowserHistory();
- const services = createServices(config, (id, working) => {
- store.dispatch(progressIndicatorActions.TOGGLE({ id, working }));
+ const services = createServices(config, {
+ progressFn: (id, working) => {
+ store.dispatch(progressIndicatorActions.TOGGLE({ id, working }));
+ },
+ errorFn: (id, message) => {
+ store.dispatch(snackbarActions.OPEN_SNACKBAR({ message }));
+ }
});
const store = configureStore(history, services);
// SPDX-License-Identifier: AGPL-3.0
export type ProgressFn = (id: string, working: boolean) => void;
+export type ErrorFn = (id: string, message: string) => void;
+
+export interface ApiActions {
+ progressFn: ProgressFn;
+ errorFn: ErrorFn;
+}
import { User } from "~/models/user";
import { AxiosInstance } from "axios";
-import { ProgressFn } from "~/services/api/api-progress";
+import { ApiActions, ProgressFn } from "~/services/api/api-actions";
import * as uuid from "uuid/v4";
export const API_TOKEN_KEY = 'apiToken';
constructor(
protected apiClient: AxiosInstance,
protected baseUrl: string,
- protected progressFn: ProgressFn) { }
+ protected actions: ApiActions) { }
public saveApiToken(token: string) {
localStorage.setItem(API_TOKEN_KEY, token);
public getUserDetails = (): Promise<User> => {
const reqId = uuid();
- this.progressFn(reqId, true);
+ this.actions.progressFn(reqId, true);
return this.apiClient
.get<UserDetailsResponse>('/users/current')
.then(resp => {
- this.progressFn(reqId, false);
+ this.actions.progressFn(reqId, false);
return {
email: resp.data.email,
firstName: resp.data.first_name,
};
})
.catch(e => {
- this.progressFn(reqId, false);
+ this.actions.progressFn(reqId, false);
+ this.actions.errorFn(reqId, e);
throw e;
});
}
import { parseFilesResponse } from "./collection-service-files-response";
import { fileToArrayBuffer } from "~/common/file";
import { TrashableResourceService } from "~/services/common-service/trashable-resource-service";
-import { ProgressFn } from "~/services/api/api-progress";
+import { ApiActions } from "~/services/api/api-actions";
export type UploadProgress = (fileId: number, loaded: number, total: number, currentTime: number) => void;
export class CollectionService extends TrashableResourceService<CollectionResource> {
- constructor(serverApi: AxiosInstance, private webdavClient: WebDAV, private authService: AuthService, progressFn: ProgressFn) {
- super(serverApi, "collections", progressFn);
+ constructor(serverApi: AxiosInstance, private webdavClient: WebDAV, private authService: AuthService, actions: ApiActions) {
+ super(serverApi, "collections", actions);
}
async files(uuid: string) {
import axios, { AxiosInstance } from "axios";
import MockAdapter from "axios-mock-adapter";
import { Resource } from "src/models/resource";
-import { ProgressFn } from "~/services/api/api-progress";
+import { ApiActions } from "~/services/api/api-actions";
+
+const actions: ApiActions = {
+ progressFn: (id: string, working: boolean) => {},
+ errorFn: (id: string, message: string) => {}
+};
export const mockResourceService = <R extends Resource, C extends CommonResourceService<R>>(
- Service: new (client: AxiosInstance, progressFn: ProgressFn) => C) => {
+ Service: new (client: AxiosInstance, actions: ApiActions) => C) => {
const axiosInstance = axios.create();
const axiosMock = new MockAdapter(axiosInstance);
- const service = new Service(axiosInstance, (id, working) => {});
+ const service = new Service(axiosInstance, actions);
Object.keys(service).map(key => service[key] = jest.fn());
return service;
};
describe("CommonResourceService", () => {
const axiosInstance = axios.create();
const axiosMock = new MockAdapter(axiosInstance);
- const progressFn = (id: string, working: boolean) => {};
beforeEach(() => {
axiosMock.reset();
.onPost("/resource/")
.reply(200, { owner_uuid: "ownerUuidValue" });
- const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
+ const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
const resource = await commonResourceService.create({ ownerUuid: "ownerUuidValue" });
expect(resource).toEqual({ ownerUuid: "ownerUuidValue" });
});
it("#create maps request params to snake case", async () => {
axiosInstance.post = jest.fn(() => Promise.resolve({data: {}}));
- const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
+ const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
await commonResourceService.create({ ownerUuid: "ownerUuidValue" });
expect(axiosInstance.post).toHaveBeenCalledWith("/resource/", {owner_uuid: "ownerUuidValue"});
});
.onDelete("/resource/uuid")
.reply(200, { deleted_at: "now" });
- const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
+ const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
const resource = await commonResourceService.delete("uuid");
expect(resource).toEqual({ deletedAt: "now" });
});
.onGet("/resource/uuid")
.reply(200, { modified_at: "now" });
- const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
+ const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
const resource = await commonResourceService.get("uuid");
expect(resource).toEqual({ modifiedAt: "now" });
});
items_available: 20
});
- const commonResourceService = new CommonResourceService(axiosInstance, "resource", progressFn);
+ const commonResourceService = new CommonResourceService(axiosInstance, "resource", actions);
const resource = await commonResourceService.list({ limit: 10, offset: 1 });
expect(resource).toEqual({
kind: "kind",
import { AxiosInstance, AxiosPromise } from "axios";
import { Resource } from "src/models/resource";
import * as uuid from "uuid/v4";
-import { ProgressFn } from "~/services/api/api-progress";
+import { ApiActions } from "~/services/api/api-actions";
export interface ListArguments {
limit?: number;
}
}
- static defaultResponse<R>(promise: AxiosPromise<R>, progressFn: ProgressFn): Promise<R> {
+ static defaultResponse<R>(promise: AxiosPromise<R>, actions: ApiActions): Promise<R> {
const reqId = uuid();
- progressFn(reqId, true);
+ actions.progressFn(reqId, true);
return promise
.then(data => {
- progressFn(reqId, false);
+ actions.progressFn(reqId, false);
return data;
})
.then(CommonResourceService.mapResponseKeys)
.catch(({ response }) => {
- progressFn(reqId, false);
+ actions.progressFn(reqId, false);
+ actions.errorFn(reqId, response.message);
Promise.reject<Errors>(CommonResourceService.mapResponseKeys(response));
});
}
protected serverApi: AxiosInstance;
protected resourceType: string;
- protected progressFn: ProgressFn;
+ protected actions: ApiActions;
- constructor(serverApi: AxiosInstance, resourceType: string, onProgress: ProgressFn) {
+ constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions) {
this.serverApi = serverApi;
this.resourceType = '/' + resourceType + '/';
- this.progressFn = onProgress;
+ this.actions = actions;
}
create(data?: Partial<T> | any) {
return CommonResourceService.defaultResponse(
this.serverApi
.post<T>(this.resourceType, data && CommonResourceService.mapKeys(_.snakeCase)(data)),
- this.progressFn
+ this.actions
);
}
return CommonResourceService.defaultResponse(
this.serverApi
.delete(this.resourceType + uuid),
- this.progressFn
+ this.actions
);
}
return CommonResourceService.defaultResponse(
this.serverApi
.get<T>(this.resourceType + uuid),
- this.progressFn
+ this.actions
);
}
.get(this.resourceType, {
params: CommonResourceService.mapKeys(_.snakeCase)(params)
}),
- this.progressFn
+ this.actions
);
}
return CommonResourceService.defaultResponse(
this.serverApi
.put<T>(this.resourceType + uuid, data && CommonResourceService.mapKeys(_.snakeCase)(data)),
- this.progressFn
+ this.actions
);
}
}
import { AxiosInstance } from "axios";
import { TrashableResource } from "src/models/resource";
import { CommonResourceService } from "~/services/common-service/common-resource-service";
-import { ProgressFn } from "~/services/api/api-progress";
+import { ApiActions } from "~/services/api/api-actions";
export class TrashableResourceService<T extends TrashableResource> extends CommonResourceService<T> {
- constructor(serverApi: AxiosInstance, resourceType: string, progressFn: ProgressFn) {
- super(serverApi, resourceType, progressFn);
+ constructor(serverApi: AxiosInstance, resourceType: string, actions: ApiActions) {
+ super(serverApi, resourceType, actions);
}
trash(uuid: string): Promise<T> {
return CommonResourceService.defaultResponse(
this.serverApi
.post(this.resourceType + `${uuid}/trash`),
- this.progressFn
+ this.actions
);
}
.post(this.resourceType + `${uuid}/untrash`, {
params: CommonResourceService.mapKeys(_.snakeCase)(params)
}),
- this.progressFn
+ this.actions
);
}
}
import { CommonResourceService } from "~/services/common-service/common-resource-service";
import { AxiosInstance } from "axios";
import { ContainerRequestResource } from '~/models/container-request';
-import { ProgressFn } from "~/services/api/api-progress";
+import { ApiActions } from "~/services/api/api-actions";
export class ContainerRequestService extends CommonResourceService<ContainerRequestResource> {
- constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
- super(serverApi, "container_requests", progressFn);
+ constructor(serverApi: AxiosInstance, actions: ApiActions) {
+ super(serverApi, "container_requests", actions);
}
}
import { CommonResourceService } from "~/services/common-service/common-resource-service";
import { AxiosInstance } from "axios";
import { ContainerResource } from '~/models/container';
-import { ProgressFn } from "~/services/api/api-progress";
+import { ApiActions } from "~/services/api/api-actions";
export class ContainerService extends CommonResourceService<ContainerResource> {
- constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
- super(serverApi, "containers", progressFn);
+ constructor(serverApi: AxiosInstance, actions: ApiActions) {
+ super(serverApi, "containers", actions);
}
}
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { GroupsService } from "./groups-service";
+import { ApiActions } from "~/services/api/api-actions";
describe("GroupsService", () => {
const axiosMock = new MockAdapter(axios);
+ const actions: ApiActions = {
+ progressFn: (id: string, working: boolean) => {},
+ errorFn: (id: string, message: string) => {}
+ };
+
beforeEach(() => {
axiosMock.reset();
});
items_available: 20
});
- const groupsService = new GroupsService(axios, (id, working) => {});
+ const groupsService = new GroupsService(axios, actions);
const resource = await groupsService.contents("1", { limit: 10, offset: 1 });
expect(resource).toEqual({
kind: "kind",
import { ProcessResource } from "~/models/process";
import { TrashableResource } from "~/models/resource";
import { TrashableResourceService } from "~/services/common-service/trashable-resource-service";
-import { ProgressFn } from "~/services/api/api-progress";
+import { ApiActions } from "~/services/api/api-actions";
export interface ContentsArguments {
limit?: number;
export class GroupsService<T extends TrashableResource = TrashableResource> extends TrashableResourceService<T> {
- constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
- super(serverApi, "groups", progressFn);
+ constructor(serverApi: AxiosInstance, actions: ApiActions) {
+ super(serverApi, "groups", actions);
}
contents(uuid: string, args: ContentsArguments = {}): Promise<ListResults<GroupContentsResource>> {
.get(this.resourceType + `${uuid}/contents`, {
params: CommonResourceService.mapKeys(_.snakeCase)(params)
}),
- this.progressFn
+ this.actions
);
}
}
import { CommonResourceService } from "~/services/common-service/common-resource-service";\r
import { AxiosInstance } from "axios";\r
import { KeepResource } from "~/models/keep";\r
-import { ProgressFn } from "~/services/api/api-progress";\r
+import { ApiActions } from "~/services/api/api-actions";\r
\r
export class KeepService extends CommonResourceService<KeepResource> {\r
- constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {\r
- super(serverApi, "keep_services", progressFn);\r
+ constructor(serverApi: AxiosInstance, actions: ApiActions) {\r
+ super(serverApi, "keep_services", actions);\r
}\r
}\r
import { CommonResourceService } from "~/services/common-service/common-resource-service";
import { LinkResource } from "~/models/link";
import { AxiosInstance } from "axios";
-import { ProgressFn } from "~/services/api/api-progress";
+import { ApiActions } from "~/services/api/api-actions";
export class LinkService extends CommonResourceService<LinkResource> {
- constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
- super(serverApi, "links", progressFn);
+ constructor(serverApi: AxiosInstance, actions: ApiActions) {
+ super(serverApi, "links", actions);
}
}
import { AxiosInstance } from "axios";
import { LogResource } from '~/models/log';
import { CommonResourceService } from "~/services/common-service/common-resource-service";
-import { ProgressFn } from "~/services/api/api-progress";
+import { ApiActions } from "~/services/api/api-actions";
export class LogService extends CommonResourceService<LogResource> {
- constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
- super(serverApi, "logs", progressFn);
+ constructor(serverApi: AxiosInstance, actions: ApiActions) {
+ super(serverApi, "logs", actions);
}
}
import axios from "axios";
import { ProjectService } from "./project-service";
import { FilterBuilder } from "~/services/api/filter-builder";
+import { ApiActions } from "~/services/api/api-actions";
describe("CommonResourceService", () => {
const axiosInstance = axios.create();
+ const actions: ApiActions = {
+ progressFn: (id: string, working: boolean) => {},
+ errorFn: (id: string, message: string) => {}
+ };
it(`#create has groupClass set to "project"`, async () => {
axiosInstance.post = jest.fn(() => Promise.resolve({ data: {} }));
- const projectService = new ProjectService(axiosInstance, (id, working) => {});
+ const projectService = new ProjectService(axiosInstance, actions);
const resource = await projectService.create({ name: "nameValue" });
expect(axiosInstance.post).toHaveBeenCalledWith("/groups/", {
name: "nameValue",
it("#list has groupClass filter set by default", async () => {
axiosInstance.get = jest.fn(() => Promise.resolve({ data: {} }));
- const projectService = new ProjectService(axiosInstance, (id, working) => {});
+ const projectService = new ProjectService(axiosInstance, actions);
const resource = await projectService.list();
expect(axiosInstance.get).toHaveBeenCalledWith("/groups/", {
params: {
import { ContainerRequestService } from './container-request-service/container-request-service';
import { ContainerService } from './container-service/container-service';
import { LogService } from './log-service/log-service';
+import { ApiActions } from "~/services/api/api-actions";
export type ServiceRepository = ReturnType<typeof createServices>;
-export const createServices = (config: Config, progressFn: (id: string, working: boolean) => void) => {
+export const createServices = (config: Config, actions: ApiActions) => {
const apiClient = Axios.create();
apiClient.defaults.baseURL = config.baseUrl;
const webdavClient = new WebDAV();
webdavClient.defaults.baseURL = config.keepWebServiceUrl;
- const containerRequestService = new ContainerRequestService(apiClient, progressFn);
- const containerService = new ContainerService(apiClient, progressFn);
- const groupsService = new GroupsService(apiClient, progressFn);
- const keepService = new KeepService(apiClient, progressFn);
- const linkService = new LinkService(apiClient, progressFn);
- const logService = new LogService(apiClient, progressFn);
- const projectService = new ProjectService(apiClient, progressFn);
- const userService = new UserService(apiClient, progressFn);
+ const containerRequestService = new ContainerRequestService(apiClient, actions);
+ const containerService = new ContainerService(apiClient, actions);
+ const groupsService = new GroupsService(apiClient, actions);
+ const keepService = new KeepService(apiClient, actions);
+ const linkService = new LinkService(apiClient, actions);
+ const logService = new LogService(apiClient, actions);
+ const projectService = new ProjectService(apiClient, actions);
+ const userService = new UserService(apiClient, actions);
const ancestorsService = new AncestorService(groupsService, userService);
- const authService = new AuthService(apiClient, config.rootUrl, progressFn);
- const collectionService = new CollectionService(apiClient, webdavClient, authService, progressFn);
+ const authService = new AuthService(apiClient, config.rootUrl, actions);
+ const collectionService = new CollectionService(apiClient, webdavClient, authService, actions);
const collectionFilesService = new CollectionFilesService(collectionService);
const favoriteService = new FavoriteService(linkService, groupsService);
const tagService = new TagService(linkService);
import { AxiosInstance } from "axios";
import { CommonResourceService } from "~/services/common-service/common-resource-service";
import { UserResource } from "~/models/user";
-import { ProgressFn } from "~/services/api/api-progress";
+import { ApiActions } from "~/services/api/api-actions";
export class UserService extends CommonResourceService<UserResource> {
- constructor(serverApi: AxiosInstance, progressFn: ProgressFn) {
- super(serverApi, "users", progressFn);
+ constructor(serverApi: AxiosInstance, actions: ApiActions) {
+ super(serverApi, "users", actions);
}
}
import { configureStore, RootStore } from "../store";
import createBrowserHistory from "history/createBrowserHistory";
import { mockConfig } from '~/common/config';
+import { ApiActions } from "~/services/api/api-actions";
describe('auth-actions', () => {
let reducer: (state: AuthState | undefined, action: AuthAction) => any;
let store: RootStore;
- const progressFn = (id: string, working: boolean) => {};
+ const actions: ApiActions = {
+ progressFn: (id: string, working: boolean) => {},
+ errorFn: (id: string, message: string) => {}
+ };
beforeEach(() => {
- store = configureStore(createBrowserHistory(), createServices(mockConfig({}), progressFn));
+ store = configureStore(createBrowserHistory(), createServices(mockConfig({}), actions));
localStorage.clear();
- reducer = authReducer(createServices(mockConfig({}), progressFn));
+ reducer = authReducer(createServices(mockConfig({}), actions));
});
it('should initialise state with user and api token from local storage', () => {
import 'jest-localstorage-mock';
import { createServices } from "~/services/services";
import { mockConfig } from '~/common/config';
+import { ApiActions } from "~/services/api/api-actions";
describe('auth-reducer', () => {
let reducer: (state: AuthState | undefined, action: AuthAction) => any;
- const progressFn = (id: string, working: boolean) => {};
+ const actions: ApiActions = {
+ progressFn: (id: string, working: boolean) => {},
+ errorFn: (id: string, message: string) => {}
+ };
beforeAll(() => {
localStorage.clear();
- reducer = authReducer(createServices(mockConfig({}), progressFn));
+ reducer = authReducer(createServices(mockConfig({}), actions));
});
it('should correctly initialise state', () => {
import { unionize, ofType, UnionOf } from "~/common/unionize";
+export enum SnackbarKind {
+ SUCCESS,
+ ERROR,
+ INFO,
+ WARNING
+}
+
export const snackbarActions = unionize({
- OPEN_SNACKBAR: ofType<{message: string; hideDuration?: number}>(),
+ OPEN_SNACKBAR: ofType<{message: string; hideDuration?: number, kind?: SnackbarKind}>(),
CLOSE_SNACKBAR: ofType<{}>()
});
//
// SPDX-License-Identifier: AGPL-3.0
-import { SnackbarAction, snackbarActions } from "./snackbar-actions";
+import { SnackbarAction, snackbarActions, SnackbarKind } from "./snackbar-actions";
export interface SnackbarState {
message: string;
open: boolean;
hideDuration: number;
+ kind: SnackbarKind;
}
const DEFAULT_HIDE_DURATION = 3000;
const initialState: SnackbarState = {
message: "",
open: false,
- hideDuration: DEFAULT_HIDE_DURATION
+ hideDuration: DEFAULT_HIDE_DURATION,
+ kind: SnackbarKind.INFO
};
export const snackbarReducer = (state = initialState, action: SnackbarAction) => {
import { RootState } from "~/store/store";
import MaterialSnackbar, { SnackbarProps } from "@material-ui/core/Snackbar";
import { Dispatch } from "redux";
-import { snackbarActions } from "~/store/snackbar/snackbar-actions";
+import { snackbarActions, SnackbarKind } from "~/store/snackbar/snackbar-actions";
+import IconButton from '@material-ui/core/IconButton';
+import SnackbarContent from '@material-ui/core/SnackbarContent';
+import WarningIcon from '@material-ui/icons/Warning';
+import CheckCircleIcon from '@material-ui/icons/CheckCircle';
+import ErrorIcon from '@material-ui/icons/Error';
+import InfoIcon from '@material-ui/icons/Info';
+import CloseIcon from '@material-ui/icons/Close';
+import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
+import { ArvadosTheme } from "~/common/custom-theme";
+import { amber, green } from "@material-ui/core/colors";
+import * as classNames from 'classnames';
const mapStateToProps = (state: RootState): SnackbarProps => ({
- anchorOrigin: { vertical: "bottom", horizontal: "center" },
+ anchorOrigin: { vertical: "bottom", horizontal: "right" },
open: state.snackbar.open,
message: <span>{state.snackbar.message}</span>,
- autoHideDuration: state.snackbar.hideDuration
+ autoHideDuration: state.snackbar.hideDuration,
});
const mapDispatchToProps = (dispatch: Dispatch): Pick<SnackbarProps, "onClose"> => ({
}
});
-export const Snackbar = connect(mapStateToProps, mapDispatchToProps)(MaterialSnackbar);
+const ArvadosSnackbar = (props: any) => <MaterialSnackbar {...props}>
+ <ArvadosSnackbarContent {...props}/>
+</MaterialSnackbar>;
+
+type CssRules = "success" | "error" | "info" | "warning" | "icon" | "iconVariant" | "message";
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ success: {
+ backgroundColor: green[600],
+ },
+ error: {
+ backgroundColor: theme.palette.error.dark,
+ },
+ info: {
+ backgroundColor: theme.palette.primary.dark,
+ },
+ warning: {
+ backgroundColor: amber[700],
+ },
+ icon: {
+ fontSize: 20,
+ },
+ iconVariant: {
+ opacity: 0.9,
+ marginRight: theme.spacing.unit,
+ },
+ message: {
+ display: 'flex',
+ alignItems: 'center',
+ },
+});
+
+interface ArvadosSnackbarProps {
+ kind: SnackbarKind;
+}
+
+const ArvadosSnackbarContent = (props: SnackbarProps & ArvadosSnackbarProps & WithStyles<CssRules>) => {
+ const { classes, className, message, onClose, kind, ...other } = props;
+
+ let Icon = InfoIcon;
+ let cssClass;
+ switch (kind) {
+ case SnackbarKind.INFO:
+ Icon = InfoIcon;
+ cssClass = classes.info;
+ break;
+ case SnackbarKind.WARNING:
+ Icon = WarningIcon;
+ cssClass = classes.warning;
+ break;
+ case SnackbarKind.SUCCESS:
+ Icon = CheckCircleIcon;
+ cssClass = classes.success;
+ break;
+ case SnackbarKind.ERROR:
+ Icon = ErrorIcon;
+ cssClass = classes.error;
+ break;
+ }
+
+ return (
+ <SnackbarContent
+ className={classNames(cssClass, className)}
+ aria-describedby="client-snackbar"
+ message={
+ <span id="client-snackbar" className={classes.message}>
+ <Icon className={classNames(classes.icon, classes.iconVariant)}/>
+ {message}
+ </span>
+ }
+ action={
+ <IconButton
+ key="close"
+ aria-label="Close"
+ color="inherit"
+ onClick={e => {
+ if (onClose) {
+ onClose(e, '');
+ }
+ }}>
+ <CloseIcon className={classes.icon}/>
+ </IconButton>
+ }
+ />
+ );
+};
+
+export const Snackbar = connect(mapStateToProps, mapDispatchToProps)(
+ withStyles(styles)(ArvadosSnackbar)
+);