@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
JavaScript
"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