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:
parent
caf85c104f
commit
3872ff5903
9 changed files with 180 additions and 80 deletions
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 };
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
|
@ -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,
|
||||||
|
};
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue