1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

tried to make upload sagas

This commit is contained in:
muerwre 2019-08-07 15:09:28 +07:00
parent caf85c104f
commit 3872ff5903
9 changed files with 180 additions and 80 deletions

View file

@ -44,6 +44,8 @@ module.exports = {
'react-hooks/rules-of-hooks': 'error', 'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn', 'react-hooks/exhaustive-deps': 'warn',
'no-nested-ternary': 1, 'no-nested-ternary': 1,
'arrow-parens': 0,
'import/prefer-default-export': 0,
}, },
globals: { globals: {
document: false, document: false,

View file

@ -1,51 +1,69 @@
import React, {FC, useCallback, useEffect} from 'react'; import React, { FC, useCallback, useEffect, useState } from 'react';
import { INode } from '~/redux/types'; import { INode, IFileWithUUID } from '~/redux/types';
import * as styles from './styles.scss'; import * as styles from './styles.scss';
import uuid from 'uuid4';
interface IProps { interface IProps {
data: INode, data: INode;
setData: (val: INode) => void; setData: (val: INode) => void;
onUpload: (val: File[]) => void; onUpload: (val: IFileWithUUID[]) => void;
} }
const ImageEditor: FC<IProps> = ({ const ImageEditor: FC<IProps> = ({ data, setData, onUpload }) => {
data,
setData,
onUpload,
}) => {
const eventPreventer = useCallback(event => event.preventDefault(), []); const eventPreventer = useCallback(event => event.preventDefault(), []);
const [temp, setTemp] = useState([]);
const onDrop = useCallback(event => { const onDrop = useCallback(
event.preventDefault(); (event: DragEvent) => {
event.preventDefault();
if (!event.dataTransfer || !event.dataTransfer.files || !event.dataTransfer.files.length) return; if (!event.dataTransfer || !event.dataTransfer.files || !event.dataTransfer.files.length)
return;
onUpload(event.dataTransfer.files); const files: IFileWithUUID[] = Array.from(event.dataTransfer.files).map(
}, [onUpload]); (file: File): IFileWithUUID => ({
file,
temp_id: uuid(),
subject: 'editor',
})
);
const onInputChange = useCallback(event => { onUpload(files);
event.preventDefault(); },
[onUpload]
);
if (!event.target.files || !event.target.files.length) return; const onInputChange = useCallback(
event => {
event.preventDefault();
onUpload(event.target.files); if (!event.target.files || !event.target.files.length) return;
}, [onUpload]);
const files: IFileWithUUID[] = Array.from(event.target.files).map(
(file: File): IFileWithUUID => ({
file,
temp_id: uuid(),
subject: 'editor',
})
);
onUpload(files);
},
[onUpload]
);
useEffect(() => { useEffect(() => {
window.addEventListener("dragover", eventPreventer,false); window.addEventListener('dragover', eventPreventer, false);
window.addEventListener("drop",eventPreventer,false); window.addEventListener('drop', eventPreventer, false);
return () => { return () => {
window.removeEventListener("dragover", eventPreventer,false); window.removeEventListener('dragover', eventPreventer, false);
window.removeEventListener("drop",eventPreventer,false); window.removeEventListener('drop', eventPreventer, false);
} };
}, [eventPreventer]); }, [eventPreventer]);
return ( return (
<form <form className={styles.uploads} onDrop={onDrop}>
className={styles.uploads}
onDrop={onDrop}
>
<div>{data.type}</div> <div>{data.type}</div>
<input type="file" onChange={onInputChange} accept="image/*" multiple /> <input type="file" onChange={onInputChange} accept="image/*" multiple />
</form> </form>

View file

@ -12,7 +12,7 @@ import { selectNode } from '~/redux/node/selectors';
import { ImageEditor } from '~/components/editors/ImageEditor'; import { ImageEditor } from '~/components/editors/ImageEditor';
import { EditorPanel } from '~/components/editors/EditorPanel'; import { EditorPanel } from '~/components/editors/EditorPanel';
import * as UPLOAD_ACTIONS from '~/redux/uploads/actions'; import * as UPLOAD_ACTIONS from '~/redux/uploads/actions';
import {uploadUploadFiles} from "~/redux/uploads/actions"; import { IFileWithUUID } from '~/redux/types';
const mapStateToProps = selectNode; const mapStateToProps = selectNode;
const mapDispatchToProps = { const mapDispatchToProps = {
@ -24,21 +24,23 @@ type IProps = IDialogProps & ReturnType<typeof mapStateToProps> & typeof mapDisp
const EditorDialogUnconnected: FC<IProps> = ({ onRequestClose, editor, uploadUploadFiles }) => { const EditorDialogUnconnected: FC<IProps> = ({ onRequestClose, editor, uploadUploadFiles }) => {
const [data, setData] = useState(editor); const [data, setData] = useState(editor);
const setTitle = useCallback(title => { const setTitle = useCallback(
setData({ ...data, title }); title => {
}, [setData, data]); setData({ ...data, title });
},
[setData, data]
);
const onUpload = useCallback((files: File[]) => { const onUpload = useCallback(
uploadUploadFiles(files, 'editor'); (files: IFileWithUUID[]) => {
}, [uploadUploadFiles]); uploadUploadFiles(files);
},
[uploadUploadFiles]
);
const buttons = ( const buttons = (
<Padder style={{ position: 'relative' }}> <Padder style={{ position: 'relative' }}>
<EditorPanel <EditorPanel data={data} setData={setData} onUpload={onUpload} />
data={data}
setData={setData}
onUpload={onUpload}
/>
<Group horizontal> <Group horizontal>
<InputText title="Название" value={data.title} handler={setTitle} /> <InputText title="Название" value={data.title} handler={setTitle} />
@ -53,16 +55,15 @@ const EditorDialogUnconnected: FC<IProps> = ({ onRequestClose, editor, uploadUpl
return ( return (
<ScrollDialog buttons={buttons} width={860} onClose={onRequestClose}> <ScrollDialog buttons={buttons} width={860} onClose={onRequestClose}>
<div className={styles.editor}> <div className={styles.editor}>
<ImageEditor <ImageEditor data={data} setData={setData} onUpload={onUpload} />
data={data}
setData={setData}
onUpload={onUpload}
/>
</div> </div>
</ScrollDialog> </ScrollDialog>
); );
}; };
const EditorDialog = connect(mapStateToProps, mapDispatchToProps)(EditorDialogUnconnected) const EditorDialog = connect(
mapStateToProps,
mapDispatchToProps
)(EditorDialogUnconnected);
export { EditorDialog }; export { EditorDialog };

View file

@ -67,6 +67,7 @@ export interface IFile {
export interface IFileWithUUID { export interface IFileWithUUID {
temp_id?: UUID; temp_id?: UUID;
file: File; file: File;
subject: string;
} }
export interface IBlock { export interface IBlock {

View file

@ -1,7 +1,25 @@
import {UPLOAD_ACTIONS} from "~/redux/uploads/constants"; import { UPLOAD_ACTIONS } from "~/redux/uploads/constants";
import { IFileWithUUID, UUID } from "../types";
import { IUploadStatus } from "./reducer";
export const uploadUploadFiles = (files: File[], subject: string) => ({ export const uploadUploadFiles = (files: IFileWithUUID[]) => ({
files, files,
subject,
type: UPLOAD_ACTIONS.UPLOAD_FILES, type: UPLOAD_ACTIONS.UPLOAD_FILES,
}); });
export const uploadAddStatus = (temp_id: UUID, status?: Partial<IUploadStatus>) => ({
temp_id,
status,
type: UPLOAD_ACTIONS.ADD_STATUS,
});
export const uploadSetStatus = (temp_id: UUID, status?: Partial<IUploadStatus>) => ({
temp_id,
status,
type: UPLOAD_ACTIONS.SET_STATUS,
});
export const uploadDropStatus = (temp_id: UUID) => ({
temp_id,
type: UPLOAD_ACTIONS.DROP_STATUS,
});

View file

@ -1,10 +1,15 @@
import { IFile } from "~/redux/types"; import { IFile } from "~/redux/types";
import { IUploadState, IUploadStatus } from "./reducer";
const prefix = 'UPLOAD.'; const prefix = 'UPLOAD.';
export const UPLOAD_ACTIONS = { export const UPLOAD_ACTIONS = {
UPLOAD_FILES: `${prefix}UPLOAD_FILES`, UPLOAD_FILES: `${prefix}UPLOAD_FILES`,
UPLOAD_CANCEL: `${prefix}UPLOAD_CANCEL`, UPLOAD_CANCEL: `${prefix}UPLOAD_CANCEL`,
ADD_STATUS: `${prefix}ADD_STATUS`,
DROP_STATUS: `${prefix}DROP_STATUS`,
SET_STATUS: `${prefix}SET_STATUS`,
}; };
export const EMPTY_FILE: IFile = { export const EMPTY_FILE: IFile = {
@ -19,3 +24,14 @@ export const EMPTY_FILE: IFile = {
type: 'image', type: 'image',
mime: 'image/jpeg', mime: 'image/jpeg',
}; };
export const EMPTY_UPLOAD_STATUS: IUploadStatus = {
is_uploading: false,
preview: null,
error: null,
uuid: null,
url: null,
progress: 0,
thumbnail_url: null,
type: null,
}

View file

@ -1,3 +1,38 @@
export const UPLOAD_HANDLERS = { import assocPath from 'ramda/es/assocPath';
import omit from 'ramda/es/omit';
} import { UPLOAD_ACTIONS, EMPTY_UPLOAD_STATUS } from './constants';
import { uploadAddStatus, uploadDropStatus, uploadSetStatus } from './actions';
import { IUploadState } from './reducer';
const addStatus = (
state: IUploadState,
{ temp_id, status }: ReturnType<typeof uploadAddStatus>
): IUploadState => assocPath(
['statuses'],
{ ...state.statuses, [temp_id]: { ...EMPTY_UPLOAD_STATUS, ...status } },
state
);
const dropStatus = (
state: IUploadState,
{ temp_id }: ReturnType<typeof uploadDropStatus>
): IUploadState => assocPath(
['statuses'],
omit([temp_id], state.statuses),
state,
);
const setStatus = (
state: IUploadState,
{ temp_id, status }: ReturnType<typeof uploadSetStatus>
): IUploadState => assocPath(
['statuses'],
{ ...state.statuses, [temp_id]: { ...(state.statuses[temp_id] || EMPTY_UPLOAD_STATUS), ...status } },
state
);
export const UPLOAD_HANDLERS = {
[UPLOAD_ACTIONS.ADD_STATUS]: addStatus,
[UPLOAD_ACTIONS.DROP_STATUS]: dropStatus,
[UPLOAD_ACTIONS.SET_STATUS]: setStatus,
};

View file

@ -4,7 +4,14 @@ import { UUID } from "../types";
import { UPLOAD_HANDLERS } from "./handlers"; import { UPLOAD_HANDLERS } from "./handlers";
export interface IUploadStatus { export interface IUploadStatus {
progress: number; is_loading: boolean; error: string; is_uploading: boolean;
error: string;
preview: string;
uuid: UUID;
url: string;
thumbnail_url: string;
type: string;
progress: number;
} }
export interface IUploadState { export interface IUploadState {

View file

@ -1,11 +1,11 @@
import { takeEvery, all, spawn, call, put, take, fork, race } from 'redux-saga/effects'; import { takeEvery, all, spawn, call, put, take, fork, race } from 'redux-saga/effects';
import { UPLOAD_ACTIONS } from '~/redux/uploads/constants'; import { UPLOAD_ACTIONS } from '~/redux/uploads/constants';
import { uploadUploadFiles } from './actions'; import { uploadUploadFiles, uploadSetStatus, uploadAddStatus, uploadDropStatus } from './actions';
import { reqWrapper } from '../auth/sagas'; import { reqWrapper } from '../auth/sagas';
import { createUploader, uploadGetThumb } from '~/utils/uploader'; import { createUploader, uploadGetThumb } from '~/utils/uploader';
import { HTTP_RESPONSES } from '~/utils/api'; import { HTTP_RESPONSES } from '~/utils/api';
import { VALIDATORS } from '~/utils/validators'; import { VALIDATORS } from '~/utils/validators';
import { UUID, IFileWithUUID, IResultWithStatus } from '../types'; import { UUID, IFileWithUUID } from '../types';
function* uploadCall({ temp_id, onProgress, file }) { function* uploadCall({ temp_id, onProgress, file }) {
return yield call(reqWrapper, console.log, { file, onProgress }); return yield call(reqWrapper, console.log, { file, onProgress });
@ -14,9 +14,8 @@ function* uploadCall({ temp_id, onProgress, file }) {
function* onUploadProgress(chan) { function* onUploadProgress(chan) {
while (true) { while (true) {
const { progress, temp_id }: { progress: number; temp_id: string } = yield take(chan); const { progress, temp_id }: { progress: number; temp_id: string } = yield take(chan);
console.log('progress', { progress, temp_id });
// replace with the one, that changes upload status with progress yield put(uploadSetStatus(temp_id, { progress }));
// yield put(inventoryUploadSet(temp_id, { progress }));
} }
} }
@ -35,52 +34,55 @@ function* uploadWorker(file: File, temp_id: UUID) {
return yield call(promise, { temp_id, file }); return yield call(promise, { temp_id, file });
} }
function* uploadFile({ file, temp_id }: IFileWithUUID): IResultWithStatus<any> { function* uploadFile({ file, temp_id }: IFileWithUUID) {
if (!file.type || !VALIDATORS.IS_IMAGE_MIME(file.type)) { if (!file.type || !VALIDATORS.IS_IMAGE_MIME(file.type)) {
return { error: 'File_Not_Image', status: HTTP_RESPONSES.BAD_REQUEST, data: {} }; return { error: 'File_Not_Image', status: HTTP_RESPONSES.BAD_REQUEST, data: {} };
} }
const preview = yield call(uploadGetThumb, file); const preview = yield call(uploadGetThumb, file);
// yield put(inventoryUploadAdd( // replace with the one, what adds file upload status yield put(
// temp_id, uploadAddStatus(
// { // replace with the one, what adds file upload status
// ...EMPTY_INVENTORY_UPLOAD, temp_id,
// preview, {
// is_uploading: true, preview,
// type: file.type, is_uploading: true,
// }, type: file.type,
// )); }
)
);
const { result, cancel, cancel_editing, save_inventory } = yield race({ const { result, cancel, cancel_editing, save_inventory } = yield race({
result: call(uploadWorker, file, temp_id), result: call(uploadWorker, file, temp_id),
cancel: call(uploadCancelWorker, temp_id), cancel: call(uploadCancelWorker, temp_id),
// subject_cancel: call(uploadSubjectCancelWorker, subject)
// add here CANCEL_UPLOADS worker, that will watch for subject // add here CANCEL_UPLOADS worker, that will watch for subject
// cancel_editing: take(UPLOAD_ACTIONS.CANCEL_EDITING), // cancel_editing: take(UPLOAD_ACTIONS.CANCEL_EDITING),
// save_inventory: take(INVENTORY_ACTIONS.SAVE_INVENTORY), // save_inventory: take(INVENTORY_ACTIONS.SAVE_INVENTORY),
}) as any; }) as any;
if (cancel || cancel_editing || save_inventory) { if (cancel || cancel_editing || save_inventory) {
// return yield put(inventoryUploadDrop(temp_id)); // replace with the one, that will delete file upload status record return yield put(uploadDropStatus(temp_id));
return { error: null, status: HTTP_RESPONSES.NOT_FOUND, data: {} };
} }
const { data, error } = result; const { data, error } = result;
if (error) { if (error) {
// replace with the one, that changes file upload status to error return yield put(
// return yield put(inventoryUploadSet(temp_id, { is_uploading: false, error: data.detail || error })); uploadSetStatus(temp_id, { is_uploading: false, error: data.detail || error })
return { error: null, status: HTTP_RESPONSES.NOT_FOUND, data: {} }; );
} }
// replace with the one, that updates upload status with actual data yield put(
// yield put(inventoryUploadSet(temp_id, { uploadSetStatus(temp_id, {
// is_uploading: false, is_uploading: false,
// error: null, error: null,
// uuid: data.uuid, uuid: data.uuid,
// url: data.url, url: data.url,
// thumbnail_url: data.url, thumbnail_url: data.url,
// })); })
);
return { error: null, status: HTTP_RESPONSES.CREATED, data: {} }; // add file here as data return { error: null, status: HTTP_RESPONSES.CREATED, data: {} }; // add file here as data
} }