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

fixed uploads with real data

This commit is contained in:
muerwre 2019-08-20 12:05:50 +07:00
parent db0a94b581
commit 38d56838f2
5 changed files with 127 additions and 58 deletions

View file

@ -1,5 +1,5 @@
import React, { import React, {
FC, useState, useCallback, useEffect FC, useState, useCallback, useEffect,
} from 'react'; } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import assocPath from 'ramda/es/assocPath'; import assocPath from 'ramda/es/assocPath';
@ -20,6 +20,7 @@ import { moveArrItem } from '~/utils/fn';
import { IFile, IFileWithUUID } from '~/redux/types'; import { IFile, IFileWithUUID } from '~/redux/types';
import * as UPLOAD_ACTIONS from '~/redux/uploads/actions'; import * as UPLOAD_ACTIONS from '~/redux/uploads/actions';
import { selectUploads } from '~/redux/uploads/selectors'; import { selectUploads } from '~/redux/uploads/selectors';
import { UPLOAD_TARGETS, UPLOAD_TYPES, UPLOAD_SUBJECTS } from '~/redux/uploads/constants';
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
const { editor } = selectNode(state); const { editor } = selectNode(state);
@ -35,19 +36,29 @@ const mapDispatchToProps = {
type IProps = IDialogProps & ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {}; type IProps = IDialogProps & ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps & {};
const EditorDialogUnconnected: FC<IProps> = ({ const EditorDialogUnconnected: FC<IProps> = ({
onRequestClose, editor, uploadUploadFiles, files, statuses onRequestClose,
editor,
uploadUploadFiles,
files,
statuses,
}) => { }) => {
const [data, setData] = useState(editor); const [data, setData] = useState(editor);
const eventPreventer = useCallback(event => event.preventDefault(), []); const eventPreventer = useCallback(event => event.preventDefault(), []);
const [temp, setTemp] = useState([]); const [temp, setTemp] = useState([]);
const onFileMove = useCallback((old_index: number, new_index: number) => { const onFileMove = useCallback(
(old_index: number, new_index: number) => {
setData(assocPath(['files'], moveArrItem(old_index, new_index, data.files), data)); setData(assocPath(['files'], moveArrItem(old_index, new_index, data.files), data));
}, [data, setData]); },
[data, setData]
);
const onFileAdd = useCallback((file: IFile) => { const onFileAdd = useCallback(
(file: IFile) => {
setData(assocPath(['files'], append(file, data.files), data)); setData(assocPath(['files'], append(file, data.files), data));
}, [data, setData]); },
[data, setData]
);
const onDrop = useCallback( const onDrop = useCallback(
(event: React.DragEvent<HTMLFormElement>) => { (event: React.DragEvent<HTMLFormElement>) => {
@ -59,7 +70,9 @@ const EditorDialogUnconnected: FC<IProps> = ({
(file: File): IFileWithUUID => ({ (file: File): IFileWithUUID => ({
file, file,
temp_id: uuid(), temp_id: uuid(),
subject: 'editor' subject: UPLOAD_SUBJECTS.EDITOR,
target: UPLOAD_TARGETS.NODES,
type: UPLOAD_TYPES.IMAGE,
}) })
); );
@ -81,7 +94,9 @@ const EditorDialogUnconnected: FC<IProps> = ({
(file: File): IFileWithUUID => ({ (file: File): IFileWithUUID => ({
file, file,
temp_id: uuid(), temp_id: uuid(),
subject: 'editor' subject: UPLOAD_SUBJECTS.EDITOR,
target: UPLOAD_TARGETS.NODES,
type: UPLOAD_TYPES.IMAGE,
}) })
); );
@ -124,11 +139,7 @@ const EditorDialogUnconnected: FC<IProps> = ({
const buttons = ( const buttons = (
<Padder style={{ position: 'relative' }}> <Padder style={{ position: 'relative' }}>
<EditorPanel <EditorPanel data={data} setData={setData} onUpload={onInputChange} />
data={data}
setData={setData}
onUpload={onInputChange}
/>
<Group horizontal> <Group horizontal>
<InputText title="Название" value={data.title} handler={setTitle} /> <InputText title="Название" value={data.title} handler={setTitle} />

View file

@ -1,5 +1,5 @@
import { import {
call, put, takeLatest, select call, put, takeLatest, select,
} from 'redux-saga/effects'; } from 'redux-saga/effects';
import { SagaIterator } from 'redux-saga'; import { SagaIterator } from 'redux-saga';
import { push } from 'connected-react-router'; import { push } from 'connected-react-router';
@ -15,10 +15,14 @@ import { IResultWithStatus } from '../types';
import { IUser } from './types'; import { IUser } from './types';
export function* reqWrapper(requestAction, props = {}): ReturnType<typeof requestAction> { export function* reqWrapper(requestAction, props = {}): ReturnType<typeof requestAction> {
const { access } = yield select(selectToken); const access = yield select(selectToken);
console.log('firing reqWrapper');
const result = yield call(requestAction, { access, ...props }); const result = yield call(requestAction, { access, ...props });
console.log('at reqWrapper', { result });
if (result && result.status === 401) { if (result && result.status === 401) {
yield put(push(URLS.BASE)); yield put(push(URLS.BASE));
yield put(modalShowDialog(DIALOGS.LOGIN)); yield put(modalShowDialog(DIALOGS.LOGIN));
@ -26,15 +30,29 @@ export function* reqWrapper(requestAction, props = {}): ReturnType<typeof reques
return result; return result;
} }
console.log('reqWrapper will return');
return result; return result;
} }
function* sendLoginRequestSaga({ username, password }: ReturnType<typeof ActionCreators.userSendLoginRequest>) { function* sendLoginRequestSaga({
username,
password,
}: ReturnType<typeof ActionCreators.userSendLoginRequest>) {
if (!username || !password) return; if (!username || !password) return;
const { error, data: { token, user } }: IResultWithStatus<{ token: string; user: IUser }> = yield call(apiUserLogin, { username, password }); const {
error,
data: { token, user },
}: IResultWithStatus<{ token: string; user: IUser }> = yield call(apiUserLogin, {
username,
password,
});
if (error) { yield put(userSetLoginError(error)); return; } if (error) {
yield put(userSetLoginError(error));
return;
}
yield put(authSetToken(token)); yield put(authSetToken(token));
yield put(authSetUser({ ...user, is_user: true })); yield put(authSetUser({ ...user, is_user: true }));

View file

@ -46,6 +46,8 @@ export interface IResultWithStatus<T> {
export type UUID = string; export type UUID = string;
export type IUploadType = 'image' | 'text' | 'audio' | 'video' | 'other';
export interface IFile { export interface IFile {
id?: UUID; id?: UUID;
temp_id?: UUID; temp_id?: UUID;
@ -58,7 +60,7 @@ export interface IFile {
url: string; url: string;
size: number; size: number;
type: 'image' | 'text' | 'audio' | 'video'; type: IUploadType;
mime: string; mime: string;
createdAt?: string; createdAt?: string;
@ -68,6 +70,7 @@ export interface IFile {
export interface IFileWithUUID { export interface IFileWithUUID {
temp_id?: UUID; temp_id?: UUID;
file: File; file: File;
subject?: string;
target: string; target: string;
type: string; type: string;
} }

View file

@ -1,4 +1,4 @@
import { IFile } from '~/redux/types'; import { IFile, IUploadType } from '~/redux/types';
import { IUploadState, IUploadStatus } from './reducer'; import { IUploadState, IUploadStatus } from './reducer';
const prefix = 'UPLOAD.'; const prefix = 'UPLOAD.';
@ -11,7 +11,7 @@ export const UPLOAD_ACTIONS = {
DROP_STATUS: `${prefix}DROP_STATUS`, DROP_STATUS: `${prefix}DROP_STATUS`,
SET_STATUS: `${prefix}SET_STATUS`, SET_STATUS: `${prefix}SET_STATUS`,
ADD_FILE: `${prefix}ADD_FILE` ADD_FILE: `${prefix}ADD_FILE`,
}; };
export const EMPTY_FILE: IFile = { export const EMPTY_FILE: IFile = {
@ -25,7 +25,7 @@ export const EMPTY_FILE: IFile = {
url: 'https://cdn.arstechnica.net/wp-content/uploads/2017/09/mario-collage-800x450.jpg', url: 'https://cdn.arstechnica.net/wp-content/uploads/2017/09/mario-collage-800x450.jpg',
size: 2400000, size: 2400000,
type: 'image', type: 'image',
mime: 'image/jpeg' mime: 'image/jpeg',
}; };
export const EMPTY_UPLOAD_STATUS: IUploadStatus = { export const EMPTY_UPLOAD_STATUS: IUploadStatus = {
@ -37,5 +37,26 @@ export const EMPTY_UPLOAD_STATUS: IUploadStatus = {
progress: 0, progress: 0,
thumbnail_url: null, thumbnail_url: null,
type: null, type: null,
temp_id: null temp_id: null,
};
// for targeted cancellation
export const UPLOAD_SUBJECTS = {
EDITOR: 'editor',
COMMENT: 'comment',
AVATAR: 'avatar',
};
export const UPLOAD_TARGETS = {
NODES: 'nodes',
COMMENTS: 'comments',
PROFILES: 'profiles',
OTHER: 'other',
};
export const UPLOAD_TYPES: Record<string, IUploadType> = {
IMAGE: 'image',
AUDIO: 'audio',
VIDEO: 'video',
OTHER: 'other',
}; };

View file

@ -1,28 +1,35 @@
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 { postUploadFile } from './api'; import { postUploadFile } from './api';
import { UPLOAD_ACTIONS } from '~/redux/uploads/constants'; import { UPLOAD_ACTIONS } from '~/redux/uploads/constants';
import { import {
uploadUploadFiles, uploadSetStatus, uploadAddStatus, uploadDropStatus, uploadAddFile uploadUploadFiles,
uploadSetStatus,
uploadAddStatus,
uploadDropStatus,
uploadAddFile,
} from './actions'; } from './actions';
import { reqWrapper } from '../auth/sagas'; import { reqWrapper } from '../auth/sagas';
import { createUploader, uploadGetThumb, fakeUploader } 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, IFile, IUploadProgressHandler } from '../types'; import { IFileWithUUID, IFile, IUploadProgressHandler } from '../types';
function* uploadCall({ file, temp_id, target, type, onProgress }: IFileWithUUID & { onProgress: IUploadProgressHandler }) { function* uploadCall({
// return yield call(reqWrapper, fakeUploader, { file: { url: 'some', error: 'cant do this boss' }, onProgress, mustSucceed: true }); file,
return yield call( temp_id,
reqWrapper, target,
postUploadFile, type,
{ onProgress,
}: IFileWithUUID & { onProgress: IUploadProgressHandler }) {
return yield call(reqWrapper, postUploadFile, {
file, file,
temp_id, temp_id,
type, type,
target, target,
onProgress, onProgress,
} });
);
} }
function* onUploadProgress(chan) { function* onUploadProgress(chan) {
@ -45,16 +52,24 @@ function* uploadCancelWorker(id) {
function* uploadWorker({ function* uploadWorker({
file, temp_id, target, type, file, temp_id, target, type,
}: IFileWithUUID) { }: IFileWithUUID) {
const [promise, chan] = createUploader<Partial<IFileWithUUID>, Partial<IFileWithUUID>>(uploadCall, { temp_id, target, type }); const [promise, chan] = createUploader<Partial<IFileWithUUID>, Partial<IFileWithUUID>>(
uploadCall,
{ temp_id, target, type }
);
yield fork(onUploadProgress, chan); fork(onUploadProgress, chan);
return yield call(promise, { return yield call(promise, {
temp_id, file, target, type, temp_id,
file,
target,
type,
}); });
} }
function* uploadFile({ file, temp_id, type, target }: IFileWithUUID) { function* uploadFile({
file, temp_id, type, target, subject,
}: 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: {} };
} }
@ -74,10 +89,13 @@ function* uploadFile({ file, temp_id, type, target }: IFileWithUUID) {
) )
); );
const { const { result, cancel, cancel_editing } = yield race({
result, cancel, cancel_editing, save_inventory, result: call(uploadWorker, {
} = yield race({ file,
result: call(uploadWorker, { file, temp_id, target, type }), temp_id,
target,
type,
}),
cancel: call(uploadCancelWorker, temp_id), cancel: call(uploadCancelWorker, temp_id),
// subject_cancel: call(uploadSubjectCancelWorker, subject) // 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
@ -85,7 +103,7 @@ function* uploadFile({ file, temp_id, type, target }: IFileWithUUID) {
// 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) {
return yield put(uploadDropStatus(temp_id)); return yield put(uploadDropStatus(temp_id));
} }
@ -97,8 +115,6 @@ function* uploadFile({ file, temp_id, type, target }: IFileWithUUID) {
); );
} }
console.log('upload', data);
yield put( yield put(
uploadSetStatus(temp_id, { uploadSetStatus(temp_id, {
is_uploading: false, is_uploading: false,