add a document upload indicator

This commit is contained in:
2019-08-30 18:40:21 +02:00
parent a1a5084b96
commit 1ee044806d
5 changed files with 83 additions and 2 deletions

View File

@@ -10,6 +10,9 @@ module.exports = {
...pathsToModuleNameMapper(compilerOptions.paths, { ...pathsToModuleNameMapper(compilerOptions.paths, {
prefix: "<rootDir>/", prefix: "<rootDir>/",
}), }),
"react-spring/renderprops":
"<rootDir>/node_modules/react-spring/renderprops.cjs",
"react-spring": "<rootDir>/node_modules/react-spring/web.cjs",
}, },
setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"], setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],
}; };

View File

@@ -24,6 +24,10 @@
.bp3-navbar { .bp3-navbar {
transition: 0.3s; transition: 0.3s;
} }
#uploadingStatusButton {
width: 40px;
}
} }
#mainContainer.bp3-dark { #mainContainer.bp3-dark {

View File

@@ -6,9 +6,11 @@ import {
Button, Button,
Classes, Classes,
IBreadcrumbProps, IBreadcrumbProps,
Icon,
Menu, Menu,
Navbar, Navbar,
Popover, Popover,
Spinner,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import * as React from "react"; import * as React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -24,10 +26,13 @@ import { toggleDarkMode } from "~redux/localSettings/actions";
import { IAppState } from "~redux/reducers"; import { IAppState } from "~redux/reducers";
import { logoutUser } from "~redux/user/actions"; import { logoutUser } from "~redux/user/actions";
interface IHomeProps extends RouteComponentProps { export interface IHomeProps extends RouteComponentProps {
allDocs: { [key: number]: IDocumentJSON }; allDocs: { [key: number]: IDocumentJSON };
user: IUserJSON | null; user: IUserJSON | null;
fetching: boolean;
uploading: boolean;
darkMode: boolean; darkMode: boolean;
logout: () => void; logout: () => void;
@@ -72,6 +77,13 @@ export class HomeComponent extends React.PureComponent<IHomeProps> {
<Breadcrumbs items={breadcrumbs} /> <Breadcrumbs items={breadcrumbs} />
</Navbar.Group> </Navbar.Group>
<Navbar.Group align={Alignment.RIGHT}> <Navbar.Group align={Alignment.RIGHT}>
<Button id="uploadingStatusButton">
{this.props.uploading ? (
<Spinner size={20} />
) : (
<Icon icon="saved" />
)}
</Button>
<Popover <Popover
target={ target={
<Button id="userButton"> <Button id="userButton">
@@ -161,6 +173,8 @@ function mapStateToProps(state: IAppState) {
allDocs: state.docs.all, allDocs: state.docs.all,
user: state.user.user, user: state.user.user,
darkMode: state.localSettings.darkMode, darkMode: state.localSettings.darkMode,
fetching: state.docs.fetching,
uploading: state.docs.uploading,
}; };
} }

View File

@@ -0,0 +1,52 @@
import { shallow } from "enzyme";
import * as React from "react";
import { Icon, Spinner } from "@blueprintjs/core";
import { HomeComponent, IHomeProps } from "../Home";
const defaultHomeProps: IHomeProps = {
allDocs: {},
user: { id: 1, username: "test" },
fetching: false,
uploading: false,
darkMode: false,
logout: jest.fn(),
dispatchToggleDarkMode: jest.fn(),
history: { location: { pathname: "/" } } as any,
location: { pathname: "/" } as any,
match: {
params: {
id: null,
},
} as any,
};
describe("<Home />", () => {
it("should show a spinner when uploading", () => {
const wrapper = shallow(
<HomeComponent {...defaultHomeProps} uploading={true} />,
);
expect(
wrapper.find("#uploadingStatusButton").find(Spinner),
).toHaveLength(1);
expect(wrapper.find("#uploadingStatusButton").find(Icon)).toHaveLength(
0,
);
});
it("should show a saved icon when not uploading", () => {
const wrapper = shallow(
<HomeComponent {...defaultHomeProps} uploading={false} />,
);
expect(
wrapper.find("#uploadingStatusButton").find(Spinner),
).toHaveLength(0);
expect(wrapper.find("#uploadingStatusButton").find(Icon)).toHaveLength(
1,
);
});
});

View File

@@ -7,6 +7,7 @@ import { DocsAction, DocsTypes } from "./actions";
export interface IDocsState { export interface IDocsState {
all: { [key: number]: IDocumentJSON }; all: { [key: number]: IDocumentJSON };
fetching: boolean; fetching: boolean;
uploading: boolean;
error: string | null; error: string | null;
spinner: boolean; spinner: boolean;
@@ -18,6 +19,7 @@ export interface IDocsState {
const defaultDocsState: IDocsState = { const defaultDocsState: IDocsState = {
all: null, all: null,
fetching: false, fetching: false,
uploading: false,
error: null, error: null,
spinner: false, spinner: false,
newDocumentID: null, newDocumentID: null,
@@ -64,11 +66,17 @@ export const docsReducer: Reducer<IDocsState, DocsAction> = (
all[deletedDocument.id] = deletedDocument; all[deletedDocument.id] = deletedDocument;
return { ...state, deletedDocument: null, all }; return { ...state, deletedDocument: null, all };
} }
case DocsTypes.DOC_UPDATE_START: {
return { ...state, uploading: true };
}
case DocsTypes.DOC_UPDATE_FAIL: {
return { ...state, uploading: false };
}
case DocsTypes.DOC_UPDATE_SUCCESS: { case DocsTypes.DOC_UPDATE_SUCCESS: {
const all = { ...state.all }; const all = { ...state.all };
const doc = action.payload.doc; const doc = action.payload.doc;
all[doc.id] = doc; all[doc.id] = doc;
return { ...state, all }; return { ...state, all, uploading: false };
} }
case DocsTypes.DOCS_FETCH_FAIL: case DocsTypes.DOCS_FETCH_FAIL:
return { ...defaultDocsState, ...action.payload }; return { ...defaultDocsState, ...action.payload };