import { MutableRefObject, useEffect, useRef, useState } from "react"; import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine"; import { draggable, dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element/adapter"; import { observer } from "mobx-react-lite"; import { TIssue, IIssueDisplayProperties, IIssueMap } from "@plane/types"; // hooks import { ControlLink, DropIndicator, TOAST_TYPE, Tooltip, setToast } from "@plane/ui"; import RenderIfVisible from "@/components/core/render-if-visible-HOC"; import { HIGHLIGHT_CLASS } from "@/components/issues/issue-layouts/utils"; import { cn } from "@/helpers/common.helper"; // hooks import { useAppRouter, useIssueDetail, useProject, useKanbanView } from "@/hooks/store"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; import { usePlatformOS } from "@/hooks/use-platform-os"; // components import { TRenderQuickActions } from "../list/list-view-types"; import { IssueProperties } from "../properties/all-properties"; import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC"; import { getIssueBlockId } from "../utils"; // ui // types // helper interface IssueBlockProps { issueId: string; groupId: string; subGroupId: string; issuesMap: IIssueMap; displayProperties: IIssueDisplayProperties | undefined; isDragDisabled: boolean; draggableId: string; canDropOverIssue: boolean; updateIssue: ((projectId: string, issueId: string, data: Partial) => Promise) | undefined; quickActions: TRenderQuickActions; canEditProperties: (projectId: string | undefined) => boolean; scrollableContainerRef?: MutableRefObject; } interface IssueDetailsBlockProps { cardRef: React.RefObject; issue: TIssue; displayProperties: IIssueDisplayProperties | undefined; updateIssue: ((projectId: string, issueId: string, data: Partial) => Promise) | undefined; quickActions: TRenderQuickActions; isReadOnly: boolean; } const KanbanIssueDetailsBlock: React.FC = observer((props) => { const { cardRef, issue, updateIssue, quickActions, isReadOnly, displayProperties } = props; // hooks const { isMobile } = usePlatformOS(); const { getProjectIdentifierById } = useProject(); const handleEventPropagation = (e: React.MouseEvent) => { e.stopPropagation(); e.preventDefault(); }; return ( <>
{getProjectIdentifierById(issue.project_id)}-{issue.sequence_id}
{quickActions({ issue, parentRef: cardRef, })}
{issue?.is_draft ? ( {issue.name} ) : (
{issue.name}
)} ); }); export const KanbanIssueBlock: React.FC = observer((props) => { const { issueId, groupId, subGroupId, issuesMap, displayProperties, isDragDisabled, canDropOverIssue, updateIssue, quickActions, canEditProperties, scrollableContainerRef, } = props; const cardRef = useRef(null); // hooks const { workspaceSlug } = useAppRouter(); const { getIsIssuePeeked, setPeekIssue } = useIssueDetail(); const { isMobile } = usePlatformOS(); const handleIssuePeekOverview = (issue: TIssue) => workspaceSlug && issue && issue.project_id && issue.id && !getIsIssuePeeked(issue.id) && setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id }); const issue = issuesMap[issueId]; const { setIsDragging: setIsKanbanDragging } = useKanbanView(); const [isDraggingOverBlock, setIsDraggingOverBlock] = useState(false); const [isCurrentBlockDragging, setIsCurrentBlockDragging] = useState(false); const canEditIssueProperties = canEditProperties(issue?.project_id); const isDragAllowed = !isDragDisabled && !issue?.tempId && canEditIssueProperties; useOutsideClickDetector(cardRef, () => { cardRef?.current?.classList?.remove(HIGHLIGHT_CLASS); }); // Make Issue block both as as Draggable and, // as a DropTarget for other issues being dragged to get the location of drop useEffect(() => { const element = cardRef.current; if (!element) return; return combine( draggable({ element, dragHandle: element, canDrag: () => isDragAllowed, getInitialData: () => ({ id: issue?.id, type: "ISSUE" }), onDragStart: () => { setIsCurrentBlockDragging(true); setIsKanbanDragging(true); }, onDrop: () => { setIsKanbanDragging(false); setIsCurrentBlockDragging(false); }, }), dropTargetForElements({ element, canDrop: ({ source }) => source?.data?.id !== issue?.id && canDropOverIssue, getData: () => ({ id: issue?.id, type: "ISSUE" }), onDragEnter: () => { setIsDraggingOverBlock(true); }, onDragLeave: () => { setIsDraggingOverBlock(false); }, onDrop: () => { setIsDraggingOverBlock(false); }, }) ); }, [cardRef?.current, issue?.id, isDragAllowed, canDropOverIssue, setIsCurrentBlockDragging, setIsDraggingOverBlock]); if (!issue) return null; return ( <>
{ if (isDragAllowed) setIsCurrentBlockDragging(true); else setToast({ type: TOAST_TYPE.WARNING, title: "Cannot move issue", message: "Drag and drop is disabled for the current grouping", }); }} > handleIssuePeekOverview(issue)} disabled={!!issue?.tempId || isMobile} >
); }); KanbanIssueBlock.displayName = "KanbanIssueBlock";