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:
parent
db0a94b581
commit
38d56838f2
5 changed files with 127 additions and 58 deletions
|
@ -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} />
|
||||||
|
|
|
@ -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 }));
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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',
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue