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

refactored upload component

This commit is contained in:
Fedor Katurov 2019-10-16 11:00:33 +07:00
parent 3fdf14d680
commit 1f9c6ac8f7
5 changed files with 151 additions and 147 deletions

View file

@ -1,4 +1,4 @@
import React, { FC, ChangeEventHandler } from 'react'; import React, { FC } from 'react';
import * as styles from './styles.scss'; import * as styles from './styles.scss';
import { INode } from '~/redux/types'; import { INode } from '~/redux/types';
import { EditorUploadButton } from '~/components/editors/EditorUploadButton'; import { EditorUploadButton } from '~/components/editors/EditorUploadButton';
@ -6,12 +6,13 @@ import { EditorUploadButton } from '~/components/editors/EditorUploadButton';
interface IProps { interface IProps {
data: INode; data: INode;
setData: (val: INode) => void; setData: (val: INode) => void;
onUpload: ChangeEventHandler<HTMLInputElement>; temp: string[];
setTemp: (val: string[]) => void;
} }
const EditorPanel: FC<IProps> = ({ onUpload }) => ( const EditorPanel: FC<IProps> = ({ data, setData, temp, setTemp }) => (
<div className={styles.panel}> <div className={styles.panel}>
<EditorUploadButton onUpload={onUpload} /> <EditorUploadButton data={data} setData={setData} temp={temp} setTemp={setTemp} />
</div> </div>
); );

View file

@ -1,21 +1,127 @@
import React, { FC, ChangeEventHandler } from 'react'; import React, { FC, useCallback, useEffect, useState } from 'react';
import * as styles from './styles.scss'; import * as styles from './styles.scss';
import { Icon } from '~/components/input/Icon'; import { Icon } from '~/components/input/Icon';
import { IFileWithUUID, INode, IFile } from '~/redux/types';
import uuid from 'uuid4';
import { UPLOAD_SUBJECTS, UPLOAD_TARGETS, UPLOAD_TYPES } from '~/redux/uploads/constants';
import * as UPLOAD_ACTIONS from '~/redux/uploads/actions';
import assocPath from 'ramda/es/assocPath';
import append from 'ramda/es/append';
import { selectUploads } from '~/redux/uploads/selectors';
import { connect } from 'react-redux';
interface IProps { const mapStateToProps = state => {
onUpload?: ChangeEventHandler<HTMLInputElement>; const { statuses, files } = selectUploads(state);
}
const EditorUploadButton: FC<IProps> = ({ return { statuses, files };
onUpload, };
}) => (
<div className={styles.wrap}>
<input type="file" onChange={onUpload} accept="image/*" multiple />
<div className={styles.icon}> const mapDispatchToProps = {
<Icon size={32} icon="plus" /> uploadUploadFiles: UPLOAD_ACTIONS.uploadUploadFiles,
};
type IProps = ReturnType<typeof mapStateToProps> &
typeof mapDispatchToProps & {
data: INode;
setData: (val: INode) => void;
temp: string[];
setTemp: (val: string[]) => void;
};
const EditorUploadButtonUnconnected: FC<IProps> = ({
data,
setData,
temp,
setTemp,
statuses,
files,
uploadUploadFiles,
}) => {
const eventPreventer = useCallback(event => event.preventDefault(), []);
const onUpload = useCallback(
(uploads: File[]) => {
const items: IFileWithUUID[] = Array.from(uploads).map(
(file: File): IFileWithUUID => ({
file,
temp_id: uuid(),
subject: UPLOAD_SUBJECTS.EDITOR,
target: UPLOAD_TARGETS.NODES,
type: UPLOAD_TYPES.IMAGE,
})
);
const temps = items.map(file => file.temp_id);
setTemp([...temp, ...temps]);
uploadUploadFiles(items);
},
[setTemp, uploadUploadFiles, temp]
);
const onFileAdd = useCallback(
(file: IFile) => {
setData(assocPath(['files'], append(file, data.files), data));
},
[data, setData]
);
// const onDrop = useCallback(
// (event: React.DragEvent<HTMLDivElement>) => {
// event.preventDefault();
// if (!event.dataTransfer || !event.dataTransfer.files || !event.dataTransfer.files.length)
// return;
// onUpload(Array.from(event.dataTransfer.files));
// },
// [onUpload]
// );
useEffect(() => {
window.addEventListener('dragover', eventPreventer, false);
window.addEventListener('drop', eventPreventer, false);
return () => {
window.removeEventListener('dragover', eventPreventer, false);
window.removeEventListener('drop', eventPreventer, false);
};
}, [eventPreventer]);
useEffect(() => {
Object.entries(statuses).forEach(([id, status]) => {
if (temp.includes(id) && !!status.uuid && files[status.uuid]) {
onFileAdd(files[status.uuid]);
setTemp(temp.filter(el => el !== id));
}
});
}, [statuses, files, temp, onFileAdd]);
const onInputChange = useCallback(
event => {
event.preventDefault();
if (!event.target.files || !event.target.files.length) return;
onUpload(Array.from(event.target.files));
},
[onUpload]
);
return (
<div className={styles.wrap}>
<input type="file" onChange={onInputChange} accept="image/*" multiple />
<div className={styles.icon}>
<Icon size={32} icon="plus" />
</div>
</div> </div>
</div> );
); };
const EditorUploadButton = connect(
mapStateToProps,
mapDispatchToProps
)(EditorUploadButtonUnconnected);
export { EditorUploadButton }; export { EditorUploadButton };

