update documents

This commit is contained in:
2019-02-10 16:35:00 +03:00
parent 67813da2e7
commit 1aa331d6db
6 changed files with 195 additions and 17 deletions

View File

@@ -55,14 +55,19 @@
margin-right: auto;
flex-shrink: 0;
}
input {
font-weight: 600;
font-size: 2rem;
height: 3rem;
flex-grow: 1;
}
.buttons {
width: 1rem;
height: 1rem;
margin-left: auto;
margin-right: 0;
display: flex;
justify-content: end;
}
margin-bottom: 1rem;
}
textarea {
overflow: auto;

View File

@@ -1,6 +1,6 @@
import "./Docs.scss";
import { Button, H1, TextArea } from "@blueprintjs/core";
import { Button, Classes, TextArea } from "@blueprintjs/core";
import * as React from "react";
import { connect } from "react-redux";
import { RouteComponentProps, withRouter } from "react-router";
@@ -12,6 +12,7 @@ import {
deleteDocCancel,
deleteDocStart,
fetchDocsStart,
updateDocStart,
} from "~redux/docs/actions";
import { IAppState } from "~redux/reducers";
@@ -24,27 +25,54 @@ export interface IDocumentEditComponentProps extends RouteComponentProps {
fetchDocs: () => void;
deleteDoc: (id: number) => void;
cancelDelete: () => void;
updateDoc: (id: number, name: string, content: string) => void;
}
export interface IDocumentEditComponentState {
loaded: boolean;
id: number | null;
name: string | null;
content: string | null;
}
const defaultDocumentEditComponentState: IDocumentEditComponentState = {
loaded: false,
id: null,
name: null,
content: null,
};
export class DocumentEditComponent extends React.PureComponent<
IDocumentEditComponentProps,
null
IDocumentEditComponentState
> {
constructor(props: IDocumentEditComponentProps) {
super(props);
this.state = defaultDocumentEditComponentState;
this.handleInputChange = this.handleInputChange.bind(this);
}
public render() {
const { id } = this.props.match.params as any;
if (this.props.allDocs && this.props.allDocs[id]) {
const doc = this.props.allDocs[id];
if (this.state.loaded) {
return (
<div className="document">
<div className="documentHeader">
<H1 contentEditable={true}>{doc.name}</H1>
<input
className={Classes.INPUT}
onChange={this.handleInputChange}
name="name"
value={this.state.name}
/>
<div className="buttons">
<Button
icon="trash"
minimal={true}
onClick={() => {
this.props.history.push(`/`);
this.props.deleteDoc(id);
this.props.deleteDoc(this.state.id);
AppToaster.show({
message: "Document deleted!",
intent: "danger",
@@ -60,20 +88,75 @@ export class DocumentEditComponent extends React.PureComponent<
<Button
icon="tick"
minimal={true}
onClick={() =>
this.props.history.push(`/docs/${id}`)
}
onClick={() => {
this.update();
this.props.history.push(
`/docs/${this.state.id}`,
);
}}
/>
</div>
</div>
<TextArea>{doc.content}</TextArea>
<TextArea
onChange={this.handleInputChange}
name="content"
value={this.state.content}
/>
</div>
);
} else {
this.props.fetchDocs();
return this.props.spinner && <LoadingStub />;
}
}
public update() {
this.props.updateDoc(
this.state.id,
this.state.name,
this.state.content,
);
}
public handleInputChange(
event:
| React.FormEvent<HTMLInputElement>
| React.FormEvent<HTMLTextAreaElement>,
) {
const target = event.currentTarget;
const value = target.value;
const name = target.name;
this.setState({
[name]: value,
} as any);
}
public componentDidUpdate() {
this.tryLoad();
}
private tryLoad() {
if (!this.props.allDocs && !this.props.fetching) {
this.props.fetchDocs();
} else {
const { id } = this.props.match.params as any;
if (
!this.state.loaded &&
this.props.allDocs &&
this.props.allDocs[id]
) {
const doc = this.props.allDocs[id];
this.setState({
...this.state,
loaded: true,
id: doc.id,
name: doc.name,
content: doc.content,
});
}
}
}
}
function mapStateToProps(state: IAppState) {
@@ -89,6 +172,8 @@ function mapDispatchToProps(dispatch: Dispatch) {
fetchDocs: () => dispatch(fetchDocsStart()),
cancelDelete: () => dispatch(deleteDocCancel()),
deleteDoc: (id: number) => dispatch(deleteDocStart(id)),
updateDoc: (id: number, name: string, content: string) =>
dispatch(updateDocStart(id, name, content)),
};
}

View File

@@ -63,7 +63,7 @@ export class HomeComponent extends React.PureComponent<IHomeProps> {
this.props.user && (
<div
id="mainContainer"
className={this.props.darkMode && Classes.DARK}
className={this.props.darkMode ? Classes.DARK : undefined}
>
<Navbar fixedToTop={true}>
<Navbar.Group align={Alignment.LEFT}>

View File

@@ -16,6 +16,10 @@ export enum DocsTypes {
DOC_DELETE_SUCCESS = "DOC_DELETE_SUCCESS",
DOC_DELETE_CANCEL = "DOC_DELETE_CANCEL",
DOC_UPDATE_START = "DOC_UPDATE_START",
DOC_UPDATE_FAIL = "DOC_UPDATE_FAIL",
DOC_UPDATE_SUCCESS = "DOC_UPDATE_SUCCESS",
DOCS_SHOW_SPINNER = "DOCS_SHOW_SPINNER",
}
@@ -136,6 +140,43 @@ export function deleteDocCancel(): IDocDeleteCancelAction {
return { type: DocsTypes.DOC_DELETE_CANCEL };
}
export interface IDocUpdateStartAction extends Action {
type: DocsTypes.DOC_UPDATE_START;
id: number;
name: string;
content: string;
}
export interface IDocUpdateFailAction extends Action {
type: DocsTypes.DOC_UPDATE_FAIL;
payload: {
error: string;
};
}
export interface IDocUpdateSuccessAction extends Action {
type: DocsTypes.DOC_UPDATE_SUCCESS;
payload: {
doc: IDocumentJSON;
};
}
export function updateDocStart(
id: number,
name: string,
content: string,
): IDocUpdateStartAction {
return { type: DocsTypes.DOC_UPDATE_START, id, name, content };
}
export function updateDocFail(error: string): IDocUpdateFailAction {
return { type: DocsTypes.DOC_UPDATE_FAIL, payload: { error } };
}
export function updateDocSuccess(doc: IDocumentJSON): IDocUpdateSuccessAction {
return { type: DocsTypes.DOC_UPDATE_SUCCESS, payload: { doc } };
}
export type DocsAction =
| IDocsFetchStartAction
| IDocsFetchFailAction
@@ -148,4 +189,7 @@ export type DocsAction =
| IDocDeleteFailAction
| IDocDeleteStartAction
| IDocDeleteSuccessAction
| IDocDeleteCancelAction;
| IDocDeleteCancelAction
| IDocUpdateFailAction
| IDocUpdateStartAction
| IDocUpdateSuccessAction;

View File

@@ -50,6 +50,12 @@ export const docsReducer: Reducer<IDocsState, DocsAction> = (
delete all[action.payload.id];
return { ...state, all };
}
case DocsTypes.DOC_UPDATE_SUCCESS: {
const all = { ...state.all };
const doc = action.payload.doc;
all[doc.id] = doc;
return { ...state, all };
}
case DocsTypes.DOCS_FETCH_FAIL:
return { ...defaultDocsState, ...action.payload };
default:

View File

@@ -9,7 +9,12 @@ import {
take,
takeLatest,
} from "redux-saga/effects";
import { createNewDoc, deleteDoc, fetchAllDocs } from "~redux/api/docs";
import {
createNewDoc,
deleteDoc,
fetchAllDocs,
patchDoc,
} from "~redux/api/docs";
import {
deleteDocFail,
@@ -20,9 +25,12 @@ import {
IDocDeleteStartAction,
IDocNewStartAction,
IDocsFetchStartAction,
IDocUpdateStartAction,
newDocFail,
newDocSuccess,
showDocsSpinner,
updateDocFail,
updateDocSuccess,
} from "./actions";
function* startSpinner() {
@@ -119,10 +127,40 @@ function* docDeleteStart(action: IDocDeleteStartAction) {
}
}
function* docUpdateStart(action: IDocUpdateStartAction) {
try {
const spinner = yield fork(startSpinner);
const { response, timeout } = yield race({
response: call(patchDoc, action.id, action.name, action.content),
timeout: delay(10000),
});
yield cancel(spinner);
if (timeout) {
yield put(updateDocFail("Timeout"));
return;
}
if (response) {
if (response.data == null) {
yield put(updateDocFail(response.error));
} else {
const updDoc = response.data;
yield put(updateDocSuccess(updDoc));
}
}
} catch (e) {
yield put(updateDocFail("Internal error"));
}
}
export function* docsSaga() {
yield all([
takeLatest(DocsTypes.DOCS_FETCH_START, docsFetchStart),
takeLatest(DocsTypes.DOC_NEW_START, docNewStart),
takeLatest(DocsTypes.DOC_DELETE_START, docDeleteStart),
takeLatest(DocsTypes.DOC_UPDATE_START, docUpdateStart),
]);
}