mirror of
https://github.com/usatiuk/writer.git
synced 2025-10-29 00:17:48 +01:00
update documents
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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)),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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),
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user