mirror of
https://github.com/makeplane/plane
synced 2025-08-07 19:59:33 +00:00
chore: double click action added and code refactor (#5821)
This commit is contained in:
committed by
GitHub
parent
658542cc62
commit
cb90810d02
@@ -1,8 +1,14 @@
|
||||
"use client";
|
||||
import React, { FC, useRef } from "react";
|
||||
import React, { FC, useRef, useState } from "react";
|
||||
import { omit } from "lodash";
|
||||
import { observer } from "mobx-react";
|
||||
import { Copy, Pencil, SquareStackIcon, Trash2 } from "lucide-react";
|
||||
// types
|
||||
import { TWorkspaceDraftIssue } from "@plane/types";
|
||||
// ui
|
||||
import { Row, Tooltip } from "@plane/ui";
|
||||
import { Row, TContextMenuItem, Tooltip } from "@plane/ui";
|
||||
// constants
|
||||
import { EIssuesStoreType } from "@/constants/issue";
|
||||
// helper
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
@@ -11,6 +17,8 @@ import { useAppTheme, useProject, useWorkspaceDraftIssues } from "@/hooks/store"
|
||||
import { IdentifierText, IssueTypeIdentifier } from "@/plane-web/components/issues";
|
||||
// local components
|
||||
import { WorkspaceDraftIssueQuickActions } from "../issue-layouts";
|
||||
import { CreateUpdateIssueModal } from "../issue-modal";
|
||||
import { WorkspaceDraftIssueDeleteIssueModal } from "./delete-modal";
|
||||
import { DraftIssueProperties } from "./draft-issue-properties";
|
||||
|
||||
type Props = {
|
||||
@@ -21,8 +29,13 @@ type Props = {
|
||||
export const DraftIssueBlock: FC<Props> = observer((props) => {
|
||||
// props
|
||||
const { workspaceSlug, issueId } = props;
|
||||
// states
|
||||
const [moveToIssue, setMoveToIssue] = useState(false);
|
||||
const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false);
|
||||
const [issueToEdit, setIssueToEdit] = useState<TWorkspaceDraftIssue | undefined>(undefined);
|
||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||
// hooks
|
||||
const { getIssueById, updateIssue, deleteIssue, moveIssue } = useWorkspaceDraftIssues();
|
||||
const { getIssueById, updateIssue, deleteIssue } = useWorkspaceDraftIssues();
|
||||
const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme();
|
||||
const { getProjectIdentifierById } = useProject();
|
||||
// ref
|
||||
@@ -32,97 +45,155 @@ export const DraftIssueBlock: FC<Props> = observer((props) => {
|
||||
const projectIdentifier = (issue && issue.project_id && getProjectIdentifierById(issue.project_id)) || undefined;
|
||||
if (!issue || !projectIdentifier) return null;
|
||||
|
||||
return (
|
||||
<div id={`issue-${issue.id}`} className=" relative border-b border-b-custom-border-200 w-full cursor-pointer">
|
||||
<Row
|
||||
ref={issueRef}
|
||||
className={cn(
|
||||
"group/list-block min-h-11 relative flex flex-col gap-3 bg-custom-background-100 hover:bg-custom-background-90 py-3 text-sm transition-colors border border-transparent last:border-b-transparent",
|
||||
{
|
||||
"md:flex-row md:items-center": isSidebarCollapsed,
|
||||
"lg:flex-row lg:items-center": !isSidebarCollapsed,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full truncate">
|
||||
<div className="flex flex-grow items-center gap-0.5 truncate">
|
||||
<div className="flex items-center gap-1">
|
||||
{/* {displayProperties && (displayProperties.key || displayProperties.issue_type) && ( */}
|
||||
<div className="flex-shrink-0">
|
||||
{issue.project_id && (
|
||||
<div className="flex items-center space-x-2">
|
||||
{issue?.type_id && <IssueTypeIdentifier issueTypeId={issue.type_id} />}
|
||||
<IdentifierText
|
||||
identifier={projectIdentifier}
|
||||
enableClickToCopyIdentifier
|
||||
textContainerClassName="text-xs font-medium text-custom-text-300"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* )} */}
|
||||
const duplicateIssuePayload = omit(
|
||||
{
|
||||
...issue,
|
||||
name: `${issue.name} (copy)`,
|
||||
is_draft: true,
|
||||
},
|
||||
["id"]
|
||||
);
|
||||
|
||||
{/* sub-issues chevron */}
|
||||
<div className="size-4 grid place-items-center flex-shrink-0" />
|
||||
const MENU_ITEMS: TContextMenuItem[] = [
|
||||
{
|
||||
key: "edit",
|
||||
title: "Edit",
|
||||
icon: Pencil,
|
||||
action: () => {
|
||||
setIssueToEdit(issue);
|
||||
setCreateUpdateIssueModal(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "make-a-copy",
|
||||
title: "Make a copy",
|
||||
icon: Copy,
|
||||
action: () => {
|
||||
setCreateUpdateIssueModal(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "move-to-issues",
|
||||
title: "Move to issues",
|
||||
icon: SquareStackIcon,
|
||||
action: () => {
|
||||
setMoveToIssue(true);
|
||||
setIssueToEdit(issue);
|
||||
setCreateUpdateIssueModal(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
title: "Delete",
|
||||
icon: Trash2,
|
||||
action: () => {
|
||||
setDeleteIssueModal(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<WorkspaceDraftIssueDeleteIssueModal
|
||||
data={issue}
|
||||
isOpen={deleteIssueModal}
|
||||
handleClose={() => setDeleteIssueModal(false)}
|
||||
onSubmit={async () => deleteIssue(workspaceSlug, issueId)}
|
||||
/>
|
||||
<CreateUpdateIssueModal
|
||||
isOpen={createUpdateIssueModal}
|
||||
onClose={() => {
|
||||
setCreateUpdateIssueModal(false);
|
||||
setIssueToEdit(undefined);
|
||||
setMoveToIssue(false);
|
||||
}}
|
||||
data={issueToEdit ?? duplicateIssuePayload}
|
||||
onSubmit={async (data) => {
|
||||
if (issueToEdit) await updateIssue(workspaceSlug, issueId, data);
|
||||
}}
|
||||
storeType={EIssuesStoreType.WORKSPACE_DRAFT}
|
||||
fetchIssueDetails={false}
|
||||
moveToIssue={moveToIssue}
|
||||
isDraft
|
||||
/>
|
||||
<div
|
||||
id={`issue-${issue.id}`}
|
||||
className=" relative border-b border-b-custom-border-200 w-full cursor-pointer"
|
||||
onDoubleClick={() => {
|
||||
setIssueToEdit(issue);
|
||||
setCreateUpdateIssueModal(true);
|
||||
}}
|
||||
>
|
||||
<Row
|
||||
ref={issueRef}
|
||||
className={cn(
|
||||
"group/list-block min-h-11 relative flex flex-col gap-3 bg-custom-background-100 hover:bg-custom-background-90 py-3 text-sm transition-colors border border-transparent last:border-b-transparent",
|
||||
{
|
||||
"md:flex-row md:items-center": isSidebarCollapsed,
|
||||
"lg:flex-row lg:items-center": !isSidebarCollapsed,
|
||||
}
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full truncate">
|
||||
<div className="flex flex-grow items-center gap-0.5 truncate">
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="flex-shrink-0">
|
||||
{issue.project_id && (
|
||||
<div className="flex items-center space-x-2">
|
||||
{issue?.type_id && <IssueTypeIdentifier issueTypeId={issue.type_id} />}
|
||||
<IdentifierText
|
||||
identifier={projectIdentifier}
|
||||
enableClickToCopyIdentifier
|
||||
textContainerClassName="text-xs font-medium text-custom-text-300"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* sub-issues chevron */}
|
||||
<div className="size-4 grid place-items-center flex-shrink-0" />
|
||||
</div>
|
||||
|
||||
<Tooltip tooltipContent={issue.name} position="top-left" renderByDefault={false}>
|
||||
<p className="w-full truncate cursor-pointer text-sm text-custom-text-100">{issue.name}</p>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<Tooltip
|
||||
tooltipContent={issue.name}
|
||||
// isMobile={isMobile}
|
||||
position="top-left"
|
||||
// disabled={isCurrentBlockDragging}
|
||||
renderByDefault={false}
|
||||
{/* quick actions */}
|
||||
<div
|
||||
className={cn("block border border-custom-border-300 rounded", {
|
||||
"md:hidden": isSidebarCollapsed,
|
||||
"lg:hidden": !isSidebarCollapsed,
|
||||
})}
|
||||
>
|
||||
<p className="w-full truncate cursor-pointer text-sm text-custom-text-100">{issue.name}</p>
|
||||
</Tooltip>
|
||||
<WorkspaceDraftIssueQuickActions parentRef={issueRef} MENU_ITEMS={MENU_ITEMS} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* quick actions */}
|
||||
<div
|
||||
className={cn("block border border-custom-border-300 rounded", {
|
||||
"md:hidden": isSidebarCollapsed,
|
||||
"lg:hidden": !isSidebarCollapsed,
|
||||
})}
|
||||
>
|
||||
<WorkspaceDraftIssueQuickActions
|
||||
parentRef={issueRef}
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<DraftIssueProperties
|
||||
className={`relative flex flex-wrap ${isSidebarCollapsed ? "md:flex-grow md:flex-shrink-0" : "lg:flex-grow lg:flex-shrink-0"} items-center gap-2 whitespace-nowrap`}
|
||||
issue={issue}
|
||||
handleUpdate={async (data) => updateIssue(workspaceSlug, issueId, data)}
|
||||
handleDelete={async () => deleteIssue(workspaceSlug, issueId)}
|
||||
handleMoveToIssues={async () => moveIssue(workspaceSlug, issueId, issue)}
|
||||
updateIssue={async (projectId, issueId, data) => {
|
||||
await updateIssue(workspaceSlug, issueId, data);
|
||||
}}
|
||||
activeLayout="List"
|
||||
/>
|
||||
<div
|
||||
className={cn("hidden", {
|
||||
"md:flex": isSidebarCollapsed,
|
||||
"lg:flex": !isSidebarCollapsed,
|
||||
})}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<WorkspaceDraftIssueQuickActions parentRef={issueRef} MENU_ITEMS={MENU_ITEMS} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-shrink-0 items-center gap-2">
|
||||
<DraftIssueProperties
|
||||
className={`relative flex flex-wrap ${isSidebarCollapsed ? "md:flex-grow md:flex-shrink-0" : "lg:flex-grow lg:flex-shrink-0"} items-center gap-2 whitespace-nowrap`}
|
||||
issue={issue}
|
||||
updateIssue={async (projectId, issueId, data) => {
|
||||
await updateIssue(workspaceSlug, issueId, data);
|
||||
}}
|
||||
activeLayout="List"
|
||||
/>
|
||||
<div
|
||||
className={cn("hidden", {
|
||||
"md:flex": isSidebarCollapsed,
|
||||
"lg:flex": !isSidebarCollapsed,
|
||||
})}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<WorkspaceDraftIssueQuickActions
|
||||
parentRef={issueRef}
|
||||
issue={issue}
|
||||
handleUpdate={async (data) => updateIssue(workspaceSlug, issueId, data)}
|
||||
handleDelete={async () => deleteIssue(workspaceSlug, issueId)}
|
||||
handleMoveToIssues={async () => moveIssue(workspaceSlug, issueId, issue)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
</Row>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,131 +1,25 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Placement } from "@popperjs/core";
|
||||
import omit from "lodash/omit";
|
||||
import { observer } from "mobx-react";
|
||||
// icons
|
||||
import { Copy, Pencil, SquareStackIcon, Trash2 } from "lucide-react";
|
||||
// types
|
||||
import { TWorkspaceDraftIssue } from "@plane/types";
|
||||
// ui
|
||||
import { ContextMenu, CustomMenu, TContextMenuItem } from "@plane/ui";
|
||||
// components
|
||||
import { CreateUpdateIssueModal } from "@/components/issues";
|
||||
// constant
|
||||
import { EIssuesStoreType } from "@/constants/issue";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// local components
|
||||
import { WorkspaceDraftIssueDeleteIssueModal } from "./delete-modal";
|
||||
|
||||
export interface IQuickActionProps {
|
||||
issue: TWorkspaceDraftIssue;
|
||||
handleDelete: () => Promise<void>;
|
||||
handleUpdate: (payload: Partial<TWorkspaceDraftIssue>) => Promise<TWorkspaceDraftIssue | undefined>;
|
||||
handleMoveToIssues?: () => Promise<void>;
|
||||
customActionButton?: React.ReactElement;
|
||||
portalElement?: HTMLDivElement | null;
|
||||
placements?: Placement;
|
||||
export interface Props {
|
||||
parentRef: React.RefObject<HTMLElement>;
|
||||
MENU_ITEMS: TContextMenuItem[];
|
||||
}
|
||||
|
||||
export const WorkspaceDraftIssueQuickActions: React.FC<IQuickActionProps> = observer((props) => {
|
||||
const {
|
||||
issue,
|
||||
handleDelete,
|
||||
handleUpdate,
|
||||
handleMoveToIssues,
|
||||
customActionButton,
|
||||
portalElement,
|
||||
placements = "bottom-end",
|
||||
parentRef,
|
||||
} = props;
|
||||
// states
|
||||
const [moveToIssue, setMoveToIssue] = useState(false);
|
||||
const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false);
|
||||
const [issueToEdit, setIssueToEdit] = useState<TWorkspaceDraftIssue | undefined>(undefined);
|
||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||
|
||||
const duplicateIssuePayload = omit(
|
||||
{
|
||||
...issue,
|
||||
name: `${issue.name} (copy)`,
|
||||
is_draft: true,
|
||||
},
|
||||
["id"]
|
||||
);
|
||||
|
||||
const MENU_ITEMS: TContextMenuItem[] = [
|
||||
{
|
||||
key: "edit",
|
||||
title: "Edit",
|
||||
icon: Pencil,
|
||||
action: () => {
|
||||
setIssueToEdit(issue);
|
||||
setCreateUpdateIssueModal(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "make-a-copy",
|
||||
title: "Make a copy",
|
||||
icon: Copy,
|
||||
action: () => {
|
||||
setCreateUpdateIssueModal(true);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "move-to-issues",
|
||||
title: "Move to issues",
|
||||
icon: SquareStackIcon,
|
||||
action: () => {
|
||||
if (handleMoveToIssues) {
|
||||
setMoveToIssue(true);
|
||||
setIssueToEdit(issue);
|
||||
setCreateUpdateIssueModal(true);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "delete",
|
||||
title: "Delete",
|
||||
icon: Trash2,
|
||||
action: () => {
|
||||
setDeleteIssueModal(true);
|
||||
},
|
||||
},
|
||||
];
|
||||
export const WorkspaceDraftIssueQuickActions: React.FC<Props> = observer((props) => {
|
||||
const { parentRef, MENU_ITEMS } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<WorkspaceDraftIssueDeleteIssueModal
|
||||
data={issue}
|
||||
isOpen={deleteIssueModal}
|
||||
handleClose={() => setDeleteIssueModal(false)}
|
||||
onSubmit={handleDelete}
|
||||
/>
|
||||
<CreateUpdateIssueModal
|
||||
isOpen={createUpdateIssueModal}
|
||||
onClose={() => {
|
||||
setCreateUpdateIssueModal(false);
|
||||
setIssueToEdit(undefined);
|
||||
setMoveToIssue(false);
|
||||
}}
|
||||
data={issueToEdit ?? duplicateIssuePayload}
|
||||
onSubmit={async (data) => {
|
||||
if (issueToEdit && handleUpdate) await handleUpdate(data as TWorkspaceDraftIssue);
|
||||
}}
|
||||
storeType={EIssuesStoreType.WORKSPACE_DRAFT}
|
||||
fetchIssueDetails={false}
|
||||
moveToIssue={moveToIssue}
|
||||
isDraft
|
||||
/>
|
||||
<ContextMenu parentRef={parentRef} items={MENU_ITEMS} />
|
||||
<CustomMenu
|
||||
ellipsis
|
||||
customButton={customActionButton}
|
||||
portalElement={portalElement}
|
||||
placement={placements}
|
||||
placement="bottom-end"
|
||||
menuItemsClassName="z-[14]"
|
||||
maxHeight="lg"
|
||||
useCaptureForOutsideClick
|
||||
|
||||
Reference in New Issue
Block a user