revite/src/components/common/messaging/bars/FilePreview.tsx

231 lines
5.7 KiB
TypeScript
Raw Normal View History

2021-07-05 06:23:23 -04:00
import { XCircle, Plus, Share, X, File } from "@styled-icons/boxicons-regular";
import styled from "styled-components";
2021-07-05 06:23:23 -04:00
import { Text } from "preact-i18n";
import { useEffect, useState } from "preact/hooks";
import { determineFileSize } from "../../../../lib/fileSize";
import { CAN_UPLOAD_AT_ONCE, UploadState } from "../MessageBox";
interface Props {
2021-07-05 06:25:20 -04:00
state: UploadState;
addFile: () => void;
removeFile: (index: number) => void;
}
const Container = styled.div`
2021-07-05 06:25:20 -04:00
gap: 4px;
padding: 8px;
display: flex;
user-select: none;
flex-direction: column;
background: var(--message-box);
`;
const Carousel = styled.div`
2021-07-05 06:25:20 -04:00
gap: 8px;
display: flex;
overflow-x: scroll;
flex-direction: row;
`;
const Entry = styled.div`
2021-07-05 06:25:20 -04:00
display: flex;
flex-direction: column;
&.fade {
opacity: 0.4;
}
span.fn {
margin: auto;
font-size: 0.8em;
overflow: hidden;
max-width: 180px;
text-align: center;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--secondary-foreground);
}
span.size {
font-size: 0.6em;
color: var(--tertiary-foreground);
text-align: center;
}
`;
const Description = styled.div`
2021-07-05 06:25:20 -04:00
gap: 4px;
display: flex;
font-size: 0.9em;
align-items: center;
color: var(--secondary-foreground);
`;
const Divider = styled.div`
2021-07-05 06:25:20 -04:00
width: 4px;
height: 130px;
flex-shrink: 0;
2021-07-10 10:42:13 -04:00
border-radius: var(--border-radius);
2021-07-05 06:25:20 -04:00
background: var(--tertiary-background);
`;
const EmptyEntry = styled.div`
2021-07-05 06:25:20 -04:00
width: 100px;
height: 100px;
display: grid;
flex-shrink: 0;
cursor: pointer;
place-items: center;
2021-07-10 10:42:13 -04:00
border-radius: var(--border-radius);
2021-07-05 06:25:20 -04:00
background: var(--primary-background);
transition: 0.1s ease background-color;
&:hover {
background: var(--secondary-background);
}
`;
const PreviewBox = styled.div`
2021-07-05 06:25:20 -04:00
display: grid;
grid-template: "main" 100px / minmax(100px, 1fr);
justify-items: center;
cursor: pointer;
2021-07-10 10:42:13 -04:00
overflow: hidden;
border-radius: var(--border-radius);
background: var(--primary-background);
2021-07-05 06:25:20 -04:00
.icon,
.overlay {
grid-area: main;
}
.icon {
height: 100px;
margin-bottom: 4px;
object-fit: contain;
}
.overlay {
display: grid;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
opacity: 0;
visibility: hidden;
transition: 0.1s ease opacity;
}
&:hover {
.overlay {
visibility: visible;
opacity: 1;
background-color: rgba(0, 0, 0, 0.8);
}
}
2021-07-05 06:23:23 -04:00
`;
function FileEntry({
2021-07-05 06:25:20 -04:00
file,
remove,
index,
2021-07-05 06:23:23 -04:00
}: {
2021-07-05 06:25:20 -04:00
file: File;
remove?: () => void;
index: number;
2021-07-05 06:23:23 -04:00
}) {
2021-07-05 06:25:20 -04:00
if (!file.type.startsWith("image/"))
return (
<Entry className={index >= CAN_UPLOAD_AT_ONCE ? "fade" : ""}>
<PreviewBox onClick={remove}>
<EmptyEntry className="icon">
<File size={36} />
</EmptyEntry>
<div class="overlay">
<XCircle size={36} />
</div>
</PreviewBox>
<span class="fn">{file.name}</span>
<span class="size">{determineFileSize(file.size)}</span>
</Entry>
);
const [url, setURL] = useState("");
useEffect(() => {
const url: string = URL.createObjectURL(file);
2021-07-05 06:25:20 -04:00
setURL(url);
return () => URL.revokeObjectURL(url);
}, [file]);
return (
<Entry className={index >= CAN_UPLOAD_AT_ONCE ? "fade" : ""}>
<PreviewBox onClick={remove}>
<img class="icon" src={url} alt={file.name} loading="eager" />
2021-07-05 06:25:20 -04:00
<div class="overlay">
<XCircle size={36} />
</div>
</PreviewBox>
<span class="fn">{file.name}</span>
<span class="size">{determineFileSize(file.size)}</span>
</Entry>
);
}
export default function FilePreview({ state, addFile, removeFile }: Props) {
2021-07-05 06:25:20 -04:00
if (state.type === "none") return null;
return (
<Container>
<Carousel>
{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>
)}
</Carousel>
{state.type === "uploading" && (
<Description>
<Share size={24} />
<Text id="app.main.channel.uploading_file" /> (
{state.percent}%)
</Description>
)}
{state.type === "sending" && (
<Description>
<Share size={24} />
Sending...
</Description>
)}
{state.type === "failed" && (
<Description>
<X size={24} />
<Text id={`error.${state.error}`} />
</Description>
)}
</Container>
);
}