From d73437d8c69101fb23f37845e8aab606334829cf Mon Sep 17 00:00:00 2001 From: muerwre Date: Wed, 7 Aug 2019 15:37:57 +0700 Subject: [PATCH] working upload saga? --- .eslintrc.js | 2 + package-lock.json | 83 ++++++++++++++++++-- package.json | 4 +- src/components/editors/EditorPanel/index.tsx | 6 +- src/components/editors/ImageEditor/index.tsx | 21 +++-- src/redux/uploads/sagas.ts | 10 ++- src/utils/uploader.ts | 46 +++++++++-- src/utils/validators.ts | 23 ++---- 8 files changed, 153 insertions(+), 42 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 652d318d..279a3bb4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -46,6 +46,8 @@ module.exports = { 'no-nested-ternary': 1, 'arrow-parens': 0, 'import/prefer-default-export': 0, + 'no-return-await': 0, + 'prefer-promise-reject-errors': 0, }, globals: { document: false, diff --git a/package-lock.json b/package-lock.json index a9989d96..248ca002 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2594,6 +2594,53 @@ } } }, + "@redux-saga/core": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.0.3.tgz", + "integrity": "sha512-zf8h5N0oTzaNeSMxOWH9GJMB9IRSM8JubDsrZVsvVltXjzFFSR8DNt7tbPoRJUK0hFfQB1it+bL+dEMWpD7wXA==", + "requires": { + "@babel/runtime": "^7.0.0", + "@redux-saga/deferred": "^1.0.1", + "@redux-saga/delay-p": "^1.0.1", + "@redux-saga/is": "^1.0.2", + "@redux-saga/symbols": "^1.0.1", + "@redux-saga/types": "^1.0.2", + "redux": ">=0.10 <5", + "typescript-tuple": "^2.1.0" + } + }, + "@redux-saga/deferred": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@redux-saga/deferred/-/deferred-1.0.1.tgz", + "integrity": "sha512-+gW5xQ93QXOOmRLAmX8x2Hx1HpbTG6CM6+HcdTSbJovh4uMWaGyeDECnqXSt8QqA/ja3s2nqYXLqXFKepIQ1hw==" + }, + "@redux-saga/delay-p": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@redux-saga/delay-p/-/delay-p-1.0.1.tgz", + "integrity": "sha512-0SnNDyDLUyB4NThtptAwiprNOnbCNhoed/Rp5JwS7SB+a/AdWynVgg/E6BmjsggLFNr07KW0bzn05tsPRBuU7Q==", + "requires": { + "@redux-saga/symbols": "^1.0.1" + } + }, + "@redux-saga/is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@redux-saga/is/-/is-1.0.2.tgz", + "integrity": "sha512-WnaUOwYvPK2waWjzebT4uhL8zY76XNkzzpJ2EQJe8bN1tByvAjvT7MuJZTSshOhdHL5PsRO0MsH224XIXBJidQ==", + "requires": { + "@redux-saga/symbols": "^1.0.1", + "@redux-saga/types": "^1.0.2" + } + }, + "@redux-saga/symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@redux-saga/symbols/-/symbols-1.0.1.tgz", + "integrity": "sha512-akKkzcVnb1RzJaZV2LQFbi51abvdICMuAKwwLoCjjxLbLAGIw9EJxk5ucNnWSSCEsoEQMeol5tkAcK+Xzuv1Bg==" + }, + "@redux-saga/types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.0.2.tgz", + "integrity": "sha512-8/qcMh15507AnXJ3lBeuhsdFwnWQqnp68EpUuHlYPixJ5vjVmls7/Jq48cnUlrZI8Jd9U1jkhfCl0gaT5KMgVw==" + }, "@types/classnames": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.7.tgz", @@ -6482,9 +6529,9 @@ } }, "date-fns": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz", - "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==" + "version": "2.0.0-beta.4", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.0.0-beta.4.tgz", + "integrity": "sha512-xekjYm7ZDBuzePM/GBodhi3hW3P8dd2RbuIOLBjet2E6EGFR82wHTTXCSGuDEoapqlDvsx88ymRsq85lbM7dDw==" }, "date-now": { "version": "0.1.4", @@ -14329,9 +14376,12 @@ "integrity": "sha512-sSJAzNq7zka3qVHKce1hbvqf0Vf5DuTVm7dr4GtsqQVOexnrvbV47RWFiPxQ8fscnyiuWyD2O92DOxPl0tGCRg==" }, "redux-saga": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-0.16.2.tgz", - "integrity": "sha512-iIjKnRThI5sKPEASpUvySemjzwqwI13e3qP7oLub+FycCRDysLSAOwt958niZW6LhxfmS6Qm1BzbU70w/Koc4w==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.0.5.tgz", + "integrity": "sha512-ytGFtaHyz6NQMQp7/LpQ/6CtOkbPRCcAeljUpc4c95hRm5U6dFvrRiZHeBPuQZ56L3oXfTX3hiYh8/3ZudsiNg==", + "requires": { + "@redux-saga/core": "^1.0.3" + } }, "reduxsauce": { "version": "1.0.1", @@ -16843,6 +16893,14 @@ "integrity": "sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw==", "dev": true }, + "typescript-compare": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", + "integrity": "sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==", + "requires": { + "typescript-logic": "^0.0.0" + } + }, "typescript-eslint-parser": { "version": "22.0.0", "resolved": "https://registry.npmjs.org/typescript-eslint-parser/-/typescript-eslint-parser-22.0.0.tgz", @@ -16876,6 +16934,19 @@ "semver": "5.5.0" } }, + "typescript-logic": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/typescript-logic/-/typescript-logic-0.0.0.tgz", + "integrity": "sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q==" + }, + "typescript-tuple": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/typescript-tuple/-/typescript-tuple-2.2.1.tgz", + "integrity": "sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==", + "requires": { + "typescript-compare": "^0.0.2" + } + }, "ua-parser-js": { "version": "0.7.17", "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz", diff --git a/package.json b/package.json index 1b314873..f7c83c6c 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "classnames": "^2.2.6", "clean-webpack-plugin": "^0.1.9", "connected-react-router": "^6.3.2", - "date-fns": "^1.30.1", + "date-fns": "^2.0.0-alpha.27", "dotenv": "^8.0.0", "dotenv-webpack": "^1.7.0", "eslint-plugin-react-hooks": "^1.6.1", @@ -92,7 +92,7 @@ "react-stack-grid": "^0.7.1", "redux": "^4.0.1", "redux-persist": "^5.10.0", - "redux-saga": "^0.16.2", + "redux-saga": "^1.0.5", "reduxsauce": "^1.0.0", "sass-loader": "^7.1.0", "sass-resources-loader": "^2.0.0", diff --git a/src/components/editors/EditorPanel/index.tsx b/src/components/editors/EditorPanel/index.tsx index fb513b8a..36b90bf9 100644 --- a/src/components/editors/EditorPanel/index.tsx +++ b/src/components/editors/EditorPanel/index.tsx @@ -1,13 +1,13 @@ import React, { FC } from 'react'; import * as styles from './styles.scss'; -import { INode } from '~/redux/types'; +import { INode, IFileWithUUID } from '~/redux/types'; interface IProps { data: INode; setData: (val: INode) => void; - onUpload: (val: File[]) => void; + onUpload: (val: IFileWithUUID[]) => void; } -const EditorPanel: FC = ({}) =>
; +const EditorPanel: FC = () =>
; export { EditorPanel }; diff --git a/src/components/editors/ImageEditor/index.tsx b/src/components/editors/ImageEditor/index.tsx index bd32975a..d09b9ebe 100644 --- a/src/components/editors/ImageEditor/index.tsx +++ b/src/components/editors/ImageEditor/index.tsx @@ -1,7 +1,9 @@ -import React, { FC, useCallback, useEffect, useState } from 'react'; +import React, { + FC, useCallback, useEffect, useState +} from 'react'; +import uuid from 'uuid4'; import { INode, IFileWithUUID } from '~/redux/types'; import * as styles from './styles.scss'; -import uuid from 'uuid4'; interface IProps { data: INode; @@ -14,20 +16,22 @@ const ImageEditor: FC = ({ data, setData, onUpload }) => { const [temp, setTemp] = useState([]); const onDrop = useCallback( - (event: DragEvent) => { + (event: React.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; const files: IFileWithUUID[] = Array.from(event.dataTransfer.files).map( (file: File): IFileWithUUID => ({ file, temp_id: uuid(), - subject: 'editor', + subject: 'editor' }) ); + const temps = files.map(file => file.temp_id); + + setTemp(temps); onUpload(files); }, [onUpload] @@ -43,10 +47,13 @@ const ImageEditor: FC = ({ data, setData, onUpload }) => { (file: File): IFileWithUUID => ({ file, temp_id: uuid(), - subject: 'editor', + subject: 'editor' }) ); + const temps = files.map(file => file.temp_id); + + setTemp(temps); onUpload(files); }, [onUpload] diff --git a/src/redux/uploads/sagas.ts b/src/redux/uploads/sagas.ts index f21f8915..db67f679 100644 --- a/src/redux/uploads/sagas.ts +++ b/src/redux/uploads/sagas.ts @@ -2,13 +2,13 @@ import { takeEvery, all, spawn, call, put, take, fork, race } from 'redux-saga/e import { UPLOAD_ACTIONS } from '~/redux/uploads/constants'; import { uploadUploadFiles, uploadSetStatus, uploadAddStatus, uploadDropStatus } from './actions'; import { reqWrapper } from '../auth/sagas'; -import { createUploader, uploadGetThumb } from '~/utils/uploader'; +import { createUploader, uploadGetThumb, fakeUploader } from '~/utils/uploader'; import { HTTP_RESPONSES } from '~/utils/api'; import { VALIDATORS } from '~/utils/validators'; import { UUID, IFileWithUUID } from '../types'; function* uploadCall({ temp_id, onProgress, file }) { - return yield call(reqWrapper, console.log, { file, onProgress }); + return yield call(reqWrapper, fakeUploader, { file: { url: 'some', error: 'cant do this boss' }, onProgress, mustSucceed: true }); } function* onUploadProgress(chan) { @@ -41,7 +41,7 @@ function* uploadFile({ file, temp_id }: IFileWithUUID) { const preview = yield call(uploadGetThumb, file); - yield put( + yield put( uploadAddStatus( // replace with the one, what adds file upload status temp_id, @@ -53,7 +53,9 @@ function* uploadFile({ file, temp_id }: IFileWithUUID) { ) ); - const { result, cancel, cancel_editing, save_inventory } = yield race({ + 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) diff --git a/src/utils/uploader.ts b/src/utils/uploader.ts index 403f57f8..c38224a1 100644 --- a/src/utils/uploader.ts +++ b/src/utils/uploader.ts @@ -1,11 +1,18 @@ -import { eventChannel, END } from 'redux-saga'; +import uuid from 'uuid4'; +import { eventChannel, END, EventChannel } from 'redux-saga'; import { VALIDATORS } from '~/utils/validators'; +import { IResultWithStatus, IFile } from '~/redux/types'; +import { HTTP_RESPONSES } from './api'; export const IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/jpg']; -export function createUploader - (callback: (args: any) => any, payload: R): - [(args: T) => (args: T & { onProgress: (current: number, total: number) => void }) => any, EventChannel] { +export function createUploader( + callback: (args: any) => any, + payload: R +): [ + (args: T) => (args: T & { onProgress: (current: number, total: number) => void }) => any, + EventChannel + ] { let emit; const chan = eventChannel(emitter => { @@ -14,7 +21,9 @@ export function createUploader }); const onProgress = (current: number, total: number): void => { - emit(current >= total ? END : { ...payload, progress: parseFloat((current / total).toFixed(1)) }); + emit( + current >= total ? END : { ...payload, progress: parseFloat((current / total).toFixed(1)) } + ); }; const wrappedCallback = args => callback({ ...args, onProgress }); @@ -31,3 +40,30 @@ export const uploadGetThumb = async file => { reader.readAsDataURL(file); }); }; + +export const fakeUploader = ({ + file, + onProgress, + mustSucceed, +}: { + file: { url?: string; error?: string }; + onProgress: (current: number, total: number) => void; + mustSucceed: boolean; +}): Promise> => { + const { url, error } = file; + + return new Promise((resolve, reject) => { + setTimeout(() => { + onProgress(1, 2); + }, 100); + + setTimeout(() => { + onProgress(2, 2); + if (mustSucceed) { + resolve({ status: HTTP_RESPONSES.CREATED, data: { id: uuid() } }); + } else { + reject({ response: { statusText: error } }); + } + }, 500); + }); +}; diff --git a/src/utils/validators.ts b/src/utils/validators.ts index bbbc9052..58eda5c3 100644 --- a/src/utils/validators.ts +++ b/src/utils/validators.ts @@ -1,28 +1,23 @@ -import { IMAGE_MIME_TYPES } from '~/utils/uploader'; import isValid from 'date-fns/isValid'; -import { IAddress } from '~/redux/types'; +import { IMAGE_MIME_TYPES } from '~/utils/uploader'; -const isValidEmail = (email: string): boolean => - !!email && - String(email) && - !!String(email).match( - /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/, +const isValidEmail = (email: string): boolean => !!email + && String(email) + && !!String(email).match( + /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/ ); -const isLikeEmail = (email: string): boolean => - !!email && String(email) && !!String(email).match(/^([^\@]+)@([^\@]+)\.([^\@]+)$$/); +const isLikeEmail = (email: string): boolean => !!email && String(email) && !!String(email).match(/^([^\@]+)@([^\@]+)\.([^\@]+)$$/); const isNonEmpty = (value: string): boolean => !!value && value.trim().length > 0; const isLikePhone = isNonEmpty; -const isAtLeast = (length: number, value: string): boolean => - !!value && value.trim().length >= length; +const isAtLeast = (length: number, value: string): boolean => !!value && value.trim().length >= length; const isMimeOfImage = (mime): boolean => !!mime && IMAGE_MIME_TYPES.indexOf(mime) >= 0; const isDate = (val: string): boolean => !!val && isValid(new Date(val)); const isStringDate = (val: string): boolean => !!val && !!val.match(/^[\d]{2,4}\-[\d]{2}-[\d]{2}/); -const isAddrWithRaw = ({ raw }: Partial): boolean => !!raw && isNonEmpty(raw); export const VALIDATORS = { EMAIL: isValidEmail, @@ -33,7 +28,5 @@ export const VALIDATORS = { IS_IMAGE_MIME: isMimeOfImage, IS_DATE: isDate, IS_STRINGY_DATE: isStringDate, - IS_ADDRESS_WITH_RAW: isAddrWithRaw, - EVOLVE: (validator: (val: any) => boolean, error: string) => (val: any) => - !val || !validator(val) ? error : null, + EVOLVE: (validator: (val: any) => boolean, error: string) => (val: any) => (!val || !validator(val) ? error : null) };