feat: upload files and management

This commit is contained in:
zerosoul
2022-03-30 10:17:48 +08:00
parent 40492c5e72
commit bdc4ab2db1
32 changed files with 585 additions and 121 deletions
+98
View File
@@ -0,0 +1,98 @@
// import React from 'react'
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { useSelector } from "react-redux";
import Styled from "./styled";
import {
VideoPreview,
AudioPreview,
ImagePreview,
PdfPreview,
CodePreview,
} from "./preview";
import { getFileIcon, formatBytes } from "../../utils";
import IconDownload from "../../../assets/icons/download.svg";
dayjs.extend(relativeTime);
const renderPreview = (data) => {
const { file_type, name, content } = data;
let preview = null;
const checks = {
image: /^image/gi,
audio: /^audio/gi,
video: /^video/gi,
code: /(json|javascript|java|rb|c|php|xml|css|html)$/gi,
doc: /^text/gi,
pdf: /\/pdf$/gi,
};
const _arr = name.split(".");
const _type = file_type || _arr[_arr.length - 1];
switch (true) {
case checks.image.test(_type):
{
console.log("image");
preview = <ImagePreview url={content} />;
}
break;
case checks.pdf.test(_type):
preview = <PdfPreview url={content} />;
break;
case checks.code.test(_type):
preview = <CodePreview url={content} />;
break;
// case checks.doc.test(_type):
// icon = <IconDoc className="icon" />;
// break;
case checks.audio.test(_type):
preview = <AudioPreview url={content} />;
break;
case checks.video.test(_type):
preview = <VideoPreview url={content} />;
break;
// default:
// icon = <IconUnkown className="icon" />;
// break;
}
return preview;
};
export default function FileBox({
preview = false,
flex,
file_type,
name,
size,
created_at,
from_uid,
content,
}) {
const fromUser = useSelector((store) => store.contacts.byId[from_uid]);
const icon = getFileIcon(file_type, name);
if (!content || !fromUser || !name) return null;
console.log("file content", content, name, flex);
return (
<Styled className={flex ? "flex" : ""}>
<div className="basic">
{icon}
<div className="info">
<span className="name">{name}</span>
<span className="details">
<i className="size">{formatBytes(size)}</i>
<i className="time">{dayjs(created_at).fromNow()}</i>
<i className="from">
by <strong>{fromUser.name}</strong>
</i>
</span>
</div>
<a className="download" download={name} href={content}>
<IconDownload />
</a>
</div>
{preview && (
<div className="preview">
{renderPreview({ file_type, content, name })}
</div>
)}
</Styled>
);
}
@@ -0,0 +1,6 @@
import React from "react";
export default function Audio({ url = "" }) {
if (!url) return null;
return <audio src={url} />;
}
@@ -0,0 +1,26 @@
import { useState, useEffect } from "react";
import styled from "styled-components";
const Styled = styled.div`
background-color: #fff;
height: 218px;
padding: 15px 15px 0 15px;
line-height: 1.4;
overflow: scroll;
white-space: pre-wrap;
word-break: break-all;
`;
export default function Code({ url = "" }) {
const [content, setContent] = useState("");
useEffect(() => {
const getContent = async (url) => {
if (!url) return;
const resp = await fetch(url);
const txt = await resp.text();
setContent(txt);
};
getContent(url);
}, [url]);
if (!content) return null;
return <Styled>{content}</Styled>;
}
@@ -0,0 +1,19 @@
import React from "react";
import styled from "styled-components";
const Styled = styled.div`
height: 218px;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
`;
export default function Image({ url = "" }) {
if (!url) return null;
return (
<Styled>
<img src={url} />
</Styled>
);
}
@@ -0,0 +1,28 @@
import { useState } from "react";
import styled from "styled-components";
import { Document, Page } from "react-pdf";
const Styled = styled.div`
height: 218px;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
`;
export default function Pdf({ url = "" }) {
const [pageNumber, setPageNumber] = useState(1);
const [numPages, setNumPages] = useState(null);
if (!url) return null;
const onDocumentLoadSuccess = ({ numPages }) => {
setNumPages(numPages);
};
console.log("pdf url", url);
return (
<Styled>
<Document file={url} onLoadSuccess={onDocumentLoadSuccess}>
<Page pageNumber={pageNumber} />
</Document>
</Styled>
);
}
@@ -0,0 +1,18 @@
import React from "react";
import styled from "styled-components";
const Styled = styled.div`
height: 218px;
video {
width: 100%;
height: 100%;
object-fit: cover;
}
`;
export default function Video({ url = "" }) {
if (!url) return null;
return (
<Styled>
<video controls src={url} />
</Styled>
);
}
@@ -0,0 +1,7 @@
import VideoPreview from "./Video";
import AudioPreview from "./Audio";
import ImagePreview from "./Image";
import PdfPreview from "./Pdf";
import CodePreview from "./Code";
export { VideoPreview, AudioPreview, ImagePreview, PdfPreview, CodePreview };
+55
View File
@@ -0,0 +1,55 @@
import styled from "styled-components";
const Styled = styled.div`
background: #f3f4f6;
border: 1px solid #d4d4d4;
box-sizing: border-box;
border-radius: 6px;
width: 370px;
max-height: 281px;
height: fit-content;
overflow: hidden;
&.flex {
width: 100%;
}
.basic {
padding: 8px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
.icon {
width: 36px;
height: 48px;
}
.info {
display: flex;
flex-direction: column;
gap: 4px;
width: 100%;
.name {
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #1c1c1e;
}
.details {
font-weight: 400;
font-size: 12px;
line-height: 18px;
color: #616161;
display: flex;
gap: 16px;
.from strong {
font-weight: bold;
}
}
}
.download {
white-space: nowrap;
}
}
.preview {
/* todo */
}
`;
export default Styled;
-81
View File
@@ -1,81 +0,0 @@
// import React from 'react'
import styled from "styled-components";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { useSelector } from "react-redux";
import { getFileIcon, formatBytes } from "../../utils";
import IconDownload from "../../../assets/icons/download.svg";
dayjs.extend(relativeTime);
const Styled = styled.div`
padding: 8px;
background: #f3f4f6;
border: 1px solid #d4d4d4;
box-sizing: border-box;
border-radius: 6px;
width: 370px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
.icon {
width: 36px;
height: 48px;
}
.info {
display: flex;
flex-direction: column;
gap: 4px;
width: 100%;
.name {
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #1c1c1e;
}
.details {
font-weight: 400;
font-size: 12px;
line-height: 18px;
color: #616161;
display: flex;
gap: 16px;
.from strong {
font-weight: bold;
}
}
}
.download {
white-space: nowrap;
}
`;
export default function FileBox({
file_type,
name,
size,
created_at,
from_uid,
content,
}) {
const fromUser = useSelector((store) => store.contacts.byId[from_uid]);
const icon = getFileIcon(file_type, name);
if (!content || !fromUser || !name) return null;
console.log("file content", content, name);
return (
<Styled>
{icon}
<div className="info">
<span className="name">{name}</span>
<span className="details">
<i className="size">{formatBytes(size)}</i>
<i className="time">{dayjs(created_at).fromNow()}</i>
<i className="from">
by <strong>{fromUser.name}</strong>
</i>
</span>
</div>
<a className="download" download={name} href={content}>
<IconDownload />
</a>
</Styled>
);
}
+15 -2
View File
@@ -9,7 +9,14 @@ export default function PreviewMessage({ mid = 0 }) {
return { msg: store.message[mid], contactsData: store.contacts.byId };
});
if (!msg) return null;
const { from_uid, created_at, content_type, content, thumbnail } = msg;
const {
from_uid,
created_at,
content_type,
content,
thumbnail,
properties,
} = msg;
const { name, avatar } = contactsData[from_uid];
return (
<StyledWrapper className={`preview`}>
@@ -24,7 +31,13 @@ export default function PreviewMessage({ mid = 0 }) {
</i>
</div>
<div className={`down`}>
{renderContent({ content_type, content, thumbnail })}
{renderContent({
content_type,
content,
thumbnail,
from_uid,
properties,
})}
</div>
</div>
</StyledWrapper>
@@ -2,7 +2,7 @@ import Linkify from "react-linkify";
import dayjs from "dayjs";
import MrakdownRender from "../MrakdownRender";
import { getDefaultSize } from "../../utils";
import FileBox from "./FileBox";
import FileBox from "../FileBox";
const renderContent = ({
from_uid,
created_at,
+1
View File
@@ -86,6 +86,7 @@ export default function useUploadImageMessage({
}
sliceUploadedCountRef.current = sliceUploadedCountRef.current + 1;
} catch (error) {
console.log("upload file error", error);
return;
}
}
@@ -9,8 +9,13 @@ import {
updateMessage,
} from "../../../app/slices/message";
import { toggleReactionMessage } from "../../../app/slices/message.reaction";
import {
addFileMessage,
removeFileMessage,
} from "../../../app/slices/message.file";
import { addUserMsg, removeUserMsg } from "../../../app/slices/message.user";
import { updateAfterMid } from "../../../app/slices/footprint";
import { ContentTypes } from "../../../app/config";
const handler = (data, dispatch, currState) => {
const {
mid,
@@ -72,6 +77,10 @@ const handler = (data, dispatch, currState) => {
local_id: properties ? properties.local_id : null,
})
);
// 加到file message 列表
if (content_type == ContentTypes.file) {
dispatch(addFileMessage(mid));
}
// }
});
}
@@ -121,6 +130,10 @@ const handler = (data, dispatch, currState) => {
dispatch(removeContextMessage({ id, mid: detailMid }));
dispatch(removeMessage(detailMid));
});
// 从file message 列表移除
if (content_type == ContentTypes.file) {
dispatch(removeFileMessage(detailMid));
}
}
break;
case "edit":