Skip to content
Snippets Groups Projects
Attachment.tsx 5.41 KiB
Newer Older
insert's avatar
insert committed
import TextFile from "./TextFile";
import { Text } from "preact-i18n";
import classNames from "classnames";
import styles from "./Attachment.module.scss";
import AttachmentActions from "./AttachmentActions";
import { useContext, useState } from "preact/hooks";
import { AppContext } from "../../../../context/revoltjs/RevoltClient";
import { Attachment as AttachmentRJS } from "revolt.js/dist/api/objects";
import { useIntermediate } from "../../../../context/intermediate/Intermediate";
import { MessageAreaWidthContext } from "../../../../pages/channels/messaging/MessageArea";

interface Props {
    attachment: AttachmentRJS;
    hasContent: boolean;
}

const MAX_ATTACHMENT_WIDTH = 480;
const MAX_ATTACHMENT_HEIGHT = 640;

export default function Attachment({ attachment, hasContent }: Props) {
    const client = useContext(AppContext);
    const { openScreen } = useIntermediate();
    const { filename, metadata } = attachment;
    const [ spoiler, setSpoiler ] = useState(filename.startsWith("SPOILER_"));
    const maxWidth = Math.min(useContext(MessageAreaWidthContext), MAX_ATTACHMENT_WIDTH);

    const url = client.generateFileURL(attachment, { width: MAX_ATTACHMENT_WIDTH * 1.5 }, true);
    let width  = 0,
        height = 0;
    
    if (metadata.type === 'Image' || metadata.type === 'Video') {
        let limitingWidth = Math.min(
            maxWidth,
            metadata.width
        );

        let limitingHeight = Math.min(
            MAX_ATTACHMENT_HEIGHT,
            metadata.height
        );

        // Calculate smallest possible WxH.
        width = Math.min(
            limitingWidth,
            limitingHeight * (metadata.width / metadata.height)
        );

        height = Math.min(
            limitingHeight,
            limitingWidth * (metadata.height / metadata.width)
        );
    }

    switch (metadata.type) {
        case "Image": {
            return (
                <div
                    style={{ width }}
                    className={styles.container}
                    onClick={() => spoiler && setSpoiler(false)}
                >
                    {spoiler && (
                        <div className={styles.overflow}>
                            <div style={{ width, height }}>
                                <span><Text id="app.main.channel.misc.spoiler_attachment" /></span>
                            </div>
                        </div>
                    )}
                    <img
                        src={url}
                        alt={filename}
                        data-spoiler={spoiler}
                        data-has-content={hasContent}
                        className={classNames(styles.attachment, styles.image)}
                        onClick={() =>
                            openScreen({ id: "image_viewer", attachment })
                        }
                        onMouseDown={ev =>
                            ev.button === 1 &&
                            window.open(url, "_blank")
                        }
                        style={{ width, height }}
                    />
                </div>
            );
        }
        case "Audio": {
            return (
                <div
                    className={classNames(styles.attachment, styles.audio)}
                    data-has-content={hasContent}
                >
                    <AttachmentActions attachment={attachment} />
                    <audio src={url} controls />
                </div>
            );
        }
        case "Video": {
            return (
                <div
                    className={styles.container}
                    onClick={() => spoiler && setSpoiler(false)}>
                    {spoiler && (
                        <div className={styles.overflow}>
                            <div style={{ width, height }}>
                                <span><Text id="app.main.channel.misc.spoiler_attachment" /></span>
                            </div>
                        </div>
                    )}
                    <div
                        style={{ width }}
                        data-spoiler={spoiler}
                        data-has-content={hasContent}
                        className={classNames(styles.attachment, styles.video)}
                    >
                        <AttachmentActions attachment={attachment} />
                        <video
                            src={url}
                            controls
                            style={{ width, height }}
                            onMouseDown={ev =>
                                ev.button === 1 &&
                                window.open(url, "_blank")
                            }
                        />
                    </div>
                </div>
            );
        }
        case 'Text': {
            return (
                <div
                    className={classNames(styles.attachment, styles.text)}
                    data-has-content={hasContent}
                >
                    <TextFile attachment={attachment} />
                    <AttachmentActions attachment={attachment} />
                </div>
            );
        }
        default: {
            return (
                <div
                    className={classNames(styles.attachment, styles.file)}
                    data-has-content={hasContent}
                >
                    <AttachmentActions attachment={attachment} />
                </div>
            );
        }
    }
}