UNPKG

@liveblocks/react-ui

Version:

A set of React pre-built components for the Liveblocks products. Liveblocks is the all-in-one toolkit to build collaborative products like Figma, Notion, and more.

391 lines (386 loc) 14.3 kB
"use client"; 'use strict'; var jsxRuntime = require('react/jsx-runtime'); var _private = require('@liveblocks/react/_private'); var react = require('react'); var Cross = require('../../icons/Cross.cjs'); var Spinner = require('../../icons/Spinner.cjs'); var Warning = require('../../icons/Warning.cjs'); var overrides = require('../../overrides.cjs'); require('../../primitives/index.cjs'); var contexts = require('../../primitives/Composer/contexts.cjs'); var classNames = require('../../utils/class-names.cjs'); var formatFileSize = require('../../utils/format-file-size.cjs'); var Tooltip = require('./Tooltip.cjs'); var utils = require('../../primitives/Composer/utils.cjs'); const MAX_DISPLAYED_MEDIA_SIZE = 60 * 1024 * 1024; const fileExtensionRegex = /^(.+?)(\.[^.]+)?$/; function splitFileName(name) { const match = name.match(fileExtensionRegex); return { base: match?.[1] ?? name, extension: match?.[2] }; } function getAttachmentIconGlyph(mimeType) { if (mimeType === "application/zip" || mimeType === "application/gzip" || mimeType === "application/vnd.rar" || mimeType === "application/x-rar-compressed" || mimeType === "application/x-7z-compressed" || mimeType === "application/x-zip-compressed" || mimeType === "application/x-tar" || mimeType === "application/x-bzip" || mimeType === "application/x-bzip2") { return /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M13 15h2v1h-1.5a.5.5 0 0 0 0 1H15v1h-1.5a.5.5 0 0 0 0 1H15v1h-1.5a.5.5 0 0 0 0 1h1a.5.5 0 0 0 .5-.5V20h1.5a.5.5 0 0 0 0-1H15v-1h1.5a.5.5 0 0 0 0-1H15v-1h1.5a.5.5 0 0 0 .5-.5V15a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2Z" }); } if (mimeType.startsWith("text/") || mimeType.startsWith("font/") || mimeType.startsWith("application/")) { return /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 16a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5Zm0 2a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 0 1h-8a.5.5 0 0 1-.5-.5Z" }); } if (mimeType.startsWith("image/")) { return /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 16h6a1 1 0 0 1 1 1v3l-1.293-1.293a1 1 0 0 0-1.414 0L14.09 20.91l-.464-.386a1 1 0 0 0-1.265-.013l-1.231.985A.995.995 0 0 1 11 21v-4a1 1 0 0 1 1-1Zm-2 1a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-6a2 2 0 0 1-2-2v-4Zm3 2a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z" }); } if (mimeType.startsWith("video/")) { return /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 15.71a1 1 0 0 1 1.49-.872l4.96 2.79a1 1 0 0 1 0 1.744l-4.96 2.79A1 1 0 0 1 12 21.29v-5.58Z" }); } if (mimeType.startsWith("audio/")) { return /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15 15a.5.5 0 0 0-.5.5v7a.5.5 0 0 0 1 0v-7a.5.5 0 0 0-.5-.5Zm-2.5 2.5a.5.5 0 0 1 1 0v3a.5.5 0 0 1-1 0v-3Zm-2 1a.5.5 0 0 1 1 0v1a.5.5 0 0 1-1 0v-1Zm6-1a.5.5 0 0 1 1 0v3a.5.5 0 0 1-1 0v-3ZM19 16a.5.5 0 0 0-.5.5v5a.5.5 0 0 0 1 0v-5a.5.5 0 0 0-.5-.5Z" }); } return null; } const AttachmentFileIcon = react.memo(({ mimeType }) => { const iconGlyph = react.useMemo(() => getAttachmentIconGlyph(mimeType), [mimeType]); return /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "lb-attachment-icon", width: 30, height: 30, viewBox: "0 0 30 30", fill: "currentColor", fillRule: "evenodd", clipRule: "evenodd", xmlns: "http://www.w3.org/2000/svg", children: [ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 5a2 2 0 0 1 2-2h5.843a4 4 0 0 1 2.829 1.172l6.156 6.156A4 4 0 0 1 24 13.157V25a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V5Z", className: "lb-attachment-icon-shadow" }), /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 5a2 2 0 0 1 2-2h5.843a4 4 0 0 1 2.829 1.172l6.156 6.156A4 4 0 0 1 24 13.157V25a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V5Z", className: "lb-attachment-icon-background" }), /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14.382 3.037a4 4 0 0 1 2.29 1.135l6.156 6.157a4 4 0 0 1 1.136 2.289A2 2 0 0 0 22 11h-4a2 2 0 0 1-2-2V5a2 2 0 0 0-1.618-1.963Z", className: "lb-attachment-icon-fold" }), iconGlyph && /* @__PURE__ */ jsxRuntime.jsx("g", { className: "lb-attachment-icon-glyph", children: iconGlyph }) ] }); }); function AttachmentImagePreview({ attachment, markPreviewAsUnsupported, roomId }) { const { url } = _private.useRoomAttachmentUrl(attachment.id, roomId); const [isLoaded, setLoaded] = react.useState(false); const handleLoad = react.useCallback(() => { setLoaded(true); }, []); return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ !isLoaded ? /* @__PURE__ */ jsxRuntime.jsx(Spinner.SpinnerIcon, {}) : null, url ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-attachment-preview-media", "data-hidden": !isLoaded ? "" : void 0, children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: url, loading: "lazy", onLoad: handleLoad, onError: markPreviewAsUnsupported }) }) : null ] }); } function AttachmentVideoPreview({ attachment, markPreviewAsUnsupported, roomId }) { const { url } = _private.useRoomAttachmentUrl(attachment.id, roomId); const [isLoaded, setLoaded] = react.useState(false); const handleLoad = react.useCallback(() => { setLoaded(true); }, []); return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ !isLoaded ? /* @__PURE__ */ jsxRuntime.jsx(Spinner.SpinnerIcon, {}) : null, url ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-attachment-preview-media", "data-hidden": !isLoaded ? "" : void 0, children: /* @__PURE__ */ jsxRuntime.jsx("video", { src: url, onLoadedData: handleLoad, onError: markPreviewAsUnsupported }) }) : null ] }); } function AttachmentPreview({ attachment, allowMediaPreview = true, roomId }) { const [isUnsupportedPreview, setUnsupportedPreview] = react.useState(false); const isUploaded = attachment.type === "attachment" || attachment.status === "uploaded"; function markPreviewAsUnsupported() { setUnsupportedPreview(true); } if (!isUnsupportedPreview && allowMediaPreview && isUploaded && attachment.size <= MAX_DISPLAYED_MEDIA_SIZE) { if (attachment.mimeType.startsWith("image/")) { return /* @__PURE__ */ jsxRuntime.jsx(AttachmentImagePreview, { attachment, markPreviewAsUnsupported, roomId }); } if (attachment.mimeType.startsWith("video/")) { return /* @__PURE__ */ jsxRuntime.jsx(AttachmentVideoPreview, { attachment, markPreviewAsUnsupported, roomId }); } } return /* @__PURE__ */ jsxRuntime.jsx(AttachmentFileIcon, { mimeType: attachment.mimeType }); } function AttachmentName({ attachment }) { const { base: fileBaseName, extension: fileExtension } = react.useMemo(() => { return splitFileName(attachment.name); }, [attachment.name]); return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "lb-attachment-name", title: attachment.name, children: [ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lb-attachment-name-base", children: fileBaseName }), fileExtension && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lb-attachment-name-extension", children: fileExtension }) ] }); } function useClickOnKeyDown(onKeyDown) { const handleKeyDown = react.useCallback( (event) => { onKeyDown?.(event); if (event.isDefaultPrevented()) { return; } if (event.key === "Enter" || event.key === " ") { event.preventDefault(); const clickEvent = new MouseEvent("click", { bubbles: true, cancelable: true, view: window }); event.target.dispatchEvent(clickEvent); } }, [onKeyDown] ); return handleKeyDown; } function useAttachmentContent(attachment, overrides$1) { const $ = overrides.useOverrides(overrides$1); const composerAttachmentsContext = contexts.useComposerAttachmentsContextOrNull(); const isInComposer = Boolean(composerAttachmentsContext); const maxAttachmentSize = composerAttachmentsContext?.maxAttachmentSize; const status = attachment.type === "localAttachment" ? attachment.status : void 0; const isUploading = status === "uploading"; const isError = status === "error"; let description; if (attachment.type === "localAttachment" && attachment.status === "error") { if (attachment.error instanceof utils.AttachmentTooLargeError) { if (attachment.error.origin === "server") { description = $.ATTACHMENT_TOO_LARGE(); } else { description = $.ATTACHMENT_TOO_LARGE( maxAttachmentSize ? formatFileSize.formatFileSize(maxAttachmentSize, $.locale) : void 0 ); } } else { description = $.ATTACHMENT_ERROR(attachment.error); } } else { description = formatFileSize.formatFileSize(attachment.size, $.locale); } const deleteLabel = isInComposer ? $.COMPOSER_REMOVE_ATTACHMENT : $.COMMENT_DELETE_ATTACHMENT; return { isUploading, isError, description, deleteLabel }; } function MediaAttachment({ attachment, overrides, onClick, onDeleteClick, preventFocusOnDelete, allowMediaPreview = true, roomId, className, onKeyDown, ...props }) { const { isUploading, isError, description, deleteLabel } = useAttachmentContent(attachment, overrides); const handleDeletePointerDown = react.useCallback( (event) => { if (preventFocusOnDelete) { event.preventDefault(); } }, [preventFocusOnDelete] ); const handleKeyDown = useClickOnKeyDown(onKeyDown); return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames.classNames("lb-attachment lb-media-attachment", className), "data-error": isError ? "" : void 0, ...props, role: onClick ? "button" : void 0, onClick, tabIndex: onClick ? 0 : -1, onKeyDown: onClick ? handleKeyDown : void 0, children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-attachment-preview", children: isUploading ? /* @__PURE__ */ jsxRuntime.jsx(Spinner.SpinnerIcon, {}) : isError ? /* @__PURE__ */ jsxRuntime.jsx(Warning.WarningIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(AttachmentPreview, { attachment, allowMediaPreview, roomId }) }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lb-attachment-details", children: [ /* @__PURE__ */ jsxRuntime.jsx(AttachmentName, { attachment }), /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lb-attachment-description", title: description, children: description }) ] }), onDeleteClick && /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, { content: deleteLabel, children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "lb-attachment-delete", onClick: onDeleteClick, onPointerDown: handleDeletePointerDown, "aria-label": deleteLabel, children: /* @__PURE__ */ jsxRuntime.jsx(Cross.CrossIcon, {}) }) }) ] }); } function FileAttachment({ attachment, overrides, onClick, onDeleteClick, preventFocusOnDelete, allowMediaPreview = true, roomId, className, onKeyDown, ...props }) { const { isUploading, isError, description, deleteLabel } = useAttachmentContent(attachment, overrides); const handleDeletePointerDown = react.useCallback( (event) => { if (preventFocusOnDelete) { event.preventDefault(); } }, [preventFocusOnDelete] ); const handleKeyDown = useClickOnKeyDown(onKeyDown); return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: classNames.classNames("lb-attachment lb-file-attachment", className), "data-error": isError ? "" : void 0, ...props, role: onClick ? "button" : void 0, onClick, tabIndex: onClick ? 0 : -1, onKeyDown: onClick ? handleKeyDown : void 0, children: [ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lb-attachment-preview", children: isUploading ? /* @__PURE__ */ jsxRuntime.jsx(Spinner.SpinnerIcon, {}) : isError ? /* @__PURE__ */ jsxRuntime.jsx(Warning.WarningIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(AttachmentPreview, { attachment, allowMediaPreview, roomId }) }), /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "lb-attachment-details", children: [ /* @__PURE__ */ jsxRuntime.jsx(AttachmentName, { attachment }), /* @__PURE__ */ jsxRuntime.jsx("span", { className: "lb-attachment-description", title: description, children: description }) ] }), onDeleteClick && /* @__PURE__ */ jsxRuntime.jsx(Tooltip.Tooltip, { content: deleteLabel, children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "lb-attachment-delete", onClick: onDeleteClick, onPointerDown: handleDeletePointerDown, "aria-label": deleteLabel, children: /* @__PURE__ */ jsxRuntime.jsx(Cross.CrossIcon, {}) }) }) ] }); } function separateMediaAttachments(attachments) { const mediaAttachments = []; const fileAttachments = []; for (const attachment of attachments) { if ((attachment.mimeType.startsWith("image/") || attachment.mimeType.startsWith("video/")) && attachment.size <= MAX_DISPLAYED_MEDIA_SIZE) { mediaAttachments.push(attachment); } else { fileAttachments.push(attachment); } } return { mediaAttachments, fileAttachments }; } exports.FileAttachment = FileAttachment; exports.MediaAttachment = MediaAttachment; exports.separateMediaAttachments = separateMediaAttachments; //# sourceMappingURL=Attachment.cjs.map