import { XCircle, Plus, Share, X, File } from "@styled-icons/boxicons-regular"; import styled from "styled-components"; 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 { state: UploadState; addFile: () => void; removeFile: (index: number) => void; } const Container = styled.div` gap: 4px; padding: 8px; display: flex; user-select: none; flex-direction: column; background: var(--message-box); `; const Carousel = styled.div` gap: 8px; display: flex; overflow-x: scroll; flex-direction: row; `; const Entry = styled.div` 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` gap: 4px; display: flex; font-size: 0.9em; align-items: center; 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` width: 100px; height: 100px; display: grid; flex-shrink: 0; cursor: pointer; border-radius: 4px; place-items: center; background: var(--primary-background); transition: 0.1s ease background-color; &:hover { background: var(--secondary-background); } `; const PreviewBox = styled.div` display: grid; grid-template: "main" 100px / minmax(100px, 1fr); justify-items: center; background: var(--primary-background); overflow: hidden; cursor: pointer; border-radius: 4px; .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); } } `; function FileEntry({ file, remove, index, }: { file: File; remove?: () => void; index: number; }) { 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(() => { let url: string = URL.createObjectURL(file); 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} /> <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) { 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> ); }