mirror of
https://github.com/revoltchat/revite.git
synced 2024-11-13 18:59:22 -05:00
Add support for multiple attachment upload.
This commit is contained in:
parent
99572066c8
commit
3c8c9a672f
5 changed files with 49 additions and 26 deletions
|
@ -73,7 +73,7 @@
|
||||||
"react-scroll": "^1.8.2",
|
"react-scroll": "^1.8.2",
|
||||||
"react-tippy": "^1.4.0",
|
"react-tippy": "^1.4.0",
|
||||||
"redux": "^4.1.0",
|
"redux": "^4.1.0",
|
||||||
"revolt.js": "4.3.0",
|
"revolt.js": "4.3.1-alpha.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"sass": "^1.35.1",
|
"sass": "^1.35.1",
|
||||||
"shade-blend-color": "^1.0.0",
|
"shade-blend-color": "^1.0.0",
|
||||||
|
|
|
@ -50,6 +50,9 @@ const Action = styled.div`
|
||||||
place-items: center;
|
place-items: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// ! FIXME: add to app config and load from app config
|
||||||
|
export const CAN_UPLOAD_AT_ONCE = 5;
|
||||||
|
|
||||||
function MessageBox({ channel, draft, dispatcher }: Props) {
|
function MessageBox({ channel, draft, dispatcher }: Props) {
|
||||||
const [ uploadState, setUploadState ] = useState<UploadState>({ type: 'none' });
|
const [ uploadState, setUploadState ] = useState<UploadState>({ type: 'none' });
|
||||||
const [typing, setTyping] = useState<boolean | number>(false);
|
const [typing, setTyping] = useState<boolean | number>(false);
|
||||||
|
@ -133,7 +136,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
|
||||||
|
|
||||||
async function sendFile(content: string) {
|
async function sendFile(content: string) {
|
||||||
if (uploadState.type !== 'attached') return;
|
if (uploadState.type !== 'attached') return;
|
||||||
let attachments = [];
|
let attachments: string[] = [];
|
||||||
|
|
||||||
const cancel = Axios.CancelToken.source();
|
const cancel = Axios.CancelToken.source();
|
||||||
const files = uploadState.files;
|
const files = uploadState.files;
|
||||||
|
@ -141,8 +144,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
|
||||||
setUploadState({ type: "uploading", files, percent: 0, cancel });
|
setUploadState({ type: "uploading", files, percent: 0, cancel });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (let i=0;i<files.length;i++) {
|
for (let i=0;i<files.length&&i<CAN_UPLOAD_AT_ONCE;i++) {
|
||||||
if (i>0)continue; // ! FIXME: temp, allow multiple uploads on server
|
|
||||||
const file = files[i];
|
const file = files[i];
|
||||||
attachments.push(
|
attachments.push(
|
||||||
await uploadFile(client.configuration!.features.autumn.url, 'attachments', file, {
|
await uploadFile(client.configuration!.features.autumn.url, 'attachments', file, {
|
||||||
|
@ -150,7 +152,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
|
||||||
setUploadState({
|
setUploadState({
|
||||||
type: "uploading",
|
type: "uploading",
|
||||||
files,
|
files,
|
||||||
percent: Math.round(((i * 100) + (100 * e.loaded) / e.total) / (files.length)),
|
percent: Math.round(((i * 100) + (100 * e.loaded) / e.total) / Math.min(files.length, CAN_UPLOAD_AT_ONCE)),
|
||||||
cancel
|
cancel
|
||||||
}),
|
}),
|
||||||
cancelToken: cancel.token
|
cancelToken: cancel.token
|
||||||
|
@ -184,7 +186,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
|
||||||
await client.channels.sendMessage(channel._id, {
|
await client.channels.sendMessage(channel._id, {
|
||||||
content,
|
content,
|
||||||
nonce,
|
nonce,
|
||||||
attachment: attachments[0] // ! FIXME: temp, allow multiple uploads on server
|
attachments // ! FIXME: temp, allow multiple uploads on server
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setUploadState({
|
setUploadState({
|
||||||
|
@ -197,8 +199,16 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
setMessage();
|
setMessage();
|
||||||
|
|
||||||
|
if (files.length > CAN_UPLOAD_AT_ONCE) {
|
||||||
|
setUploadState({
|
||||||
|
type: "attached",
|
||||||
|
files: files.slice(CAN_UPLOAD_AT_ONCE)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
setUploadState({ type: "none" });
|
setUploadState({ type: "none" });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function startTyping() {
|
function startTyping() {
|
||||||
if (typeof typing === 'number' && + new Date() < typing) return;
|
if (typeof typing === 'number' && + new Date() < typing) return;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Text } from "preact-i18n";
|
import { Text } from "preact-i18n";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { UploadState } from "../MessageBox";
|
import { CAN_UPLOAD_AT_ONCE, UploadState } from "../MessageBox";
|
||||||
import { useEffect, useState } from 'preact/hooks';
|
import { useEffect, useState } from 'preact/hooks';
|
||||||
import { XCircle, Plus, Share, X } from "@styled-icons/feather";
|
|
||||||
import { determineFileSize } from '../../../../lib/fileSize';
|
import { determineFileSize } from '../../../../lib/fileSize';
|
||||||
|
import { XCircle, Plus, Share, X, FileText } from "@styled-icons/feather";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
state: UploadState,
|
state: UploadState,
|
||||||
|
@ -39,6 +39,10 @@ const Entry = styled.div`
|
||||||
background: var(--secondary-background);
|
background: var(--secondary-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.fade {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
span.fn {
|
span.fn {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
font-size: .8em;
|
font-size: .8em;
|
||||||
|
@ -56,7 +60,7 @@ const Entry = styled.div`
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
div:first-child {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 0;
|
height: 0;
|
||||||
|
|
||||||
|
@ -69,7 +73,7 @@ const Entry = styled.div`
|
||||||
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: 0.1s ease opacity;
|
transition: 0.1s ease opacity;
|
||||||
background: rgba(0, 0, 0, 0.5);
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
@ -86,6 +90,14 @@ const Description = styled.div`
|
||||||
color: var(--secondary-foreground);
|
color: var(--secondary-foreground);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const Divider = styled.div`
|
||||||
|
width: 4px;
|
||||||
|
height: 130px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--tertiary-background);
|
||||||
|
`;
|
||||||
|
|
||||||
const EmptyEntry = styled.div`
|
const EmptyEntry = styled.div`
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
|
@ -102,11 +114,13 @@ const EmptyEntry = styled.div`
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function FileEntry({ file, remove }: { file: File, remove?: () => void }) {
|
function FileEntry({ file, remove, index }: { file: File, remove?: () => void, index: number }) {
|
||||||
if (!file.type.startsWith('image/')) return (
|
if (!file.type.startsWith('image/')) return (
|
||||||
<Entry>
|
<Entry className={index >= CAN_UPLOAD_AT_ONCE ? 'fade' : ''}>
|
||||||
<div><div onClick={remove}><XCircle size={36} /></div></div>
|
<div><div onClick={remove}><XCircle size={36} /></div></div>
|
||||||
|
<EmptyEntry>
|
||||||
|
<FileText size={36} />
|
||||||
|
</EmptyEntry>
|
||||||
<span class="fn">{file.name}</span>
|
<span class="fn">{file.name}</span>
|
||||||
<span class="size">{determineFileSize(file.size)}</span>
|
<span class="size">{determineFileSize(file.size)}</span>
|
||||||
</Entry>
|
</Entry>
|
||||||
|
@ -121,7 +135,7 @@ function FileEntry({ file, remove }: { file: File, remove?: () => void }) {
|
||||||
}, [ file ]);
|
}, [ file ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Entry>
|
<Entry className={index >= CAN_UPLOAD_AT_ONCE ? 'fade' : ''}>
|
||||||
{ remove && <div><div onClick={remove}><XCircle size={36} /></div></div> }
|
{ remove && <div><div onClick={remove}><XCircle size={36} /></div></div> }
|
||||||
<img src={url}
|
<img src={url}
|
||||||
alt={file.name} />
|
alt={file.name} />
|
||||||
|
@ -137,10 +151,14 @@ export default function FilePreview({ state, addFile, removeFile }: Props) {
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Carousel>
|
<Carousel>
|
||||||
{ state.files.map((file, index) => <FileEntry file={file} key={file.name} remove={state.type === 'attached' ? () => removeFile(index) : undefined} />) }
|
{ state.files.map((file, index) =>
|
||||||
|
<>
|
||||||
|
{ index === CAN_UPLOAD_AT_ONCE && <Divider /> }
|
||||||
|
<FileEntry index={index} file={file} key={file.name} remove={state.type === 'attached' ? () => removeFile(index) : undefined} />
|
||||||
|
</>
|
||||||
|
) }
|
||||||
{ state.type === 'attached' && <EmptyEntry onClick={addFile}><Plus size={48} /></EmptyEntry> }
|
{ state.type === 'attached' && <EmptyEntry onClick={addFile}><Plus size={48} /></EmptyEntry> }
|
||||||
</Carousel>
|
</Carousel>
|
||||||
{ state.files.length > 1 && state.type === 'attached' && <Description>Warning: Only first file will be uploaded, this will be changed in a future update.</Description> }
|
|
||||||
{ state.type === 'uploading' && <Description>
|
{ state.type === 'uploading' && <Description>
|
||||||
<Share size={24} />
|
<Share size={24} />
|
||||||
<Text id="app.main.channel.uploading_file" /> ({state.percent}%)
|
<Text id="app.main.channel.uploading_file" /> ({state.percent}%)
|
||||||
|
|
|
@ -114,11 +114,6 @@ function Context({ auth, sync, children, dispatcher }: Props) {
|
||||||
dispatcher({ type: "LOGOUT" });
|
dispatcher({ type: "LOGOUT" });
|
||||||
|
|
||||||
delete client.user;
|
delete client.user;
|
||||||
// ! FIXME: write procedure client.clear();
|
|
||||||
client.users.clear();
|
|
||||||
client.channels.clear();
|
|
||||||
client.servers.clear();
|
|
||||||
client.servers.members.clear();
|
|
||||||
dispatcher({ type: "RESET" });
|
dispatcher({ type: "RESET" });
|
||||||
|
|
||||||
openScreen({ id: "none" });
|
openScreen({ id: "none" });
|
||||||
|
|
|
@ -3369,10 +3369,10 @@ reusify@^1.0.4:
|
||||||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
|
||||||
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
|
||||||
|
|
||||||
revolt.js@4.3.0:
|
revolt.js@4.3.1-alpha.0:
|
||||||
version "4.3.0"
|
version "4.3.1-alpha.0"
|
||||||
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.0.tgz#dc396470da82dd58eac74150ed9e3d64f67c28db"
|
resolved "https://registry.yarnpkg.com/revolt.js/-/revolt.js-4.3.1-alpha.0.tgz#21abb0706852468a0b7991a80d81093f547d25f3"
|
||||||
integrity sha512-QFD0KQMk6e6bOioZJSSSnzgtx76yJLFSp9LyM1fIIelP02vrMpU1wO7s89lE+7jljh7SVgJqyCfZmlshdyb7Ew==
|
integrity sha512-YwDdDgioVYeBYkgZtgtXM37//96WmT18XVPJ7cBJzDQ3GWUKKPrw4VFjmi9FSh0ksfgfkSIrA7/hqmztZWbnVw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@insertish/mutable" "1.1.0"
|
"@insertish/mutable" "1.1.0"
|
||||||
axios "^0.19.2"
|
axios "^0.19.2"
|
||||||
|
|
Loading…
Reference in a new issue