diff --git a/.eslintrc.js b/.eslintrc.js index f02c9765..652d318d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -44,6 +44,8 @@ module.exports = { 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', 'no-nested-ternary': 1, + 'arrow-parens': 0, + 'import/prefer-default-export': 0, }, globals: { document: false, diff --git a/src/components/editors/ImageEditor/index.tsx b/src/components/editors/ImageEditor/index.tsx index f2b9f418..bd32975a 100644 --- a/src/components/editors/ImageEditor/index.tsx +++ b/src/components/editors/ImageEditor/index.tsx @@ -1,51 +1,69 @@ -import React, {FC, useCallback, useEffect} from 'react'; -import { INode } from '~/redux/types'; +import React, { FC, useCallback, useEffect, useState } from 'react'; +import { INode, IFileWithUUID } from '~/redux/types'; import * as styles from './styles.scss'; +import uuid from 'uuid4'; interface IProps { - data: INode, + data: INode; setData: (val: INode) => void; - onUpload: (val: File[]) => void; + onUpload: (val: IFileWithUUID[]) => void; } -const ImageEditor: FC = ({ - data, - setData, - onUpload, -}) => { +const ImageEditor: FC = ({ data, setData, onUpload }) => { const eventPreventer = useCallback(event => event.preventDefault(), []); + const [temp, setTemp] = useState([]); - const onDrop = useCallback(event => { - event.preventDefault(); + const onDrop = useCallback( + (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); - }, [onUpload]); + const files: IFileWithUUID[] = Array.from(event.dataTransfer.files).map( + (file: File): IFileWithUUID => ({ + file, + temp_id: uuid(), + subject: 'editor', + }) + ); - const onInputChange = useCallback(event => { - event.preventDefault(); + onUpload(files); + }, + [onUpload] + ); - if (!event.target.files || !event.target.files.length) return; + const onInputChange = useCallback( + event => { + event.preventDefault(); - onUpload(event.target.files); - }, [onUpload]); + if (!event.target.files || !event.target.files.length) return; + + const files: IFileWithUUID[] = Array.from(event.target.files).map( + (file: File): IFileWithUUID => ({ + file, + temp_id: uuid(), + subject: 'editor', + }) + ); + + onUpload(files); + }, + [onUpload] + ); useEffect(() => { - window.addEventListener("dragover", eventPreventer,false); - window.addEventListener("drop",eventPreventer,false); + window.addEventListener('dragover', eventPreventer, false); + window.addEventListener('drop', eventPreventer, false); return () => { - window.removeEventListener("dragover", eventPreventer,false); - window.removeEventListener("drop",eventPreventer,false); - } + window.removeEventListener('dragover', eventPreventer, false); + window.removeEventListener('drop', eventPreventer, false); + }; }, [eventPreventer]); return ( -
+
{data.type}
diff --git a/src/containers/dialogs/EditorDialog/index.tsx b/src/containers/dialogs/EditorDialog/index.tsx index eb92b026..3da37de8 100644 --- a/src/containers/dialogs/EditorDialog/index.tsx +++ b/src/containers/dialogs/EditorDialog/index.tsx @@ -12,7 +12,7 @@ import { selectNode } from '~/redux/node/selectors'; import { ImageEditor } from '~/components/editors/ImageEditor'; import { EditorPanel } from '~/components/editors/EditorPanel'; import * as UPLOAD_ACTIONS from '~/redux/uploads/actions'; -import {uploadUploadFiles} from "~/redux/uploads/actions"; +import { IFileWithUUID } from '~/redux/types'; const mapStateToProps = selectNode; const mapDispatchToProps = { @@ -24,21 +24,23 @@ type IProps = IDialogProps & ReturnType & typeof mapDisp const EditorDialogUnconnected: FC = ({ onRequestClose, editor, uploadUploadFiles }) => { const [data, setData] = useState(editor); - const setTitle = useCallback(title => { - setData({ ...data, title }); - }, [setData, data]); + const setTitle = useCallback( + title => { + setData({ ...data, title }); + }, + [setData, data] + ); - const onUpload = useCallback((files: File[]) => { - uploadUploadFiles(files, 'editor'); - }, [uploadUploadFiles]); + const onUpload = useCallback( + (files: IFileWithUUID[]) => { + uploadUploadFiles(files); + }, + [uploadUploadFiles] + ); const buttons = ( - + @@ -53,16 +55,15 @@ const EditorDialogUnconnected: FC = ({ onRequestClose, editor, uploadUpl return (
- +
); }; -const EditorDialog = connect(mapStateToProps, mapDispatchToProps)(EditorDialogUnconnected) +const EditorDialog = connect( + mapStateToProps, + mapDispatchToProps +)(EditorDialogUnconnected); export { EditorDialog }; diff --git a/src/redux/types.ts b/src/redux/types.ts index 506ee7a8..d5a98490 100644 --- a/src/redux/types.ts +++ b/src/redux/types.ts @@ -67,6 +67,7 @@ export interface IFile { export interface IFileWithUUID { temp_id?: UUID; file: File; + subject: string; } export interface IBlock { diff --git a/src/redux/uploads/actions.ts b/src/redux/uploads/actions.ts index 33d2045c..41c85c69 100644 --- a/src/redux/uploads/actions.ts +++ b/src/redux/uploads/actions.ts @@ -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, - subject, type: UPLOAD_ACTIONS.UPLOAD_FILES, }); + +export const uploadAddStatus = (temp_id: UUID, status?: Partial) => ({ + temp_id, + status, + type: UPLOAD_ACTIONS.ADD_STATUS, +}); + +export const uploadSetStatus = (temp_id: UUID, status?: Partial) => ({ + temp_id, + status, + type: UPLOAD_ACTIONS.SET_STATUS, +}); + +export const uploadDropStatus = (temp_id: UUID) => ({ + temp_id, + type: UPLOAD_ACTIONS.DROP_STATUS, +}); diff --git a/src/redux/uploads/constants.ts b/src/redux/uploads/constants.ts index d620d6ee..32ce0320 100644 --- a/src/redux/uploads/constants.ts +++ b/src/redux/uploads/constants.ts @@ -1,10 +1,15 @@ import { IFile } from "~/redux/types"; +import { IUploadState, IUploadStatus } from "./reducer"; const prefix = 'UPLOAD.'; export const UPLOAD_ACTIONS = { UPLOAD_FILES: `${prefix}UPLOAD_FILES`, 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 = { @@ -19,3 +24,14 @@ export const EMPTY_FILE: IFile = { type: 'image', 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, +} \ No newline at end of file diff --git a/src/redux/uploads/handlers.ts b/src/redux/uploads/handlers.ts index 76f9e6b0..bfda31c2 100644 --- a/src/redux/uploads/handlers.ts +++ b/src/redux/uploads/handlers.ts @@ -1,3 +1,38 @@ -export const UPLOAD_HANDLERS = { +import assocPath from 'ramda/es/assocPath'; +import omit from 'ramda/es/omit'; -} \ No newline at end of file +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 +): IUploadState => assocPath( + ['statuses'], + { ...state.statuses, [temp_id]: { ...EMPTY_UPLOAD_STATUS, ...status } }, + state +); + +const dropStatus = ( + state: IUploadState, + { temp_id }: ReturnType +): IUploadState => assocPath( + ['statuses'], + omit([temp_id], state.statuses), + state, +); + +const setStatus = ( + state: IUploadState, + { temp_id, status }: ReturnType +): 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, +}; diff --git a/src/redux/uploads/reducer.ts b/src/redux/uploads/reducer.ts index 062d2e63..2cb24437 100644 --- a/src/redux/uploads/reducer.ts +++ b/src/redux/uploads/reducer.ts @@ -4,7 +4,14 @@ import { UUID } from "../types"; import { UPLOAD_HANDLERS } from "./handlers"; 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 { diff --git a/src/redux/uploads/sagas.ts b/src/redux/uploads/sagas.ts index c95461eb..f21f8915 100644 --- a/src/redux/uploads/sagas.ts +++ b/src/redux/uploads/sagas.ts @@ -1,11 +1,11 @@ import { takeEvery, all, spawn, call, put, take, fork, race } from 'redux-saga/effects'; import { UPLOAD_ACTIONS } from '~/redux/uploads/constants'; -import { uploadUploadFiles } from './actions'; +import { uploadUploadFiles, uploadSetStatus, uploadAddStatus, uploadDropStatus } from './actions'; import { reqWrapper } from '../auth/sagas'; import { createUploader, uploadGetThumb } from '~/utils/uploader'; import { HTTP_RESPONSES } from '~/utils/api'; import { VALIDATORS } from '~/utils/validators'; -import { UUID, IFileWithUUID, IResultWithStatus } from '../types'; +import { UUID, IFileWithUUID } from '../types'; function* uploadCall({ temp_id, onProgress, file }) { return yield call(reqWrapper, console.log, { file, onProgress }); @@ -14,9 +14,8 @@ function* uploadCall({ temp_id, onProgress, file }) { function* onUploadProgress(chan) { while (true) { 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(inventoryUploadSet(temp_id, { progress })); + + yield put(uploadSetStatus(temp_id, { progress })); } } @@ -35,52 +34,55 @@ function* uploadWorker(file: File, temp_id: UUID) { return yield call(promise, { temp_id, file }); } -function* uploadFile({ file, temp_id }: IFileWithUUID): IResultWithStatus { +function* uploadFile({ file, temp_id }: IFileWithUUID) { if (!file.type || !VALIDATORS.IS_IMAGE_MIME(file.type)) { return { error: 'File_Not_Image', status: HTTP_RESPONSES.BAD_REQUEST, data: {} }; } const preview = yield call(uploadGetThumb, file); - // yield put(inventoryUploadAdd( // replace with the one, what adds file upload status - // temp_id, - // { - // ...EMPTY_INVENTORY_UPLOAD, - // preview, - // is_uploading: true, - // type: file.type, - // }, - // )); + yield put( + uploadAddStatus( + // replace with the one, what adds file upload status + temp_id, + { + preview, + is_uploading: true, + type: file.type, + } + ) + ); const { result, cancel, cancel_editing, save_inventory } = yield race({ result: call(uploadWorker, file, temp_id), cancel: call(uploadCancelWorker, temp_id), + // subject_cancel: call(uploadSubjectCancelWorker, subject) // add here CANCEL_UPLOADS worker, that will watch for subject // cancel_editing: take(UPLOAD_ACTIONS.CANCEL_EDITING), // save_inventory: take(INVENTORY_ACTIONS.SAVE_INVENTORY), }) as any; if (cancel || cancel_editing || save_inventory) { - // return yield put(inventoryUploadDrop(temp_id)); // replace with the one, that will delete file upload status record - return { error: null, status: HTTP_RESPONSES.NOT_FOUND, data: {} }; + return yield put(uploadDropStatus(temp_id)); } const { data, error } = result; if (error) { - // replace with the one, that changes file upload status to error - // return yield put(inventoryUploadSet(temp_id, { is_uploading: false, error: data.detail || error })); - return { error: null, status: HTTP_RESPONSES.NOT_FOUND, data: {} }; + return yield put( + uploadSetStatus(temp_id, { is_uploading: false, error: data.detail || error }) + ); } - // replace with the one, that updates upload status with actual data - // yield put(inventoryUploadSet(temp_id, { - // is_uploading: false, - // error: null, - // uuid: data.uuid, - // url: data.url, - // thumbnail_url: data.url, - // })); + yield put( + uploadSetStatus(temp_id, { + is_uploading: false, + error: null, + uuid: data.uuid, + url: data.url, + thumbnail_url: data.url, + }) + ); return { error: null, status: HTTP_RESPONSES.CREATED, data: {} }; // add file here as data }