UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: зона вложений в карточке деталей
This commit is contained in:
parent
21a9d2b809
commit
5f2d543cab
|
|
@ -59,7 +59,8 @@ export const IssueAttachmentItemList = observer(function IssueAttachmentItemList
|
|||
// file size
|
||||
const { maxFileSize } = useFileSize();
|
||||
// derived values
|
||||
const issueAttachments = getAttachmentsByIssueId(issueId);
|
||||
const issueAttachments = getAttachmentsByIssueId(issueId) ?? [];
|
||||
const hasAttachmentRows = issueAttachments.length > 0 || !!uploadStatus?.length;
|
||||
|
||||
// handlers
|
||||
const handleFetchPropertyActivities = useCallback(() => {
|
||||
|
|
@ -68,80 +69,96 @@ export const IssueAttachmentItemList = observer(function IssueAttachmentItemList
|
|||
|
||||
const onDrop = useCallback(
|
||||
(acceptedFiles: File[], rejectedFiles: FileRejection[]) => {
|
||||
const totalAttachedFiles = acceptedFiles.length + rejectedFiles.length;
|
||||
|
||||
if (rejectedFiles.length === 0) {
|
||||
const currentFile: File = acceptedFiles[0];
|
||||
if (!currentFile || !workspaceSlug) return;
|
||||
|
||||
if (acceptedFiles.length > 0) {
|
||||
if (!workspaceSlug) return;
|
||||
setIsUploading(true);
|
||||
createAttachment(currentFile)
|
||||
.catch(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("toast.error"),
|
||||
message: t("attachment.error"),
|
||||
});
|
||||
|
||||
Promise.allSettled(acceptedFiles.map((file) => createAttachment(file)))
|
||||
.then((results) => {
|
||||
const failedUploads = results.filter((result) => result.status === "rejected").length;
|
||||
if (failedUploads > 0)
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("toast.error"),
|
||||
message: t("attachment.error"),
|
||||
});
|
||||
return undefined;
|
||||
})
|
||||
.finally(() => {
|
||||
handleFetchPropertyActivities();
|
||||
setIsUploading(false);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("toast.error"),
|
||||
message:
|
||||
totalAttachedFiles > 1
|
||||
? t("attachment.only_one_file_allowed")
|
||||
: t("attachment.file_size_limit", { size: maxFileSize / 1024 / 1024 }),
|
||||
});
|
||||
if (rejectedFiles.length > 0)
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: t("toast.error"),
|
||||
message: t("attachment.file_size_limit", { size: maxFileSize / 1024 / 1024 }),
|
||||
});
|
||||
|
||||
return;
|
||||
},
|
||||
[createAttachment, maxFileSize, workspaceSlug, handleFetchPropertyActivities]
|
||||
[createAttachment, maxFileSize, workspaceSlug, handleFetchPropertyActivities, t]
|
||||
);
|
||||
|
||||
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
||||
const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({
|
||||
onDrop,
|
||||
maxSize: maxFileSize,
|
||||
multiple: false,
|
||||
multiple: true,
|
||||
disabled: isUploading || disabled,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{uploadStatus?.map((uploadStatus) => (
|
||||
<IssueAttachmentsUploadItem key={uploadStatus.id} uploadStatus={uploadStatus} />
|
||||
))}
|
||||
{issueAttachments && (
|
||||
<>
|
||||
{attachmentDeleteModalId && (
|
||||
<IssueAttachmentDeleteModal
|
||||
isOpen={Boolean(attachmentDeleteModalId)}
|
||||
onClose={() => toggleDeleteAttachmentModal(null)}
|
||||
attachmentOperations={attachmentOperations}
|
||||
attachmentId={attachmentDeleteModalId}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={`relative flex flex-col ${isDragActive && issueAttachments.length < 3 ? "min-h-[200px]" : ""} ${disabled ? "cursor-not-allowed" : "cursor-pointer"}`}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
{isDragActive && (
|
||||
<div className="absolute top-0 left-0 z-30 flex h-full w-full items-center justify-center bg-surface-2/75">
|
||||
<div className="flex items-center justify-center rounded-md bg-surface-1 p-1">
|
||||
<div className="flex flex-col items-center justify-center rounded-md border border-dashed border-strong px-5 py-6">
|
||||
<UploadCloud className="size-7" />
|
||||
<span className="text-13 text-tertiary">{t("attachment.drag_and_drop")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{issueAttachments?.map((attachmentId) => (
|
||||
<div className="space-y-3">
|
||||
{attachmentDeleteModalId && (
|
||||
<IssueAttachmentDeleteModal
|
||||
isOpen={Boolean(attachmentDeleteModalId)}
|
||||
onClose={() => toggleDeleteAttachmentModal(null)}
|
||||
attachmentOperations={attachmentOperations}
|
||||
attachmentId={attachmentDeleteModalId}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
{...getRootProps()}
|
||||
data-drag-active={isDragActive ? "true" : "false"}
|
||||
data-drag-reject={isDragReject ? "true" : "false"}
|
||||
className={`nodedc-attachment-upload flex w-full items-center justify-between gap-4 px-5 py-4 ${
|
||||
disabled ? "cursor-not-allowed opacity-70" : "cursor-pointer"
|
||||
}`}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<div className="flex min-w-0 items-center gap-4">
|
||||
<div className="grid size-11 flex-shrink-0 place-items-center rounded-2xl bg-[rgba(var(--nodedc-accent-rgb),0.14)] text-[rgb(var(--nodedc-accent-rgb))]">
|
||||
<UploadCloud className="size-5" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-14 font-semibold text-primary">
|
||||
{isDragActive ? "Отпустите файлы здесь" : "Перетащите файлы сюда или нажмите для выбора"}
|
||||
</div>
|
||||
<div className="mt-1 truncate text-12 text-tertiary">
|
||||
Любой формат, несколько файлов за раз, до {maxFileSize / 1024 / 1024} МБ на файл
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden flex-shrink-0 rounded-full bg-[rgba(var(--nodedc-accent-rgb),0.12)] px-3 py-1 text-12 font-semibold text-[rgb(var(--nodedc-accent-rgb))] sm:block">
|
||||
Выбрать
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasAttachmentRows && (
|
||||
<div className="rounded-[1.35rem] border border-subtle bg-surface-1/60 p-3 shadow-[0_12px_28px_rgba(0,0,0,0.08)]">
|
||||
<div className="mb-2 flex items-center justify-between gap-3 px-1">
|
||||
<div className="text-12 font-semibold uppercase tracking-[0.08em] text-tertiary">Вложения</div>
|
||||
<div className="text-12 text-tertiary">{issueAttachments.length}</div>
|
||||
</div>
|
||||
<div className="flex gap-3 overflow-x-auto pb-1">
|
||||
{uploadStatus?.map((status) => (
|
||||
<IssueAttachmentsUploadItem key={status.id} uploadStatus={status} />
|
||||
))}
|
||||
{issueAttachments.map((attachmentId) => (
|
||||
<IssueAttachmentsListItem
|
||||
key={attachmentId}
|
||||
attachmentId={attachmentId}
|
||||
|
|
@ -150,8 +167,8 @@ export const IssueAttachmentItemList = observer(function IssueAttachmentItemList
|
|||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useState } from "react";
|
||||
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { getIconButtonStyling } from "@plane/propel/icon-button";
|
||||
|
|
@ -14,8 +15,9 @@ import type { TIssueServiceType } from "@plane/types";
|
|||
import { EIssueServiceType } from "@plane/types";
|
||||
// ui
|
||||
import type { TContextMenuItem } from "@plane/ui";
|
||||
import { ActionDropdown } from "@plane/ui";
|
||||
import { ActionDropdown, EModalPosition, EModalWidth, ModalCore } from "@plane/ui";
|
||||
import { convertBytesToSize, getFileExtension, getFileName, getFileURL, renderFormattedDate } from "@plane/utils";
|
||||
import { Download, FileText, ImageIcon, Play, X } from "lucide-react";
|
||||
// components
|
||||
//
|
||||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||
|
|
@ -32,6 +34,18 @@ type TIssueAttachmentsListItem = {
|
|||
issueServiceType?: TIssueServiceType;
|
||||
};
|
||||
|
||||
const IMAGE_EXTENSIONS = new Set(["apng", "avif", "bmp", "gif", "jpg", "jpeg", "png", "svg", "webp"]);
|
||||
const VIDEO_EXTENSIONS = new Set(["avi", "m4v", "mov", "mp4", "mpeg", "mpg", "ogv", "webm"]);
|
||||
const PDF_EXTENSIONS = new Set(["pdf"]);
|
||||
|
||||
const getPreviewType = (extension: string) => {
|
||||
const normalizedExtension = extension.toLowerCase();
|
||||
if (IMAGE_EXTENSIONS.has(normalizedExtension)) return "image";
|
||||
if (VIDEO_EXTENSIONS.has(normalizedExtension)) return "video";
|
||||
if (PDF_EXTENSIONS.has(normalizedExtension)) return "pdf";
|
||||
return "file";
|
||||
};
|
||||
|
||||
export const IssueAttachmentsListItem = observer(function IssueAttachmentsListItem(props: TIssueAttachmentsListItem) {
|
||||
const { t } = useTranslation();
|
||||
// props
|
||||
|
|
@ -46,8 +60,11 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
|
|||
const attachment = attachmentId ? getAttachmentById(attachmentId) : undefined;
|
||||
const fileName = getFileName(attachment?.attributes.name ?? "");
|
||||
const fileExtension = getFileExtension(attachment?.attributes.name ?? "");
|
||||
const fileIcon = getFileIcon(fileExtension, 18);
|
||||
const fullFileName = fileExtension ? `${fileName}.${fileExtension}` : fileName;
|
||||
const previewType = getPreviewType(fileExtension);
|
||||
const fileIcon = getFileIcon(fileExtension, 32);
|
||||
const fileURL = getFileURL(attachment?.asset_url ?? "");
|
||||
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
||||
const menuItems: TContextMenuItem[] = [
|
||||
{
|
||||
key: "delete",
|
||||
|
|
@ -64,56 +81,135 @@ export const IssueAttachmentsListItem = observer(function IssueAttachmentsListIt
|
|||
if (!attachment) return <></>;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.open(fileURL, "_blank");
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.open(fileURL, "_blank");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="group flex h-11 items-center justify-between gap-3 pr-2 pl-9 hover:bg-surface-2">
|
||||
<div className="flex items-center gap-3 truncate text-13">
|
||||
<div className="flex items-center gap-3">{fileIcon}</div>
|
||||
<Tooltip tooltipContent={`${fileName}.${fileExtension}`} isMobile={isMobile}>
|
||||
<p className="truncate font-medium text-secondary">{`${fileName}.${fileExtension}`}</p>
|
||||
</Tooltip>
|
||||
<span className="flex size-1.5 rounded-full bg-layer-1" />
|
||||
<span className="flex-shrink-0 text-placeholder">{convertBytesToSize(attachment.attributes.size)}</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
{attachment?.created_by && (
|
||||
<>
|
||||
<div className="group flex h-32 w-72 flex-shrink-0 overflow-hidden rounded-2xl border border-subtle bg-surface-2/80 transition hover:border-[rgba(var(--nodedc-accent-rgb),0.45)] hover:bg-surface-2">
|
||||
<button
|
||||
type="button"
|
||||
className="flex min-w-0 flex-1 items-stretch text-left"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsPreviewOpen(true);
|
||||
}}
|
||||
>
|
||||
<div className="relative h-full w-28 flex-shrink-0 overflow-hidden bg-surface-1">
|
||||
{previewType === "image" && fileURL ? (
|
||||
<img src={fileURL} alt={fullFileName} className="h-full w-full object-cover" loading="lazy" />
|
||||
) : previewType === "video" && fileURL ? (
|
||||
<>
|
||||
<Tooltip
|
||||
isMobile={isMobile}
|
||||
tooltipContent={`${
|
||||
getUserDetails(attachment?.created_by)?.display_name ?? ""
|
||||
} uploaded on ${renderFormattedDate(attachment.updated_at)}`}
|
||||
>
|
||||
<div className="flex items-center justify-center">
|
||||
<ButtonAvatars showTooltip userIds={attachment?.created_by} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<video src={fileURL} className="h-full w-full object-cover" muted playsInline preload="metadata" />
|
||||
<div className="absolute inset-0 grid place-items-center bg-black/20 text-white">
|
||||
<Play className="size-6 fill-current" />
|
||||
</div>
|
||||
</>
|
||||
) : previewType === "pdf" && fileURL ? (
|
||||
<div className="flex h-full w-full items-center justify-center bg-white text-red-500">
|
||||
<FileText className="size-9" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
{previewType === "file" ? fileIcon : <ImageIcon className="size-8 text-tertiary" />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ActionDropdown
|
||||
items={menuItems}
|
||||
buttonClassName={getIconButtonStyling("ghost", "sm")}
|
||||
placement="bottom-end"
|
||||
disabled={!!disabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex min-w-0 flex-1 flex-col justify-between p-3">
|
||||
<div className="min-w-0">
|
||||
<Tooltip tooltipContent={fullFileName} isMobile={isMobile}>
|
||||
<p className="line-clamp-2 text-13 font-semibold text-secondary">{fullFileName}</p>
|
||||
</Tooltip>
|
||||
<div className="mt-2 flex items-center gap-2 text-11 text-tertiary">
|
||||
<span>{fileExtension ? fileExtension.toUpperCase() : "FILE"}</span>
|
||||
<span className="size-1 rounded-full bg-layer-1" />
|
||||
<span>{convertBytesToSize(attachment.attributes.size)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{attachment?.created_by && (
|
||||
<Tooltip
|
||||
isMobile={isMobile}
|
||||
tooltipContent={`${
|
||||
getUserDetails(attachment?.created_by)?.display_name ?? ""
|
||||
} uploaded on ${renderFormattedDate(attachment.updated_at)}`}
|
||||
>
|
||||
<div className="flex w-fit items-center justify-center">
|
||||
<ButtonAvatars showTooltip userIds={attachment?.created_by} />
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div className="flex w-10 flex-shrink-0 items-start justify-center pt-2">
|
||||
<ActionDropdown
|
||||
items={menuItems}
|
||||
buttonClassName={getIconButtonStyling("ghost", "sm")}
|
||||
placement="bottom-end"
|
||||
disabled={!!disabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModalCore
|
||||
isOpen={isPreviewOpen}
|
||||
handleClose={() => setIsPreviewOpen(false)}
|
||||
position={EModalPosition.CENTER}
|
||||
width={EModalWidth.VIXL}
|
||||
className="overflow-hidden border border-subtle bg-surface-1"
|
||||
>
|
||||
<div className="relative min-h-[55vh] bg-surface-1 p-4">
|
||||
<div className="absolute top-4 right-4 z-10 flex items-center gap-2">
|
||||
{fileURL && (
|
||||
<a
|
||||
href={fileURL}
|
||||
download={fullFileName}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="grid size-10 place-items-center rounded-full bg-[rgb(var(--nodedc-accent-rgb))] text-[rgb(var(--nodedc-on-accent-rgb))] shadow-[0_14px_30px_rgba(0,0,0,0.3)] transition hover:brightness-110"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Download className="size-5" />
|
||||
</a>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="grid size-10 place-items-center rounded-full bg-black/35 text-white backdrop-blur-md transition hover:bg-black/50"
|
||||
onClick={() => setIsPreviewOpen(false)}
|
||||
>
|
||||
<X className="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="pr-24">
|
||||
<div className="text-15 font-semibold text-primary">{fullFileName}</div>
|
||||
<div className="mt-1 text-12 text-tertiary">{convertBytesToSize(attachment.attributes.size)}</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex h-[70vh] max-h-[70vh] items-center justify-center overflow-hidden rounded-2xl bg-black/20">
|
||||
{previewType === "image" && fileURL ? (
|
||||
<img src={fileURL} alt={fullFileName} className="max-h-full max-w-full object-contain" />
|
||||
) : previewType === "video" && fileURL ? (
|
||||
<video src={fileURL} className="max-h-full max-w-full" controls autoPlay>
|
||||
<track kind="captions" />
|
||||
</video>
|
||||
) : previewType === "pdf" && fileURL ? (
|
||||
<iframe
|
||||
title={fullFileName}
|
||||
src={fileURL}
|
||||
sandbox="allow-downloads allow-same-origin"
|
||||
className="h-full w-full rounded-2xl bg-white"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex flex-col items-center gap-4 p-8 text-center">
|
||||
<div className="grid size-20 place-items-center rounded-3xl bg-surface-2">{fileIcon}</div>
|
||||
<div className="max-w-md text-14 text-secondary">
|
||||
Предпросмотр для этого формата недоступен. Файл можно скачать или открыть в новой вкладке.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ModalCore>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -32,18 +32,21 @@ export const IssueAttachmentsUploadItem = observer(function IssueAttachmentsUplo
|
|||
const { isMobile } = usePlatformOS();
|
||||
|
||||
return (
|
||||
<div className="pointer-events-none flex h-11 items-center justify-between gap-3 bg-surface-2 pr-2 pl-9">
|
||||
<div className="flex items-center gap-3 truncate text-13">
|
||||
<div className="flex-shrink-0">{fileIcon}</div>
|
||||
<Tooltip tooltipContent={fileName} isMobile={isMobile}>
|
||||
<p className="truncate font-medium text-secondary">{fileName}</p>
|
||||
</Tooltip>
|
||||
<div className="pointer-events-none flex h-24 w-64 flex-shrink-0 items-center justify-between gap-3 rounded-2xl border border-subtle bg-surface-2 px-3 py-2">
|
||||
<div className="flex min-w-0 items-center gap-3 text-13">
|
||||
<div className="grid size-12 flex-shrink-0 place-items-center rounded-xl bg-surface-1">{fileIcon}</div>
|
||||
<div className="min-w-0">
|
||||
<Tooltip tooltipContent={fileName} isMobile={isMobile}>
|
||||
<p className="truncate font-medium text-secondary">{fileName}</p>
|
||||
</Tooltip>
|
||||
<p className="mt-1 text-11 text-tertiary">Загрузка</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<div className="flex flex-shrink-0 flex-col items-center gap-1">
|
||||
<span className="flex-shrink-0">
|
||||
<CircularProgressIndicator size={20} strokeWidth={3} percentage={uploadStatus.progress} />
|
||||
</span>
|
||||
<div className="flex-shrink-0 text-13 font-medium">{uploadStatus.progress}% done</div>
|
||||
<div className="flex-shrink-0 text-11 font-medium text-secondary">{uploadStatus.progress}%</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ export const IssueDetailWidgetCollapsibles = observer(function IssueDetailWidget
|
|||
const attachmentUploads = getAttachmentsUploadStatusByIssueId(issueId);
|
||||
const attachmentsCount = getAttachmentsCountByIssueId(issueId);
|
||||
const shouldRenderAttachments =
|
||||
attachmentsCount > 0 ||
|
||||
(!!attachmentUploads && attachmentUploads.length > 0 && !hideWidgets?.includes("attachments"));
|
||||
!hideWidgets?.includes("attachments") &&
|
||||
(attachmentsCount > 0 || (!!attachmentUploads && attachmentUploads.length > 0));
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import React from "react";
|
|||
// plane imports
|
||||
import type { TIssueServiceType, TWorkItemWidgets } from "@plane/types";
|
||||
// local imports
|
||||
import { IssueAttachmentsCollapsibleContent } from "./attachments/content";
|
||||
import { IssueDetailWidgetActionButtons } from "./action-buttons";
|
||||
import { IssueDetailWidgetCollapsibles } from "./issue-detail-widget-collapsibles";
|
||||
import { IssueDetailWidgetModals } from "./issue-detail-widget-modals";
|
||||
|
|
@ -34,6 +35,9 @@ export function IssueDetailWidgets(props: Props) {
|
|||
hideWidgets,
|
||||
compactView = false,
|
||||
} = props;
|
||||
const hideWidgetsWithInlineAttachments = hideWidgets?.includes("attachments")
|
||||
? hideWidgets
|
||||
: ([...(hideWidgets ?? []), "attachments"] as TWorkItemWidgets[]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -44,16 +48,25 @@ export function IssueDetailWidgets(props: Props) {
|
|||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
hideWidgets={hideWidgets}
|
||||
hideWidgets={hideWidgetsWithInlineAttachments}
|
||||
compactView={compactView}
|
||||
/>
|
||||
{!hideWidgets?.includes("attachments") && (
|
||||
<IssueAttachmentsCollapsibleContent
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
/>
|
||||
)}
|
||||
<IssueDetailWidgetCollapsibles
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueId={issueId}
|
||||
disabled={disabled}
|
||||
issueServiceType={issueServiceType}
|
||||
hideWidgets={hideWidgets}
|
||||
hideWidgets={hideWidgetsWithInlineAttachments}
|
||||
/>
|
||||
</div>
|
||||
{renderWidgetModals && (
|
||||
|
|
|
|||
Loading…
Reference in New Issue