View file

@ -1,10 +1,12 @@
import React, { FC, ChangeEventHandler, DragEventHandler } from 'react'; import React, { FC, ChangeEventHandler, DragEventHandler, useCallback } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { INode } from '~/redux/types'; import { INode } 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 { ImageGrid } from '~/components/editors/ImageGrid'; import { ImageGrid } from '~/components/editors/ImageGrid';
import { IUploadStatus } from '~/redux/uploads/reducer'; import { IUploadStatus } from '~/redux/uploads/reducer';
import { moveArrItem } from '~/utils/fn';
import assocPath from 'ramda/es/assocPath';
const mapStateToProps = selectUploads; const mapStateToProps = selectUploads;
const mapDispatchToProps = { const mapDispatchToProps = {
@ -21,19 +23,9 @@ type IProps = ReturnType<typeof mapStateToProps> &
onInputChange: ChangeEventHandler<HTMLInputElement>; onInputChange: ChangeEventHandler<HTMLInputElement>;
}; };
const ImageEditorUnconnected: FC<IProps> = ({ const ImageEditorUnconnected: FC<IProps> = ({ data, setData, pending_files }) => {
data, return <ImageGrid data={data} setData={setData} locked={pending_files} />;
onFileMove, };
onInputChange,
pending_files,
}) => (
<ImageGrid
onFileMove={onFileMove}
items={data.files}
locked={pending_files}
onUpload={onInputChange}
/>
);
const ImageEditor = connect( const ImageEditor = connect(
mapStateToProps, mapStateToProps,

View file

@ -1,16 +1,19 @@
import React, { FC, useCallback, ChangeEventHandler, DragEventHandler } from 'react'; import React, { FC, useCallback } from 'react';
import { SortableContainer, SortableElement } from 'react-sortable-hoc'; import { SortableContainer, SortableElement, SortEvent, SortEnd } from 'react-sortable-hoc';
import * as styles from './styles.scss'; import * as styles from './styles.scss';
import { ImageUpload } from '~/components/upload/ImageUpload'; import { ImageUpload } from '~/components/upload/ImageUpload';
import { IFile } from '~/redux/types'; import { IFile, INode } from '~/redux/types';
import { IUploadStatus } from '~/redux/uploads/reducer'; import { IUploadStatus } from '~/redux/uploads/reducer';
import { getURL } from '~/utils/dom'; import { getURL } from '~/utils/dom';
import assocPath from 'ramda/es/assocPath';
import { moveArrItem } from '~/utils/fn';
interface IProps { interface IProps {
items: IFile[]; data: INode;
setData: (val: INode) => void;
locked: IUploadStatus[]; locked: IUploadStatus[];
onFileMove: (o: number, n: number) => void; // items: IFile[];
onUpload?: ChangeEventHandler<HTMLInputElement>; // onFileMove: (o: number, n: number) => void;
} }
const SortableItem = SortableElement(({ children }) => ( const SortableItem = SortableElement(({ children }) => (
@ -18,14 +21,7 @@ const SortableItem = SortableElement(({ children }) => (
)); ));
const SortableList = SortableContainer( const SortableList = SortableContainer(
({ ({ items, locked }: { items: IFile[]; locked: IUploadStatus[] }) => (
items,
locked,
}: {
items: IFile[];
locked: IUploadStatus[];
onUpload: ChangeEventHandler<HTMLInputElement>;
}) => (
<div className={styles.grid}> <div className={styles.grid}>
{items.map((file, index) => ( {items.map((file, index) => (
<SortableItem key={file.id} index={index} collection={0}> <SortableItem key={file.id} index={index} collection={0}>
@ -42,18 +38,20 @@ const SortableList = SortableContainer(
) )
); );
const ImageGrid: FC<IProps> = ({ items, locked, onFileMove, onUpload }) => { const ImageGrid: FC<IProps> = ({ data, setData, locked }) => {
const onMove = useCallback(({ oldIndex, newIndex }) => onFileMove(oldIndex, newIndex), [ const onMove = useCallback(
onFileMove, ({ oldIndex, newIndex }: SortEnd) => {
]); setData(assocPath(['files'], moveArrItem(oldIndex, newIndex, data.files), data));
},
[data, setData]
);
return ( return (
<SortableList <SortableList
onSortEnd={onMove} onSortEnd={onMove}
axis="xy" axis="xy"
items={items} items={data.files}
locked={locked} locked={locked}
onUpload={onUpload}
pressDelay={window.innerWidth < 768 ? 200 : 0} pressDelay={window.innerWidth < 768 ? 200 : 0}
helperClass={styles.helper} helperClass={styles.helper}
/> />

View file

@ -14,12 +14,8 @@ import * as styles from './styles.scss';
import { selectNode } from '~/redux/node/selectors'; 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 { moveArrItem } from '~/utils/fn';
import { IFile, IFileWithUUID } from '~/redux/types';
import * as UPLOAD_ACTIONS from '~/redux/uploads/actions';
import * as NODE_ACTIONS from '~/redux/node/actions'; import * as NODE_ACTIONS from '~/redux/node/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);
@ -29,101 +25,15 @@ const mapStateToProps = state => {
}; };
const mapDispatchToProps = { const mapDispatchToProps = {
uploadUploadFiles: UPLOAD_ACTIONS.uploadUploadFiles,
nodeSave: NODE_ACTIONS.nodeSave, nodeSave: NODE_ACTIONS.nodeSave,
}; };
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, statuses, nodeSave }) => {
onRequestClose,
editor,
files,
statuses,
uploadUploadFiles,
nodeSave,
}) => {
const [data, setData] = useState(editor); const [data, setData] = useState(editor);
const eventPreventer = useCallback(event => event.preventDefault(), []);
const [temp, setTemp] = useState([]); const [temp, setTemp] = useState([]);
const onUpload = useCallback(
(uploads: File[]) => {
const items: IFileWithUUID[] = Array.from(uploads).map(
(file: File): IFileWithUUID => ({
file,
temp_id: uuid(),
subject: UPLOAD_SUBJECTS.EDITOR,
target: UPLOAD_TARGETS.NODES,
type: UPLOAD_TYPES.IMAGE,
})
);
const temps = items.map(file => file.temp_id);
setTemp([...temp, ...temps]);
uploadUploadFiles(items);
},
[setTemp, uploadUploadFiles, temp]
);
const onFileMove = useCallback(
(old_index: number, new_index: number) => {
setData(assocPath(['files'], moveArrItem(old_index, new_index, data.files), data));
},
[data, setData]
);
const onFileAdd = useCallback(
(file: IFile) => {
setData(assocPath(['files'], append(file, data.files), data));
},
[data, setData]
);
const onDrop = useCallback(
(event: React.DragEvent<HTMLDivElement>) => {
event.preventDefault();
if (!event.dataTransfer || !event.dataTransfer.files || !event.dataTransfer.files.length)
return;
onUpload(Array.from(event.dataTransfer.files));
},
[onUpload]
);
const onInputChange = useCallback(
event => {
event.preventDefault();
if (!event.target.files || !event.target.files.length) return;
onUpload(Array.from(event.target.files));
},
[onUpload]
);
useEffect(() => {
window.addEventListener('dragover', eventPreventer, false);
window.addEventListener('drop', eventPreventer, false);
return () => {
window.removeEventListener('dragover', eventPreventer, false);
window.removeEventListener('drop', eventPreventer, false);
};
}, [eventPreventer]);
useEffect(() => {
Object.entries(statuses).forEach(([id, status]) => {
if (temp.includes(id) && !!status.uuid && files[status.uuid]) {
onFileAdd(files[status.uuid]);
setTemp(temp.filter(el => el !== id));
}
});
}, [statuses, files, temp, onFileAdd]);
const setTitle = useCallback( const setTitle = useCallback(
title => { title => {
setData({ ...data, title }); setData({ ...data, title });
@ -141,7 +51,7 @@ const EditorDialogUnconnected: FC<IProps> = ({
const buttons = ( const buttons = (
<Padder style={{ position: 'relative' }}> <Padder style={{ position: 'relative' }}>
<EditorPanel data={data} setData={setData} onUpload={onInputChange} /> <EditorPanel data={data} setData={setData} temp={temp} setTemp={setTemp} />
<Group horizontal> <Group horizontal>
<InputText title="Название" value={data.title} handler={setTitle} autoFocus /> <InputText title="Название" value={data.title} handler={setTitle} autoFocus />
@ -156,14 +66,11 @@ const EditorDialogUnconnected: FC<IProps> = ({
return ( return (
<form onSubmit={onSubmit} className={styles.form}> <form onSubmit={onSubmit} className={styles.form}>
<ScrollDialog buttons={buttons} width={860} onClose={onRequestClose}> <ScrollDialog buttons={buttons} width={860} onClose={onRequestClose}>
<div className={styles.editor} onDrop={onDrop}> <div className={styles.editor}>
<ImageEditor <ImageEditor
data={data} data={data}
pending_files={temp.filter(id => !!statuses[id]).map(id => statuses[id])} pending_files={temp.filter(id => !!statuses[id]).map(id => statuses[id])}
setData={setData} setData={setData}
onUpload={onInputChange}
onFileMove={onFileMove}
onInputChange={onInputChange}
/> />
</div> </div>
</ScrollDialog> </ScrollDialog>