{"version":3,"file":"ChatAttachments.cjs","names":[],"sources":["../../../src/components/Chat/ChatAttachments.tsx"],"sourcesContent":["import { useState } from \"react\";\n\nimport { formatBytes } from \"@/utils/numbers\";\n\nimport { AudioPlayer } from \"../AudioPlayer\";\nimport { Lightbox } from \"../Lightbox\";\nimport { VideoPlayer } from \"../VideoPlayer\";\nimport type { ChatAttachment } from \"./chat-groups\";\nimport styles from \"./Chat.module.css\";\n\n/** Strings this view needs, passed in so the locale table stays in one file. */\nexport interface ChatAttachmentStrings {\n    voiceNote: string;\n    download: string;\n}\n\nexport interface ChatAttachmentsProps {\n    /** What this message carries. */\n    attachments: readonly ChatAttachment[];\n    /** Labels, from `chatStrings(locale)`. */\n    strings: ChatAttachmentStrings;\n}\n\n/** Bars a voice note draws when the app gave no peaks. */\nconst FLAT_WAVEFORM = Array.from({ length: 28 }, () => 0.25);\n\n/** `1:04` from milliseconds — a duration, never a clock time. */\nfunction durationLabel(durationMs: number): string {\n    const total = Math.round(durationMs / 1000);\n    const minutes = Math.floor(total / 60);\n    const seconds = total % 60;\n    return `${minutes}:${String(seconds).padStart(2, \"0\")}`;\n}\n\n/**\n * The media half of a bubble.\n *\n * Kept in its own module, and reached through `lazy()` from the thread, because\n * it pulls three of the heaviest components in the SDK — `Lightbox`,\n * `VideoPlayer`, `AudioPlayer` — and a support thread of plain text should not\n * pay for a photo viewer it never opens. The thread renders it only for messages\n * that actually carry an attachment, so that chunk is requested the first time\n * one appears.\n *\n * Nothing here is new UI: an image opens the SDK's lightbox, a video and an audio\n * clip use the SDK's players, and a file is a download row. That composition is\n * the whole point — every app was writing it again, the same way.\n *\n * @param props - The attachments and the locale strings.\n * @returns The rendered attachment list.\n */\nexport function ChatAttachments({ attachments, strings }: ChatAttachmentsProps) {\n    const images = attachments.filter((attachment) => attachment.kind === \"image\");\n    const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);\n\n    return (\n        <div className={styles.attachments}>\n            {attachments.map((attachment, index) => {\n                const key = `${attachment.kind}-${attachment.url}-${index}`;\n                if (attachment.kind === \"image\") {\n                    const position = images.indexOf(attachment);\n                    return (\n                        <button\n                            key={key}\n                            type=\"button\"\n                            className={styles.imageAttachment}\n                            onClick={() => setLightboxIndex(position)}\n                        >\n                            <img\n                                src={attachment.thumbnailUrl ?? attachment.url}\n                                alt={attachment.alt ?? attachment.name ?? \"\"}\n                                loading=\"lazy\"\n                            />\n                        </button>\n                    );\n                }\n                if (attachment.kind === \"video\") {\n                    return (\n                        <VideoPlayer\n                            key={key}\n                            src={attachment.url}\n                            durationMs={attachment.durationMs}\n                            poster={attachment.thumbnailUrl}\n                            className={styles.videoAttachment}\n                        />\n                    );\n                }\n                if (attachment.kind === \"voice\") {\n                    return (\n                        <div key={key} className={styles.voiceAttachment}>\n                            <div className={styles.waveform} aria-hidden=\"true\">\n                                {(attachment.waveform ?? FLAT_WAVEFORM).map((peak, bar) => (\n                                    <span\n                                        key={bar}\n                                        style={{\n                                            height: `${Math.max(8, Math.min(100, peak * 100))}%`,\n                                        }}\n                                    />\n                                ))}\n                            </div>\n                            <AudioPlayer\n                                src={attachment.url}\n                                durationMs={attachment.durationMs}\n                                aria-label={strings.voiceNote}\n                            />\n                        </div>\n                    );\n                }\n                if (attachment.kind === \"audio\") {\n                    return (\n                        <AudioPlayer\n                            key={key}\n                            src={attachment.url}\n                            durationMs={attachment.durationMs}\n                        />\n                    );\n                }\n                return (\n                    <a\n                        key={key}\n                        className={styles.fileAttachment}\n                        href={attachment.url}\n                        download={attachment.name ?? true}\n                    >\n                        <span className={styles.fileName}>\n                            {attachment.name ?? strings.download}\n                        </span>\n                        <span className={styles.fileMeta}>\n                            {[\n                                attachment.sizeBytes !== undefined\n                                    ? formatBytes(attachment.sizeBytes)\n                                    : null,\n                                attachment.durationMs !== undefined\n                                    ? durationLabel(attachment.durationMs)\n                                    : null,\n                            ]\n                                .filter(Boolean)\n                                .join(\" · \")}\n                        </span>\n                    </a>\n                );\n            })}\n\n            {images.length > 0 && (\n                <Lightbox\n                    items={images.map((image) => ({\n                        src: image.url,\n                        alt: image.alt ?? image.name ?? \"\",\n                        thumbnail: image.thumbnailUrl,\n                    }))}\n                    open={lightboxIndex !== null}\n                    index={lightboxIndex ?? 0}\n                    onIndexChange={setLightboxIndex}\n                    onClose={() => setLightboxIndex(null)}\n                />\n            )}\n        </div>\n    );\n}\n"],"mappings":"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"}