plane/web/components/labels/project-setting-label-list.tsx

232 lines
8.2 KiB
TypeScript

import React, { useState, useRef } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { observer } from "mobx-react-lite";
import {
DragDropContext,
Draggable,
DraggableProvided,
DraggableStateSnapshot,
DropResult,
Droppable,
} from "@hello-pangea/dnd";
// store
import { useMobxStore } from "lib/mobx/store-provider";
// hooks
import useDraggableInPortal from "hooks/use-draggable-portal";
// components
import {
CreateUpdateLabelInline,
DeleteLabelModal,
ProjectSettingLabelGroup,
ProjectSettingLabelItem,
} from "components/labels";
// ui
import { Button, Loader } from "@plane/ui";
import { EmptyState } from "components/common";
// images
import emptyLabel from "public/empty-state/label.svg";
// types
import { IIssueLabel } from "types";
const LABELS_ROOT = "labels.root";
export const ProjectSettingsLabelList: React.FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const renderDraggable = useDraggableInPortal();
// store
const {
projectLabel: { fetchProjectLabels, projectLabels, updateLabelPosition, projectLabelsTree },
} = useMobxStore();
// states
const [showLabelForm, setLabelForm] = useState(false);
const [isUpdating, setIsUpdating] = useState(false);
const [selectDeleteLabel, setSelectDeleteLabel] = useState<IIssueLabel | null>(null);
const [isDraggingGroup, setIsDraggingGroup] = useState(false);
// ref
const scrollToRef = useRef<HTMLFormElement>(null);
// api call to fetch project details
useSWR(
workspaceSlug && projectId ? "PROJECT_LABELS" : null,
workspaceSlug && projectId ? () => fetchProjectLabels(workspaceSlug.toString(), projectId.toString()) : null
);
const newLabel = () => {
setIsUpdating(false);
setLabelForm(true);
};
const onDragEnd = (result: DropResult) => {
const { combine, draggableId, destination, source } = result;
// return if dropped outside the DragDropContext
if (!combine && !destination) return;
const childLabel = draggableId.split(".")[2];
let parentLabel: string | undefined | null = destination?.droppableId?.split(".")[3];
const index = destination?.index || 0;
const prevParentLabel: string | undefined | null = source?.droppableId?.split(".")[3];
const prevIndex = source?.index;
if (combine && combine.draggableId) parentLabel = combine?.draggableId?.split(".")[2];
if (destination?.droppableId === LABELS_ROOT) parentLabel = null;
if (result.reason == "DROP" && childLabel != parentLabel) {
updateLabelPosition(
workspaceSlug?.toString()!,
projectId?.toString()!,
childLabel,
parentLabel,
index,
prevParentLabel == parentLabel,
prevIndex
);
return;
}
};
return (
<>
<DeleteLabelModal
isOpen={!!selectDeleteLabel}
data={selectDeleteLabel ?? null}
onClose={() => setSelectDeleteLabel(null)}
/>
<div className="flex items-center justify-between border-b border-custom-border-100 py-3.5">
<h3 className="text-xl font-medium">Labels</h3>
<Button variant="primary" onClick={newLabel} size="sm">
Add label
</Button>
</div>
<div className="w-full">
{showLabelForm && (
<div className="w-full rounded border border-custom-border-200 px-3.5 py-2">
<CreateUpdateLabelInline
labelForm={showLabelForm}
setLabelForm={setLabelForm}
isUpdating={isUpdating}
ref={scrollToRef}
onClose={() => {
setLabelForm(false);
setIsUpdating(false);
}}
/>
</div>
)}
{/* labels */}
<>
{projectLabelsTree && (
<DragDropContext
onDragEnd={onDragEnd}
autoScrollerOptions={{
startFromPercentage: 1,
disabled: false,
maxScrollAtPercentage: 0,
maxPixelScroll: 2,
}}
>
<Droppable
droppableId={LABELS_ROOT}
isCombineEnabled={!isDraggingGroup}
ignoreContainerClipping
isDropDisabled={isUpdating}
>
{(droppableProvided, droppableSnapshot) => (
<div className="mt-3" ref={droppableProvided.innerRef} {...droppableProvided.droppableProps}>
{projectLabelsTree.map((label, index) => {
if (label.children && label.children.length) {
return (
<Draggable
key={`label.draggable.${label.id}`}
draggableId={`label.draggable.${label.id}.group`}
index={index}
isDragDisabled={isUpdating}
>
{(provided: DraggableProvided, snapshot: DraggableStateSnapshot) => {
const isGroup = droppableSnapshot.draggingFromThisWith?.split(".")[3] === "group";
setIsDraggingGroup(isGroup);
return (
<div ref={provided.innerRef} {...provided.draggableProps} className="mt-3">
<ProjectSettingLabelGroup
key={label.id}
label={label}
labelChildren={label.children || []}
isDropDisabled={isGroup}
dragHandleProps={provided.dragHandleProps!}
handleLabelDelete={(label: IIssueLabel) => setSelectDeleteLabel(label)}
draggableSnapshot={snapshot}
isUpdating={isUpdating}
setIsUpdating={setIsUpdating}
/>
</div>
);
}}
</Draggable>
);
}
return (
<Draggable
key={`label.draggable.${label.id}`}
draggableId={`label.draggable.${label.id}`}
index={index}
isDragDisabled={isUpdating}
>
{renderDraggable((provided: DraggableProvided, snapshot: DraggableStateSnapshot) => (
<div ref={provided.innerRef} {...provided.draggableProps} className="mt-3">
<ProjectSettingLabelItem
dragHandleProps={provided.dragHandleProps!}
draggableSnapshot={snapshot}
label={label}
setIsUpdating={setIsUpdating}
handleLabelDelete={(label) => setSelectDeleteLabel(label)}
isChild={false}
/>
</div>
))}
</Draggable>
);
})}
{droppableProvided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
)}
</>
{/* loading state */}
{!projectLabels && (
<Loader className="space-y-5">
<Loader.Item height="42px" />
<Loader.Item height="42px" />
<Loader.Item height="42px" />
<Loader.Item height="42px" />
</Loader>
)}
{/* empty state */}
{projectLabels && projectLabels.length === 0 && (
<EmptyState
title="No labels yet"
description="Create labels to help organize and filter issues in you project"
image={emptyLabel}
primaryButton={{
text: "Add label",
onClick: () => newLabel(),
}}
/>
)}
</div>
</>
);
});