mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
[WEB-1249] feat: Kanban multi dragndrop (#4479)
* Kanban multi dnd * complete Kanban multi dnd * add proper brackets to if conditions
This commit is contained in:
parent
bab52a2672
commit
1ad7011aac
@ -87,6 +87,8 @@ module.exports = {
|
||||
800: convertToRGB("--color-background-800"),
|
||||
900: convertToRGB("--color-background-900"),
|
||||
1000: "rgb(0, 0, 0)",
|
||||
primary: convertToRGB(" --color-background-primary"),
|
||||
error: convertToRGB(" --color-background-error"),
|
||||
DEFAULT: convertToRGB("--color-background-100"),
|
||||
},
|
||||
text: {
|
||||
@ -110,6 +112,8 @@ module.exports = {
|
||||
800: convertToRGB("--color-text-800"),
|
||||
900: convertToRGB("--color-text-900"),
|
||||
1000: "rgb(0, 0, 0)",
|
||||
primary: convertToRGB("--color-text-primary"),
|
||||
error: convertToRGB("--color-text-error"),
|
||||
DEFAULT: convertToRGB("--color-text-100"),
|
||||
},
|
||||
border: {
|
||||
@ -119,8 +123,18 @@ module.exports = {
|
||||
300: convertToRGB("--color-border-300"),
|
||||
400: convertToRGB("--color-border-400"),
|
||||
1000: "rgb(0, 0, 0)",
|
||||
primary: convertToRGB("--color-border-primary"),
|
||||
error: convertToRGB("--color-border-error"),
|
||||
DEFAULT: convertToRGB("--color-border-200"),
|
||||
},
|
||||
error: {
|
||||
10: convertToRGB("--color-error-10"),
|
||||
20: convertToRGB("--color-error-20"),
|
||||
30: convertToRGB("--color-error-30"),
|
||||
100: convertToRGB("--color-error-100"),
|
||||
200: convertToRGB("--color-error-200"),
|
||||
500: convertToRGB("--color-error-500"),
|
||||
},
|
||||
sidebar: {
|
||||
background: {
|
||||
0: "rgb(255, 255, 255)",
|
||||
|
2
packages/types/src/issues.d.ts
vendored
2
packages/types/src/issues.d.ts
vendored
@ -217,6 +217,8 @@ export interface IGroupByColumn {
|
||||
name: string;
|
||||
icon: ReactElement | undefined;
|
||||
payload: Partial<TIssue>;
|
||||
isDropDisabled?: boolean;
|
||||
dropErrorMessage?: string;
|
||||
}
|
||||
|
||||
export interface IIssueMap {
|
||||
|
@ -12,7 +12,6 @@ type Props = {
|
||||
alwaysRender?: boolean;
|
||||
placeholderChildren?: ReactNode;
|
||||
pauseHeightUpdateWhileRendering?: boolean;
|
||||
changingReference?: any;
|
||||
};
|
||||
|
||||
const RenderIfVisible: React.FC<Props> = (props) => {
|
||||
@ -27,7 +26,6 @@ const RenderIfVisible: React.FC<Props> = (props) => {
|
||||
alwaysRender = false, //render the children even if it is not visible in root
|
||||
placeholderChildren = null, //placeholder children
|
||||
pauseHeightUpdateWhileRendering = false, //while this is true the height of the blocks are maintained
|
||||
changingReference, //This is to force render when this reference is changed
|
||||
} = props;
|
||||
const [shouldVisible, setShouldVisible] = useState<boolean>(alwaysRender);
|
||||
const placeholderHeight = useRef<string>(defaultHeight);
|
||||
@ -63,7 +61,7 @@ const RenderIfVisible: React.FC<Props> = (props) => {
|
||||
}
|
||||
};
|
||||
}
|
||||
}, [intersectionRef, children, changingReference, root, verticalOffset, horizontalOffset]);
|
||||
}, [intersectionRef, children, root, verticalOffset, horizontalOffset]);
|
||||
|
||||
//Set height after render
|
||||
useEffect(() => {
|
||||
|
@ -4,21 +4,25 @@ import { dropTargetForElements } from "@atlaskit/pragmatic-drag-and-drop/element
|
||||
import { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/router";
|
||||
// hooks
|
||||
import { TIssue } from "@plane/types";
|
||||
import { Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
||||
import { DeleteIssueModal } from "@/components/issues";
|
||||
import { ISSUE_DELETED } from "@/constants/event-tracker";
|
||||
import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue";
|
||||
import { EUserProjectRoles } from "@/constants/project";
|
||||
// hooks
|
||||
import { useEventTracker, useIssueDetail, useIssues, useKanbanView, useUser } from "@/hooks/store";
|
||||
import { useIssuesActions } from "@/hooks/use-issues-actions";
|
||||
// store
|
||||
import { ISSUE_FILTER_DEFAULT_DATA } from "@/store/issue/helpers/issue-helper.store";
|
||||
// ui
|
||||
// types
|
||||
import { IQuickActionProps, TRenderQuickActions } from "../list/list-view-types";
|
||||
//components
|
||||
import { GroupDropLocation, handleGroupDragDrop, getSourceFromDropPayload } from "../utils";
|
||||
import { KanBan } from "./default";
|
||||
import { KanBanSwimLanes } from "./swimlanes";
|
||||
import { KanbanDropLocation, handleDragDrop, getSourceFromDropPayload } from "./utils";
|
||||
|
||||
|
||||
export type KanbanStoreType =
|
||||
| EIssuesStoreType.PROJECT
|
||||
@ -61,6 +65,12 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
} = useIssueDetail();
|
||||
const { updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue, updateFilters } =
|
||||
useIssuesActions(storeType);
|
||||
const {
|
||||
issues: { addCycleToIssue, removeCycleFromIssue },
|
||||
} = useIssues(EIssuesStoreType.CYCLE);
|
||||
const {
|
||||
issues: { changeModulesInIssue },
|
||||
} = useIssues(EIssuesStoreType.MODULE);
|
||||
|
||||
const deleteAreaRef = useRef<HTMLDivElement | null>(null);
|
||||
const [isDragOverDelete, setIsDragOverDelete] = useState(false);
|
||||
@ -143,7 +153,60 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
);
|
||||
}, [deleteAreaRef?.current, setIsDragOverDelete, setDraggedIssueId, setDeleteIssueModal]);
|
||||
|
||||
const handleOnDrop = async (source: KanbanDropLocation, destination: KanbanDropLocation) => {
|
||||
/**
|
||||
* update Issue on Drop, checks if modules or cycles are changed and then calls appropriate functions
|
||||
* @param projectId
|
||||
* @param issueId
|
||||
* @param data
|
||||
* @param issueUpdates
|
||||
*/
|
||||
const updateIssueOnDrop = async (
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
data: Partial<TIssue>,
|
||||
issueUpdates: {
|
||||
[groupKey: string]: {
|
||||
ADD: string[];
|
||||
REMOVE: string[];
|
||||
};
|
||||
}
|
||||
) => {
|
||||
|
||||
const errorToastProps = {
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error!",
|
||||
message: "Error while updating issue"
|
||||
}
|
||||
const moduleKey = ISSUE_FILTER_DEFAULT_DATA["module"];
|
||||
const cycleKey = ISSUE_FILTER_DEFAULT_DATA["cycle"];
|
||||
|
||||
const isModuleChanged = Object.keys(data).includes(moduleKey);
|
||||
const isCycleChanged = Object.keys(data).includes(cycleKey);
|
||||
|
||||
if (isCycleChanged && workspaceSlug) {
|
||||
if(data[cycleKey]) {
|
||||
addCycleToIssue(workspaceSlug.toString(), projectId, data[cycleKey], issueId).catch(() => setToast(errorToastProps));
|
||||
} else {
|
||||
removeCycleFromIssue(workspaceSlug.toString(), projectId, issueId).catch(() => setToast(errorToastProps))
|
||||
}
|
||||
delete data[cycleKey];
|
||||
}
|
||||
|
||||
if (isModuleChanged && workspaceSlug && issueUpdates[moduleKey]) {
|
||||
changeModulesInIssue(
|
||||
workspaceSlug.toString(),
|
||||
projectId,
|
||||
issueId,
|
||||
issueUpdates[moduleKey].ADD,
|
||||
issueUpdates[moduleKey].REMOVE
|
||||
).catch(() => setToast(errorToastProps));
|
||||
delete data[moduleKey];
|
||||
}
|
||||
|
||||
updateIssue && updateIssue(projectId, issueId, data).catch(() => setToast(errorToastProps));
|
||||
};
|
||||
|
||||
const handleOnDrop = async (source: GroupDropLocation, destination: GroupDropLocation) => {
|
||||
if (
|
||||
source.columnId &&
|
||||
destination.columnId &&
|
||||
@ -152,12 +215,12 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
)
|
||||
return;
|
||||
|
||||
await handleDragDrop(
|
||||
await handleGroupDragDrop(
|
||||
source,
|
||||
destination,
|
||||
getIssueById,
|
||||
issues.getIssueIds,
|
||||
updateIssue,
|
||||
updateIssueOnDrop,
|
||||
group_by,
|
||||
sub_group_by,
|
||||
orderBy !== "sort_order"
|
||||
@ -207,8 +270,11 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
const handleKanbanFilters = (toggle: "group_by" | "sub_group_by", value: string) => {
|
||||
if (workspaceSlug && projectId) {
|
||||
let kanbanFilters = issuesFilter?.issueFilters?.kanbanFilters?.[toggle] || [];
|
||||
if (kanbanFilters.includes(value)) kanbanFilters = kanbanFilters.filter((_value) => _value != value);
|
||||
else kanbanFilters.push(value);
|
||||
if (kanbanFilters.includes(value)) {
|
||||
kanbanFilters = kanbanFilters.filter((_value) => _value != value);
|
||||
} else {
|
||||
kanbanFilters.push(value);
|
||||
}
|
||||
updateFilters(projectId.toString(), EIssueFilterType.KANBAN_FILTERS, {
|
||||
[toggle]: kanbanFilters,
|
||||
});
|
||||
|
@ -16,12 +16,15 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
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;
|
||||
@ -30,7 +33,6 @@ interface IssueBlockProps {
|
||||
quickActions: TRenderQuickActions;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||
issueIds: string[]; //DO NOT REMOVE< needed to force render for virtualization
|
||||
}
|
||||
|
||||
interface IssueDetailsBlockProps {
|
||||
@ -99,6 +101,8 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
|
||||
export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
||||
const {
|
||||
issueId,
|
||||
groupId,
|
||||
subGroupId,
|
||||
issuesMap,
|
||||
displayProperties,
|
||||
isDragDisabled,
|
||||
@ -106,7 +110,6 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
||||
quickActions,
|
||||
canEditProperties,
|
||||
scrollableContainerRef,
|
||||
issueIds,
|
||||
} = props;
|
||||
|
||||
const cardRef = useRef<HTMLAnchorElement | null>(null);
|
||||
@ -194,7 +197,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
||||
}}
|
||||
>
|
||||
<ControlLink
|
||||
id={`issue-${issue.id}`}
|
||||
id={getIssueBlockId(issueId, groupId, subGroupId)}
|
||||
href={`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}issues/${
|
||||
issue.id
|
||||
}`}
|
||||
@ -214,7 +217,6 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
||||
root={scrollableContainerRef}
|
||||
defaultHeight="100px"
|
||||
horizontalOffset={50}
|
||||
changingReference={issueIds}
|
||||
>
|
||||
<KanbanIssueDetailsBlock
|
||||
cardRef={cardRef}
|
||||
|
@ -7,7 +7,7 @@ import { TRenderQuickActions } from "../list/list-view-types";
|
||||
|
||||
interface IssueBlocksListProps {
|
||||
sub_group_id: string;
|
||||
columnId: string;
|
||||
groupId: string;
|
||||
issuesMap: IIssueMap;
|
||||
issueIds: string[];
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
@ -21,7 +21,7 @@ interface IssueBlocksListProps {
|
||||
const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
|
||||
const {
|
||||
sub_group_id,
|
||||
columnId,
|
||||
groupId,
|
||||
issuesMap,
|
||||
issueIds,
|
||||
displayProperties,
|
||||
@ -40,13 +40,15 @@ const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
|
||||
if (!issueId) return null;
|
||||
|
||||
let draggableId = issueId;
|
||||
if (columnId) draggableId = `${draggableId}__${columnId}`;
|
||||
if (groupId) draggableId = `${draggableId}__${groupId}`;
|
||||
if (sub_group_id) draggableId = `${draggableId}__${sub_group_id}`;
|
||||
|
||||
return (
|
||||
<KanbanIssueBlock
|
||||
key={draggableId}
|
||||
issueId={issueId}
|
||||
groupId={groupId}
|
||||
subGroupId={sub_group_id}
|
||||
issuesMap={issuesMap}
|
||||
displayProperties={displayProperties}
|
||||
updateIssue={updateIssue}
|
||||
@ -55,7 +57,6 @@ const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
|
||||
isDragDisabled={isDragDisabled}
|
||||
canEditProperties={canEditProperties}
|
||||
scrollableContainerRef={scrollableContainerRef}
|
||||
issueIds={issueIds} //passing to force render for virtualization whenever parent rerenders
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -19,12 +19,11 @@ import { useCycle, useKanbanView, useLabel, useMember, useModule, useProject, us
|
||||
// types
|
||||
// parent components
|
||||
import { TRenderQuickActions } from "../list/list-view-types";
|
||||
import { getGroupByColumns, isWorkspaceLevel } from "../utils";
|
||||
import { getGroupByColumns, isWorkspaceLevel, GroupDropLocation } from "../utils";
|
||||
// components
|
||||
import { KanbanStoreType } from "./base-kanban-root";
|
||||
import { HeaderGroupByCard } from "./headers/group-by-card";
|
||||
import { KanbanGroup } from "./kanban-group";
|
||||
import { KanbanDropLocation } from "./utils";
|
||||
|
||||
export interface IGroupByKanBan {
|
||||
issuesMap: IIssueMap;
|
||||
@ -52,7 +51,7 @@ export interface IGroupByKanBan {
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||
handleOnDrop: (source: KanbanDropLocation, destination: KanbanDropLocation) => Promise<void>;
|
||||
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||
showEmptyGroup?: boolean;
|
||||
subGroupIssueHeaderCount?: (listId: string) => number;
|
||||
}
|
||||
@ -176,6 +175,8 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
orderBy={orderBy}
|
||||
sub_group_id={sub_group_id}
|
||||
isDragDisabled={isDragDisabled}
|
||||
isDropDisabled={!!subList.isDropDisabled}
|
||||
dropErrorMessage={subList.dropErrorMessage}
|
||||
updateIssue={updateIssue}
|
||||
quickActions={quickActions}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
@ -220,7 +221,7 @@ export interface IKanBan {
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||
handleOnDrop: (source: KanbanDropLocation, destination: KanbanDropLocation) => Promise<void>;
|
||||
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||
subGroupIssueHeaderCount?: (listId: string) => number;
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ import { cn } from "@/helpers/common.helper";
|
||||
import { useProjectState } from "@/hooks/store";
|
||||
//components
|
||||
import { TRenderQuickActions } from "../list/list-view-types";
|
||||
import { KanbanDropLocation, getSourceFromDropPayload, getDestinationFromDropPayload } from "./utils";
|
||||
import { GroupDropLocation, getSourceFromDropPayload, getDestinationFromDropPayload, getIssueBlockId } from "../utils";
|
||||
import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from ".";
|
||||
|
||||
interface IKanbanGroup {
|
||||
@ -33,6 +33,8 @@ interface IKanbanGroup {
|
||||
group_by: TIssueGroupByOptions | undefined;
|
||||
sub_group_id: string;
|
||||
isDragDisabled: boolean;
|
||||
isDropDisabled: boolean;
|
||||
dropErrorMessage: string | undefined;
|
||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
||||
quickActions: TRenderQuickActions;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
@ -47,7 +49,7 @@ interface IKanbanGroup {
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
groupByVisibilityToggle?: boolean;
|
||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||
handleOnDrop: (source: KanbanDropLocation, destination: KanbanDropLocation) => Promise<void>;
|
||||
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||
orderBy: TIssueOrderByOptions | undefined;
|
||||
}
|
||||
|
||||
@ -62,6 +64,8 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
||||
displayProperties,
|
||||
issueIds,
|
||||
isDragDisabled,
|
||||
isDropDisabled,
|
||||
dropErrorMessage,
|
||||
updateIssue,
|
||||
quickActions,
|
||||
canEditProperties,
|
||||
@ -103,18 +107,21 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
||||
const source = getSourceFromDropPayload(payload);
|
||||
const destination = getDestinationFromDropPayload(payload);
|
||||
|
||||
if (!source || !destination) return;
|
||||
if (!source || !destination || isDropDisabled) return;
|
||||
|
||||
handleOnDrop(source, destination);
|
||||
|
||||
highlightIssueOnDrop(payload.source.element.id, orderBy !== "sort_order");
|
||||
highlightIssueOnDrop(
|
||||
getIssueBlockId(source.id, destination?.groupId, destination?.subGroupId),
|
||||
orderBy !== "sort_order"
|
||||
);
|
||||
},
|
||||
}),
|
||||
autoScrollForElements({
|
||||
element,
|
||||
})
|
||||
);
|
||||
}, [columnRef?.current, groupId, sub_group_id, setIsDraggingOverColumn, orderBy]);
|
||||
}, [columnRef?.current, groupId, sub_group_id, setIsDraggingOverColumn, orderBy, isDropDisabled, handleOnDrop]);
|
||||
|
||||
const prePopulateQuickAddData = (
|
||||
groupByKey: string | undefined,
|
||||
@ -168,7 +175,7 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
||||
return preloadedData;
|
||||
};
|
||||
|
||||
const shouldOverlay = isDraggingOverColumn && orderBy !== "sort_order";
|
||||
const shouldOverlay = isDraggingOverColumn && (orderBy !== "sort_order" || isDropDisabled);
|
||||
const readableOrderBy = ISSUE_ORDER_BY_OPTIONS.find((orderByObj) => orderByObj.key === orderBy)?.title;
|
||||
|
||||
return (
|
||||
@ -184,20 +191,38 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
||||
<div
|
||||
//column overlay when issues are not sorted by manual
|
||||
className={cn(
|
||||
"absolute top-0 left-0 h-full w-full items-center text-sm font-medium text-custom-text-300 rounded",
|
||||
"absolute top-0 left-0 h-full w-full items-center text-sm font-medium text-custom-text-300 rounded transparent",
|
||||
{
|
||||
"flex flex-col bg-custom-background-80 border-[1px] border-custom-border-300 z-[2]": shouldOverlay,
|
||||
"flex flex-col border-[1px] border-custom-border-300 z-[2]": shouldOverlay,
|
||||
},
|
||||
{ hidden: !shouldOverlay },
|
||||
{ "justify-center": !sub_group_by }
|
||||
)}
|
||||
>
|
||||
{readableOrderBy && <span className="pt-6">The layout is ordered by {readableOrderBy}.</span>}
|
||||
<span>Drop here to move the issue.</span>
|
||||
<div
|
||||
className={cn(
|
||||
"p-3 mt-6 flex flex-col border-[1px] rounded items-center",
|
||||
{
|
||||
"bg-custom-background-primary border-custom-border-primary text-custom-text-primary": shouldOverlay,
|
||||
},
|
||||
{
|
||||
"bg-custom-background-error border-custom-border-error text-custom-text-error": isDropDisabled,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{dropErrorMessage ? (
|
||||
<span>{dropErrorMessage}</span>
|
||||
) : (
|
||||
<>
|
||||
{readableOrderBy && <span>The layout is ordered by {readableOrderBy}.</span>}
|
||||
<span>Drop here to move the issue.</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<KanbanIssueBlocksList
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={groupId}
|
||||
groupId={groupId}
|
||||
issuesMap={issuesMap}
|
||||
issueIds={(issueIds as TGroupedIssues)?.[groupId] || []}
|
||||
displayProperties={displayProperties}
|
||||
|
@ -16,12 +16,11 @@ import {
|
||||
// components
|
||||
import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "@/hooks/store";
|
||||
import { TRenderQuickActions } from "../list/list-view-types";
|
||||
import { getGroupByColumns, isWorkspaceLevel } from "../utils";
|
||||
import { getGroupByColumns, isWorkspaceLevel, GroupDropLocation } from "../utils";
|
||||
import { KanbanStoreType } from "./base-kanban-root";
|
||||
import { KanBan } from "./default";
|
||||
import { HeaderGroupByCard } from "./headers/group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./headers/sub-group-by-card";
|
||||
import { KanbanDropLocation } from "./utils";
|
||||
// types
|
||||
// constants
|
||||
|
||||
@ -111,7 +110,7 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||
quickActions: TRenderQuickActions;
|
||||
kanbanFilters: TIssueKanbanFilters;
|
||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||
handleOnDrop: (source: KanbanDropLocation, destination: KanbanDropLocation) => Promise<void>;
|
||||
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||
disableIssueCreation?: boolean;
|
||||
storeType: KanbanStoreType;
|
||||
enableQuickIssueCreate: boolean;
|
||||
@ -244,7 +243,7 @@ export interface IKanBanSwimLanes {
|
||||
kanbanFilters: TIssueKanbanFilters;
|
||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||
showEmptyGroup: boolean;
|
||||
handleOnDrop: (source: KanbanDropLocation, destination: KanbanDropLocation) => Promise<void>;
|
||||
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||
disableIssueCreation?: boolean;
|
||||
storeType: KanbanStoreType;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||
|
@ -1,213 +0,0 @@
|
||||
import pull from "lodash/pull";
|
||||
import { IPragmaticDropPayload, TIssue, TIssueGroupByOptions } from "@plane/types";
|
||||
import { ISSUE_FILTER_DEFAULT_DATA } from "@/store/issue/helpers/issue-helper.store";
|
||||
|
||||
export type KanbanDropLocation = {
|
||||
columnId: string;
|
||||
groupId: string;
|
||||
subGroupId?: string;
|
||||
id: string | undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* get Kanban Source data from Pragmatic Payload
|
||||
* @param payload
|
||||
* @returns
|
||||
*/
|
||||
export const getSourceFromDropPayload = (payload: IPragmaticDropPayload): KanbanDropLocation | undefined => {
|
||||
const { location, source: sourceIssue } = payload;
|
||||
|
||||
const sourceIssueData = sourceIssue.data;
|
||||
let sourceColumData;
|
||||
|
||||
const sourceDropTargets = location?.initial?.dropTargets ?? [];
|
||||
for (const dropTarget of sourceDropTargets) {
|
||||
const dropTargetData = dropTarget?.data;
|
||||
|
||||
if (!dropTargetData) continue;
|
||||
|
||||
if (dropTargetData.type === "COLUMN") {
|
||||
sourceColumData = dropTargetData;
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceIssueData?.id === undefined || !sourceColumData?.groupId) return;
|
||||
|
||||
return {
|
||||
groupId: sourceColumData.groupId as string,
|
||||
subGroupId: sourceColumData.subGroupId as string,
|
||||
columnId: sourceColumData.columnId as string,
|
||||
id: sourceIssueData.id as string,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* get Destination Source data from Pragmatic Payload
|
||||
* @param payload
|
||||
* @returns
|
||||
*/
|
||||
export const getDestinationFromDropPayload = (payload: IPragmaticDropPayload): KanbanDropLocation | undefined => {
|
||||
const { location } = payload;
|
||||
|
||||
let destinationIssueData, destinationColumnData;
|
||||
|
||||
const destDropTargets = location?.current?.dropTargets ?? [];
|
||||
|
||||
for (const dropTarget of destDropTargets) {
|
||||
const dropTargetData = dropTarget?.data;
|
||||
|
||||
if (!dropTargetData) continue;
|
||||
|
||||
if (dropTargetData.type === "COLUMN" || dropTargetData.type === "DELETE") {
|
||||
destinationColumnData = dropTargetData;
|
||||
}
|
||||
|
||||
if (dropTargetData.type === "ISSUE") {
|
||||
destinationIssueData = dropTargetData;
|
||||
}
|
||||
}
|
||||
|
||||
if (!destinationColumnData?.groupId) return;
|
||||
|
||||
return {
|
||||
groupId: destinationColumnData.groupId as string,
|
||||
subGroupId: destinationColumnData.subGroupId as string,
|
||||
columnId: destinationColumnData.columnId as string,
|
||||
id: destinationIssueData?.id as string | undefined,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns Sort order of the issue block at the position of drop
|
||||
* @param destinationIssues
|
||||
* @param destinationIssueId
|
||||
* @param getIssueById
|
||||
* @returns
|
||||
*/
|
||||
const handleSortOrder = (
|
||||
destinationIssues: string[],
|
||||
destinationIssueId: string | undefined,
|
||||
getIssueById: (issueId: string) => TIssue | undefined,
|
||||
shouldAddIssueAtTop = false
|
||||
) => {
|
||||
const sortOrderDefaultValue = 65535;
|
||||
let currentIssueState = {};
|
||||
|
||||
const destinationIndex = destinationIssueId
|
||||
? destinationIssues.indexOf(destinationIssueId)
|
||||
: shouldAddIssueAtTop
|
||||
? 0
|
||||
: destinationIssues.length;
|
||||
|
||||
if (destinationIssues && destinationIssues.length > 0) {
|
||||
if (destinationIndex === 0) {
|
||||
const destinationIssueId = destinationIssues[0];
|
||||
const destinationIssue = getIssueById(destinationIssueId);
|
||||
if (!destinationIssue) return currentIssueState;
|
||||
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: destinationIssue.sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destinationIndex === destinationIssues.length) {
|
||||
const destinationIssueId = destinationIssues[destinationIssues.length - 1];
|
||||
const destinationIssue = getIssueById(destinationIssueId);
|
||||
if (!destinationIssue) return currentIssueState;
|
||||
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: destinationIssue.sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
const destinationTopIssueId = destinationIssues[destinationIndex - 1];
|
||||
const destinationBottomIssueId = destinationIssues[destinationIndex];
|
||||
|
||||
const destinationTopIssue = getIssueById(destinationTopIssueId);
|
||||
const destinationBottomIssue = getIssueById(destinationBottomIssueId);
|
||||
if (!destinationTopIssue || !destinationBottomIssue) return currentIssueState;
|
||||
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: (destinationTopIssue.sort_order + destinationBottomIssue.sort_order) / 2,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: sortOrderDefaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
return currentIssueState;
|
||||
};
|
||||
|
||||
export const handleDragDrop = async (
|
||||
source: KanbanDropLocation,
|
||||
destination: KanbanDropLocation,
|
||||
getIssueById: (issueId: string) => TIssue | undefined,
|
||||
getIssueIds: (groupId?: string, subGroupId?: string) => string[] | undefined,
|
||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined,
|
||||
groupBy: TIssueGroupByOptions | undefined,
|
||||
subGroupBy: TIssueGroupByOptions | undefined,
|
||||
shouldAddIssueAtTop = false
|
||||
) => {
|
||||
if (!source.id || !groupBy || (subGroupBy && (!source.subGroupId || !destination.subGroupId))) return;
|
||||
|
||||
let updatedIssue: Partial<TIssue> = {};
|
||||
const sourceIssues = getIssueIds(source.groupId, source.subGroupId);
|
||||
const destinationIssues = getIssueIds(destination.groupId, destination.subGroupId);
|
||||
|
||||
const sourceIssue = getIssueById(source.id);
|
||||
|
||||
if (!sourceIssues || !destinationIssues || !sourceIssue) return;
|
||||
|
||||
updatedIssue = {
|
||||
id: sourceIssue.id,
|
||||
project_id: sourceIssue.project_id,
|
||||
};
|
||||
|
||||
// for both horizontal and vertical dnd
|
||||
updatedIssue = {
|
||||
...updatedIssue,
|
||||
...handleSortOrder(destinationIssues, destination.id, getIssueById, shouldAddIssueAtTop),
|
||||
};
|
||||
|
||||
if (source.groupId && destination.groupId && source.groupId !== destination.groupId) {
|
||||
const groupKey = ISSUE_FILTER_DEFAULT_DATA[groupBy];
|
||||
let groupValue = sourceIssue[groupKey];
|
||||
|
||||
if (Array.isArray(groupValue)) {
|
||||
pull(groupValue, source.groupId);
|
||||
groupValue.push(destination.groupId);
|
||||
} else {
|
||||
groupValue = destination.groupId;
|
||||
}
|
||||
|
||||
updatedIssue = { ...updatedIssue, [groupKey]: groupValue };
|
||||
}
|
||||
|
||||
if (subGroupBy && source.subGroupId && destination.subGroupId && source.subGroupId !== destination.subGroupId) {
|
||||
const subGroupKey = ISSUE_FILTER_DEFAULT_DATA[subGroupBy];
|
||||
let subGroupValue = sourceIssue[subGroupKey];
|
||||
|
||||
if (Array.isArray(subGroupValue)) {
|
||||
pull(subGroupValue, source.subGroupId);
|
||||
subGroupValue.push(destination.subGroupId);
|
||||
} else {
|
||||
subGroupValue = destination.subGroupId;
|
||||
}
|
||||
|
||||
updatedIssue = { ...updatedIssue, [subGroupKey]: subGroupValue };
|
||||
}
|
||||
|
||||
if (updatedIssue) {
|
||||
return (
|
||||
updateIssue &&
|
||||
(await updateIssue(sourceIssue.project_id, sourceIssue.id, {
|
||||
...updatedIssue,
|
||||
id: sourceIssue.id,
|
||||
project_id: sourceIssue.project_id,
|
||||
}))
|
||||
);
|
||||
}
|
||||
};
|
@ -50,7 +50,6 @@ export const IssueBlockRoot: FC<Props> = observer((props) => {
|
||||
defaultHeight="3rem"
|
||||
root={containerRef}
|
||||
classNames="relative border-b border-b-custom-border-200 last:border-b-transparent"
|
||||
changingReference={issueIds}
|
||||
>
|
||||
<IssueBlock
|
||||
issueId={issueId}
|
||||
|
@ -49,10 +49,10 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||
const { labelMap } = useLabel();
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const {
|
||||
issues: { addModulesToIssue, removeModulesFromIssue },
|
||||
issues: { changeModulesInIssue },
|
||||
} = useIssues(EIssuesStoreType.MODULE);
|
||||
const {
|
||||
issues: { addIssueToCycle, removeIssueFromCycle },
|
||||
issues: { addCycleToIssue, removeCycleFromIssue },
|
||||
} = useIssues(EIssuesStoreType.CYCLE);
|
||||
const { areEstimatesEnabledForCurrentProject } = useEstimate();
|
||||
const { getStateById } = useProjectState();
|
||||
@ -69,22 +69,22 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||
() => ({
|
||||
addModulesToIssue: async (moduleIds: string[]) => {
|
||||
if (!workspaceSlug || !issue.project_id || !issue.id) return;
|
||||
await addModulesToIssue?.(workspaceSlug.toString(), issue.project_id, issue.id, moduleIds);
|
||||
await changeModulesInIssue?.(workspaceSlug.toString(), issue.project_id, issue.id, moduleIds, []);
|
||||
},
|
||||
removeModulesFromIssue: async (moduleIds: string[]) => {
|
||||
if (!workspaceSlug || !issue.project_id || !issue.id) return;
|
||||
await removeModulesFromIssue?.(workspaceSlug.toString(), issue.project_id, issue.id, moduleIds);
|
||||
await changeModulesInIssue?.(workspaceSlug.toString(), issue.project_id, issue.id, [], moduleIds);
|
||||
},
|
||||
addIssueToCycle: async (cycleId: string) => {
|
||||
if (!workspaceSlug || !issue.project_id || !issue.id) return;
|
||||
await addIssueToCycle?.(workspaceSlug.toString(), issue.project_id, cycleId, [issue.id]);
|
||||
await addCycleToIssue?.(workspaceSlug.toString(), issue.project_id, cycleId, issue.id);
|
||||
},
|
||||
removeIssueFromCycle: async (cycleId: string) => {
|
||||
removeIssueFromCycle: async () => {
|
||||
if (!workspaceSlug || !issue.project_id || !issue.id) return;
|
||||
await removeIssueFromCycle?.(workspaceSlug.toString(), issue.project_id, cycleId, issue.id);
|
||||
await removeCycleFromIssue?.(workspaceSlug.toString(), issue.project_id, issue.id);
|
||||
},
|
||||
}),
|
||||
[workspaceSlug, issue, addModulesToIssue, removeModulesFromIssue, addIssueToCycle, removeIssueFromCycle]
|
||||
[workspaceSlug, issue, changeModulesInIssue, addCycleToIssue, removeCycleFromIssue]
|
||||
);
|
||||
|
||||
const handleState = (stateId: string) => {
|
||||
@ -174,7 +174,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
||||
(cycleId: string | null) => {
|
||||
if (!issue || issue.cycle_id === cycleId) return;
|
||||
if (cycleId) issueOperations.addIssueToCycle?.(cycleId);
|
||||
else issueOperations.removeIssueFromCycle?.(issue.cycle_id ?? "");
|
||||
else issueOperations.removeIssueFromCycle?.();
|
||||
|
||||
captureIssueEvent({
|
||||
eventName: ISSUE_UPDATED,
|
||||
|
@ -25,14 +25,14 @@ export const SpreadsheetCycleColumn: React.FC<Props> = observer((props) => {
|
||||
// hooks
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const {
|
||||
issues: { addIssueToCycle, removeIssueFromCycle },
|
||||
issues: { addCycleToIssue, removeCycleFromIssue },
|
||||
} = useIssues(EIssuesStoreType.CYCLE);
|
||||
|
||||
const handleCycle = useCallback(
|
||||
async (cycleId: string | null) => {
|
||||
if (!workspaceSlug || !issue || issue.cycle_id === cycleId) return;
|
||||
if (cycleId) await addIssueToCycle(workspaceSlug.toString(), issue.project_id, cycleId, [issue.id]);
|
||||
else await removeIssueFromCycle(workspaceSlug.toString(), issue.project_id, issue.cycle_id ?? "", issue.id);
|
||||
if (cycleId) await addCycleToIssue(workspaceSlug.toString(), issue.project_id, cycleId, issue.id);
|
||||
else await removeCycleFromIssue(workspaceSlug.toString(), issue.project_id, issue.id);
|
||||
captureIssueEvent({
|
||||
eventName: "Issue updated",
|
||||
payload: {
|
||||
@ -44,7 +44,7 @@ export const SpreadsheetCycleColumn: React.FC<Props> = observer((props) => {
|
||||
path: router.asPath,
|
||||
});
|
||||
},
|
||||
[workspaceSlug, issue, addIssueToCycle, removeIssueFromCycle, captureIssueEvent, router.asPath]
|
||||
[workspaceSlug, issue, addCycleToIssue, removeCycleFromIssue, captureIssueEvent, router.asPath]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -26,7 +26,7 @@ export const SpreadsheetModuleColumn: React.FC<Props> = observer((props) => {
|
||||
// hooks
|
||||
const { captureIssueEvent } = useEventTracker();
|
||||
const {
|
||||
issues: { addModulesToIssue, removeModulesFromIssue },
|
||||
issues: { changeModulesInIssue },
|
||||
} = useIssues(EIssuesStoreType.MODULE);
|
||||
|
||||
const handleModule = useCallback(
|
||||
@ -36,13 +36,11 @@ export const SpreadsheetModuleColumn: React.FC<Props> = observer((props) => {
|
||||
const updatedModuleIds = xor(issue.module_ids, moduleIds);
|
||||
const modulesToAdd: string[] = [];
|
||||
const modulesToRemove: string[] = [];
|
||||
for (const moduleId of updatedModuleIds)
|
||||
for (const moduleId of updatedModuleIds) {
|
||||
if (issue.module_ids.includes(moduleId)) modulesToRemove.push(moduleId);
|
||||
else modulesToAdd.push(moduleId);
|
||||
if (modulesToAdd.length > 0)
|
||||
addModulesToIssue(workspaceSlug.toString(), issue.project_id, issue.id, modulesToAdd);
|
||||
if (modulesToRemove.length > 0)
|
||||
removeModulesFromIssue(workspaceSlug.toString(), issue.project_id, issue.id, modulesToRemove);
|
||||
}
|
||||
changeModulesInIssue(workspaceSlug.toString(), issue.project_id, issue.id, modulesToAdd, modulesToRemove);
|
||||
|
||||
captureIssueEvent({
|
||||
eventName: "Issue updated",
|
||||
@ -55,7 +53,7 @@ export const SpreadsheetModuleColumn: React.FC<Props> = observer((props) => {
|
||||
path: router.asPath,
|
||||
});
|
||||
},
|
||||
[workspaceSlug, issue, addModulesToIssue, removeModulesFromIssue, captureIssueEvent, router.asPath]
|
||||
[workspaceSlug, issue, changeModulesInIssue, captureIssueEvent, router.asPath]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -66,7 +66,6 @@ export const SpreadsheetIssueRow = observer((props: Props) => {
|
||||
defaultHeight="calc(2.75rem - 1px)"
|
||||
root={containerRef}
|
||||
placeholderChildren={<td colSpan={100} className="border-b-[0.5px] border-custom-border-200" />}
|
||||
changingReference={issueIds}
|
||||
>
|
||||
<IssueRowDetails
|
||||
issueId={issueId}
|
||||
|
@ -1,6 +1,17 @@
|
||||
import clone from "lodash/clone";
|
||||
import concat from "lodash/concat";
|
||||
import pull from "lodash/pull";
|
||||
import uniq from "lodash/uniq";
|
||||
import scrollIntoView from "smooth-scroll-into-view-if-needed";
|
||||
import { ContrastIcon } from "lucide-react";
|
||||
import { GroupByColumnTypes, IGroupByColumn, TCycleGroups } from "@plane/types";
|
||||
import {
|
||||
GroupByColumnTypes,
|
||||
IGroupByColumn,
|
||||
TCycleGroups,
|
||||
IPragmaticDropPayload,
|
||||
TIssue,
|
||||
TIssueGroupByOptions,
|
||||
} from "@plane/types";
|
||||
import { Avatar, CycleGroupIcon, DiceIcon, PriorityIcon, StateGroupIcon } from "@plane/ui";
|
||||
// components
|
||||
import { ProjectLogo } from "@/components/project";
|
||||
@ -8,6 +19,7 @@ import { ProjectLogo } from "@/components/project";
|
||||
import { ISSUE_PRIORITIES, EIssuesStoreType } from "@/constants/issue";
|
||||
import { STATE_GROUPS } from "@/constants/state";
|
||||
import { ICycleStore } from "@/store/cycle.store";
|
||||
import { ISSUE_FILTER_DEFAULT_DATA } from "@/store/issue/helpers/issue-helper.store";
|
||||
import { ILabelStore } from "@/store/label.store";
|
||||
import { IMemberRootStore } from "@/store/member";
|
||||
import { IModuleStore } from "@/store/module.store";
|
||||
@ -20,6 +32,20 @@ import { IStateStore } from "@/store/state.store";
|
||||
export const HIGHLIGHT_CLASS = "highlight";
|
||||
export const HIGHLIGHT_WITH_LINE = "highlight-with-line";
|
||||
|
||||
export type GroupDropLocation = {
|
||||
columnId: string;
|
||||
groupId: string;
|
||||
subGroupId?: string;
|
||||
id: string | undefined;
|
||||
};
|
||||
|
||||
export type IssueUpdates = {
|
||||
[groupKey: string]: {
|
||||
ADD: string[];
|
||||
REMOVE: string[];
|
||||
};
|
||||
};
|
||||
|
||||
export const isWorkspaceLevel = (type: EIssuesStoreType) =>
|
||||
[EIssuesStoreType.PROFILE, EIssuesStoreType.GLOBAL].includes(type) ? true : false;
|
||||
|
||||
@ -96,11 +122,14 @@ const getCycleColumns = (projectStore: IProjectStore, cycleStore: ICycleStore):
|
||||
const cycle = getCycleById(cycleId);
|
||||
if (cycle) {
|
||||
const cycleStatus = cycle.status ? (cycle.status.toLocaleLowerCase() as TCycleGroups) : "draft";
|
||||
const isDropDisabled = cycleStatus === "completed";
|
||||
cycles.push({
|
||||
id: cycle.id,
|
||||
name: cycle.name,
|
||||
icon: <CycleGroupIcon cycleGroup={cycleStatus as TCycleGroups} className="h-3.5 w-3.5" />,
|
||||
payload: { cycle_id: cycle.id },
|
||||
isDropDisabled,
|
||||
dropErrorMessage: isDropDisabled ? "Issue cannot be moved to completed cycles" : undefined,
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -263,3 +292,226 @@ export const highlightIssueOnDrop = (
|
||||
await scrollIntoView(sourceElement, { behavior: "smooth", block: "center", duration: 1500 });
|
||||
}, 200);
|
||||
};
|
||||
|
||||
/**
|
||||
* get Kanban Source data from Pragmatic Payload
|
||||
* @param payload
|
||||
* @returns
|
||||
*/
|
||||
export const getSourceFromDropPayload = (payload: IPragmaticDropPayload): GroupDropLocation | undefined => {
|
||||
const { location, source: sourceIssue } = payload;
|
||||
|
||||
const sourceIssueData = sourceIssue.data;
|
||||
let sourceColumData;
|
||||
|
||||
const sourceDropTargets = location?.initial?.dropTargets ?? [];
|
||||
for (const dropTarget of sourceDropTargets) {
|
||||
const dropTargetData = dropTarget?.data;
|
||||
|
||||
if (!dropTargetData) continue;
|
||||
|
||||
if (dropTargetData.type === "COLUMN") {
|
||||
sourceColumData = dropTargetData;
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceIssueData?.id === undefined || !sourceColumData?.groupId) return;
|
||||
|
||||
return {
|
||||
groupId: sourceColumData.groupId as string,
|
||||
subGroupId: sourceColumData.subGroupId as string,
|
||||
columnId: sourceColumData.columnId as string,
|
||||
id: sourceIssueData.id as string,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* get Destination Source data from Pragmatic Payload
|
||||
* @param payload
|
||||
* @returns
|
||||
*/
|
||||
export const getDestinationFromDropPayload = (payload: IPragmaticDropPayload): GroupDropLocation | undefined => {
|
||||
const { location } = payload;
|
||||
|
||||
let destinationIssueData, destinationColumnData;
|
||||
|
||||
const destDropTargets = location?.current?.dropTargets ?? [];
|
||||
|
||||
for (const dropTarget of destDropTargets) {
|
||||
const dropTargetData = dropTarget?.data;
|
||||
|
||||
if (!dropTargetData) continue;
|
||||
|
||||
if (dropTargetData.type === "COLUMN" || dropTargetData.type === "DELETE") {
|
||||
destinationColumnData = dropTargetData;
|
||||
}
|
||||
|
||||
if (dropTargetData.type === "ISSUE") {
|
||||
destinationIssueData = dropTargetData;
|
||||
}
|
||||
}
|
||||
|
||||
if (!destinationColumnData?.groupId) return;
|
||||
|
||||
return {
|
||||
groupId: destinationColumnData.groupId as string,
|
||||
subGroupId: destinationColumnData.subGroupId as string,
|
||||
columnId: destinationColumnData.columnId as string,
|
||||
id: destinationIssueData?.id as string | undefined,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns Sort order of the issue block at the position of drop
|
||||
* @param destinationIssues
|
||||
* @param destinationIssueId
|
||||
* @param getIssueById
|
||||
* @returns
|
||||
*/
|
||||
const handleSortOrder = (
|
||||
destinationIssues: string[],
|
||||
destinationIssueId: string | undefined,
|
||||
getIssueById: (issueId: string) => TIssue | undefined,
|
||||
shouldAddIssueAtTop = false
|
||||
) => {
|
||||
const sortOrderDefaultValue = 65535;
|
||||
let currentIssueState = {};
|
||||
|
||||
const destinationIndex = destinationIssueId
|
||||
? destinationIssues.indexOf(destinationIssueId)
|
||||
: shouldAddIssueAtTop
|
||||
? 0
|
||||
: destinationIssues.length;
|
||||
|
||||
if (destinationIssues && destinationIssues.length > 0) {
|
||||
if (destinationIndex === 0) {
|
||||
const destinationIssueId = destinationIssues[0];
|
||||
const destinationIssue = getIssueById(destinationIssueId);
|
||||
if (!destinationIssue) return currentIssueState;
|
||||
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: destinationIssue.sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destinationIndex === destinationIssues.length) {
|
||||
const destinationIssueId = destinationIssues[destinationIssues.length - 1];
|
||||
const destinationIssue = getIssueById(destinationIssueId);
|
||||
if (!destinationIssue) return currentIssueState;
|
||||
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: destinationIssue.sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
const destinationTopIssueId = destinationIssues[destinationIndex - 1];
|
||||
const destinationBottomIssueId = destinationIssues[destinationIndex];
|
||||
|
||||
const destinationTopIssue = getIssueById(destinationTopIssueId);
|
||||
const destinationBottomIssue = getIssueById(destinationBottomIssueId);
|
||||
if (!destinationTopIssue || !destinationBottomIssue) return currentIssueState;
|
||||
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: (destinationTopIssue.sort_order + destinationBottomIssue.sort_order) / 2,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: sortOrderDefaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
return currentIssueState;
|
||||
};
|
||||
|
||||
export const getIssueBlockId = (
|
||||
issueId: string | undefined,
|
||||
groupId: string | undefined,
|
||||
subGroupId: string | undefined
|
||||
) => `issue_${issueId}_${groupId}_${subGroupId}`;
|
||||
|
||||
/**
|
||||
* returns empty Array if groupId is None
|
||||
* @param groupId
|
||||
* @returns
|
||||
*/
|
||||
const getGroupId = (groupId: string) => {
|
||||
if (groupId === "None") return [];
|
||||
return [groupId];
|
||||
};
|
||||
|
||||
export const handleGroupDragDrop = async (
|
||||
source: GroupDropLocation,
|
||||
destination: GroupDropLocation,
|
||||
getIssueById: (issueId: string) => TIssue | undefined,
|
||||
getIssueIds: (groupId?: string, subGroupId?: string) => string[] | undefined,
|
||||
updateIssueOnDrop: (projectId: string, issueId: string, data: Partial<TIssue>, issueUpdates: IssueUpdates) => void,
|
||||
groupBy: TIssueGroupByOptions | undefined,
|
||||
subGroupBy: TIssueGroupByOptions | undefined,
|
||||
shouldAddIssueAtTop = false
|
||||
) => {
|
||||
if (!source.id || !groupBy || (subGroupBy && (!source.subGroupId || !destination.subGroupId))) return;
|
||||
|
||||
let updatedIssue: Partial<TIssue> = {};
|
||||
const issueUpdates: IssueUpdates = {};
|
||||
const destinationIssues = getIssueIds(destination.groupId, destination.subGroupId) ?? [];
|
||||
|
||||
const sourceIssue = getIssueById(source.id);
|
||||
|
||||
if (!sourceIssue) return;
|
||||
|
||||
updatedIssue = {
|
||||
id: sourceIssue.id,
|
||||
project_id: sourceIssue.project_id,
|
||||
};
|
||||
|
||||
// for both horizontal and vertical dnd
|
||||
updatedIssue = {
|
||||
...updatedIssue,
|
||||
...handleSortOrder(destinationIssues, destination.id, getIssueById, shouldAddIssueAtTop),
|
||||
};
|
||||
|
||||
// update updatedIssue values based on the source and destination groupIds
|
||||
if (source.groupId && destination.groupId && source.groupId !== destination.groupId) {
|
||||
const groupKey = ISSUE_FILTER_DEFAULT_DATA[groupBy];
|
||||
let groupValue = clone(sourceIssue[groupKey]);
|
||||
|
||||
// If groupValues is an array, remove source groupId and add destination groupId
|
||||
if (Array.isArray(groupValue)) {
|
||||
pull(groupValue, source.groupId);
|
||||
if (destination.groupId !== "None") groupValue = uniq(concat(groupValue, [destination.groupId]));
|
||||
} // else just update the groupValue based on destination groupId
|
||||
else {
|
||||
groupValue = destination.groupId === "None" ? null : destination.groupId;
|
||||
}
|
||||
|
||||
// keep track of updates on what was added and what was removed
|
||||
issueUpdates[groupKey] = { ADD: getGroupId(destination.groupId), REMOVE: getGroupId(source.groupId) };
|
||||
updatedIssue = { ...updatedIssue, [groupKey]: groupValue };
|
||||
}
|
||||
|
||||
// do the same for subgroup
|
||||
// update updatedIssue values based on the source and destination subGroupIds
|
||||
if (subGroupBy && source.subGroupId && destination.subGroupId && source.subGroupId !== destination.subGroupId) {
|
||||
const subGroupKey = ISSUE_FILTER_DEFAULT_DATA[subGroupBy];
|
||||
let subGroupValue = clone(sourceIssue[subGroupKey]);
|
||||
|
||||
// If subGroupValue is an array, remove source subGroupId and add destination subGroupId
|
||||
if (Array.isArray(subGroupValue)) {
|
||||
pull(subGroupValue, source.subGroupId);
|
||||
if (destination.subGroupId !== "None") subGroupValue = uniq(concat(subGroupValue, [destination.subGroupId]));
|
||||
} // else just update the subGroupValue based on destination subGroupId
|
||||
else {
|
||||
subGroupValue = destination.subGroupId === "None" ? null : destination.subGroupId;
|
||||
}
|
||||
|
||||
// keep track of updates on what was added and what was removed
|
||||
issueUpdates[subGroupKey] = { ADD: getGroupId(destination.subGroupId), REMOVE: getGroupId(source.subGroupId) };
|
||||
updatedIssue = { ...updatedIssue, [subGroupKey]: subGroupValue };
|
||||
}
|
||||
|
||||
if (updatedIssue) {
|
||||
return await updateIssueOnDrop(sourceIssue.project_id, sourceIssue.id, updatedIssue, issueUpdates);
|
||||
}
|
||||
};
|
||||
|
@ -121,7 +121,7 @@ export const CreateUpdateIssueModal: React.FC<IssuesModalProps> = observer((prop
|
||||
const addIssueToModule = async (issue: TIssue, moduleIds: string[]) => {
|
||||
if (!workspaceSlug || !activeProjectId) return;
|
||||
|
||||
await moduleIssues.addModulesToIssue(workspaceSlug, activeProjectId, issue.id, moduleIds);
|
||||
await moduleIssues.changeModulesInIssue(workspaceSlug, activeProjectId, issue.id, moduleIds, []);
|
||||
moduleIds.forEach((moduleId) => fetchModuleDetails(workspaceSlug, activeProjectId, moduleId));
|
||||
};
|
||||
|
||||
|
@ -60,6 +60,7 @@ export interface ICycleIssues {
|
||||
) => Promise<void>;
|
||||
removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
|
||||
addCycleToIssue: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
|
||||
removeCycleFromIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<void>
|
||||
transferIssuesFromCycle: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
@ -273,7 +274,13 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
|
||||
const response = await this.createIssue(workspaceSlug, projectId, data, cycleId);
|
||||
|
||||
if (data.module_ids && data.module_ids.length > 0)
|
||||
await this.rootStore.moduleIssues.addModulesToIssue(workspaceSlug, projectId, response.id, data.module_ids);
|
||||
await this.rootStore.moduleIssues.changeModulesInIssue(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
response.id,
|
||||
data.module_ids,
|
||||
[]
|
||||
);
|
||||
|
||||
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, cycleId);
|
||||
|
||||
@ -327,6 +334,36 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a cycle from issue
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param issueId
|
||||
* @returns
|
||||
*/
|
||||
removeCycleFromIssue = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
||||
const issueCycleId = this.rootIssueStore.issues.getIssueById(issueId)?.cycle_id;
|
||||
if(!issueCycleId) return;
|
||||
try {
|
||||
// perform optimistic update, update store
|
||||
runInAction(() => {
|
||||
pull(this.issues[issueCycleId], issueId);
|
||||
});
|
||||
this.rootStore.issues.updateIssue(issueId, { cycle_id: null });
|
||||
|
||||
// make API call
|
||||
await this.issueService.removeIssueFromCycle(workspaceSlug, projectId, issueCycleId, issueId);
|
||||
this.rootIssueStore.rootStore.cycle.fetchCycleDetails(workspaceSlug, projectId, issueCycleId);
|
||||
} catch (error) {
|
||||
// revert back changes if fails
|
||||
runInAction(() => {
|
||||
update(this.issues, issueCycleId, (cycleIssueIds = []) => uniq(concat(cycleIssueIds, [issueId])));
|
||||
});
|
||||
this.rootStore.issues.updateIssue(issueId, { cycle_id: issueCycleId });
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
removeIssueFromCycle = async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
|
||||
try {
|
||||
runInAction(() => {
|
||||
|
@ -197,11 +197,12 @@ export class IssueStore implements IIssueStore {
|
||||
};
|
||||
|
||||
addModulesToIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
|
||||
const currentModule = await this.rootIssueDetailStore.rootIssueStore.moduleIssues.addModulesToIssue(
|
||||
const currentModule = await this.rootIssueDetailStore.rootIssueStore.moduleIssues.changeModulesInIssue(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
issueId,
|
||||
moduleIds
|
||||
moduleIds,
|
||||
[]
|
||||
);
|
||||
if (moduleIds && moduleIds.length > 0)
|
||||
await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
|
||||
@ -209,10 +210,11 @@ export class IssueStore implements IIssueStore {
|
||||
};
|
||||
|
||||
removeModulesFromIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
|
||||
const currentModule = await this.rootIssueDetailStore.rootIssueStore.moduleIssues.removeModulesFromIssue(
|
||||
const currentModule = await this.rootIssueDetailStore.rootIssueStore.moduleIssues.changeModulesInIssue(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
issueId,
|
||||
[],
|
||||
moduleIds
|
||||
);
|
||||
await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
|
||||
|
@ -22,6 +22,8 @@ export interface IIssueKanBanViewStore {
|
||||
setIsDragging: (isDragging: boolean) => void;
|
||||
}
|
||||
|
||||
const DRAG_ALLOWED_GROUPS: TIssueGroupByOptions[] = ["state", "priority", "assignees", "labels", "module", "cycle"];
|
||||
|
||||
export class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||
kanBanToggle: {
|
||||
groupByHeaderMinMax: string[];
|
||||
@ -53,9 +55,9 @@ export class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||
|
||||
getCanUserDragDrop = computedFn(
|
||||
(group_by: TIssueGroupByOptions | undefined, sub_group_by: TIssueGroupByOptions | undefined) => {
|
||||
if (group_by && ["state", "priority"].includes(group_by)) {
|
||||
if (group_by && DRAG_ALLOWED_GROUPS.includes(group_by)) {
|
||||
if (!sub_group_by) return true;
|
||||
if (sub_group_by && ["state", "priority"].includes(sub_group_by)) return true;
|
||||
if (sub_group_by && DRAG_ALLOWED_GROUPS.includes(sub_group_by)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import concat from "lodash/concat";
|
||||
import pull from "lodash/pull";
|
||||
import isEmpty from "lodash/isEmpty";
|
||||
import set from "lodash/set";
|
||||
import uniq from "lodash/uniq";
|
||||
import update from "lodash/update";
|
||||
@ -63,12 +64,12 @@ export interface IModuleIssues {
|
||||
moduleId: string,
|
||||
issueIds: string[]
|
||||
) => Promise<void>;
|
||||
addModulesToIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => Promise<void>;
|
||||
removeModulesFromIssue: (
|
||||
changeModulesInIssue: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
moduleIds: string[]
|
||||
addModuleIds: string[],
|
||||
removeModuleIds: string[]
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
@ -104,8 +105,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
|
||||
quickAddIssue: action,
|
||||
addIssuesToModule: action,
|
||||
removeIssuesFromModule: action,
|
||||
addModulesToIssue: action,
|
||||
removeModulesFromIssue: action,
|
||||
changeModulesInIssue: action,
|
||||
});
|
||||
|
||||
this.rootIssueStore = _rootStore;
|
||||
@ -368,67 +368,78 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
|
||||
}
|
||||
};
|
||||
|
||||
addModulesToIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
|
||||
/**
|
||||
* change modules array in issue
|
||||
* @param workspaceSlug
|
||||
* @param projectId
|
||||
* @param issueId
|
||||
* @param addModuleIds array of modules to be added
|
||||
* @param removeModuleIds array of modules to be removed
|
||||
*/
|
||||
changeModulesInIssue = async (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
issueId: string,
|
||||
addModuleIds: string[],
|
||||
removeModuleIds: string[]
|
||||
) => {
|
||||
// keep a copy of the original module ids
|
||||
const originalModuleIds = this.rootStore.issues.issuesMap[issueId]?.module_ids ?? [];
|
||||
const originalModuleIds = this.rootStore.issues.issuesMap[issueId]?.module_ids
|
||||
? [...this.rootStore.issues.issuesMap[issueId].module_ids!]
|
||||
: [];
|
||||
try {
|
||||
runInAction(() => {
|
||||
// add the new issue ids to the module issues map
|
||||
moduleIds.forEach((moduleId) => {
|
||||
// remove the new issue id to the module issues map
|
||||
removeModuleIds.forEach((moduleId) => {
|
||||
update(this.issues, moduleId, (moduleIssueIds = []) => {
|
||||
if (moduleIssueIds.includes(issueId)) return pull(moduleIssueIds, issueId);
|
||||
else return moduleIssueIds;
|
||||
});
|
||||
});
|
||||
// add the new issue id to the module issues map
|
||||
addModuleIds.forEach((moduleId) => {
|
||||
update(this.issues, moduleId, (moduleIssueIds = []) => {
|
||||
if (moduleIssueIds.includes(issueId)) return moduleIssueIds;
|
||||
else return uniq(concat(moduleIssueIds, [issueId]));
|
||||
});
|
||||
});
|
||||
});
|
||||
if(originalModuleIds){
|
||||
// update the root issue map with the new module ids
|
||||
update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
|
||||
uniq(concat(issueModuleIds, moduleIds))
|
||||
);
|
||||
});
|
||||
let currentModuleIds = concat([...originalModuleIds], addModuleIds);
|
||||
currentModuleIds = pull(currentModuleIds, ...removeModuleIds);
|
||||
this.rootStore.issues.updateIssue(issueId, { module_ids: uniq(currentModuleIds) });
|
||||
}
|
||||
|
||||
const issueToModule = await this.moduleService.addModulesToIssue(workspaceSlug, projectId, issueId, {
|
||||
modules: moduleIds,
|
||||
});
|
||||
//Perform API calls
|
||||
if (!isEmpty(addModuleIds)) {
|
||||
await this.moduleService.addModulesToIssue(workspaceSlug, projectId, issueId, {
|
||||
modules: addModuleIds,
|
||||
});
|
||||
}
|
||||
if (!isEmpty(removeModuleIds)) {
|
||||
await this.moduleService.removeModulesFromIssueBulk(workspaceSlug, projectId, issueId, removeModuleIds);
|
||||
}
|
||||
|
||||
return issueToModule;
|
||||
} catch (error) {
|
||||
// revert the issue back to its original module ids
|
||||
set(this.rootStore.issues.issuesMap, [issueId, "module_ids"], originalModuleIds);
|
||||
// remove the new issue ids from the module issues map
|
||||
moduleIds.forEach((moduleId) => {
|
||||
runInAction(() => {
|
||||
update(this.issues, moduleId, (moduleIssueIds = []) => pull(moduleIssueIds, issueId));
|
||||
// add the removed issue id to the module issues map
|
||||
addModuleIds.forEach((moduleId) => {
|
||||
update(this.issues, moduleId, (moduleIssueIds = []) => {
|
||||
if (moduleIssueIds.includes(issueId)) return pull(moduleIssueIds, issueId);
|
||||
else return moduleIssueIds;
|
||||
});
|
||||
});
|
||||
// remove the added issue id to the module issues map
|
||||
removeModuleIds.forEach((moduleId) => {
|
||||
update(this.issues, moduleId, (moduleIssueIds = []) => {
|
||||
if (moduleIssueIds.includes(issueId)) return moduleIssueIds;
|
||||
else return uniq(concat(moduleIssueIds, [issueId]));
|
||||
});
|
||||
});
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
removeModulesFromIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
|
||||
try {
|
||||
runInAction(() => {
|
||||
moduleIds.forEach((moduleId) => {
|
||||
update(this.issues, moduleId, (moduleIssueIds = []) => {
|
||||
if (moduleIssueIds.includes(issueId)) return pull(moduleIssueIds, issueId);
|
||||
else return uniq(concat(moduleIssueIds, [issueId]));
|
||||
});
|
||||
update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
|
||||
pull(issueModuleIds, moduleId)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const response = await this.moduleService.removeModulesFromIssueBulk(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
issueId,
|
||||
moduleIds
|
||||
);
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -243,7 +243,13 @@ export class ProjectViewIssues extends IssueHelperStore implements IProjectViewI
|
||||
await this.rootStore.cycleIssues.addIssueToCycle(workspaceSlug, projectId, data.cycle_id, [response.id]);
|
||||
|
||||
if (data.module_ids && data.module_ids.length > 0)
|
||||
await this.rootStore.moduleIssues.addModulesToIssue(workspaceSlug, projectId, response.id, data.module_ids);
|
||||
await this.rootStore.moduleIssues.changeModulesInIssue(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
response.id,
|
||||
data.module_ids,
|
||||
[]
|
||||
);
|
||||
|
||||
const quickAddIssueIndex = this.issues[viewId].findIndex((_issueId) => _issueId === data.id);
|
||||
if (quickAddIssueIndex >= 0)
|
||||
|
@ -227,7 +227,13 @@ export class ProjectIssues extends IssueHelperStore implements IProjectIssues {
|
||||
await this.rootStore.cycleIssues.addIssueToCycle(workspaceSlug, projectId, data.cycle_id, [response.id]);
|
||||
|
||||
if (data.module_ids && data.module_ids.length > 0)
|
||||
await this.rootStore.moduleIssues.addModulesToIssue(workspaceSlug, projectId, response.id, data.module_ids);
|
||||
await this.rootStore.moduleIssues.changeModulesInIssue(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
response.id,
|
||||
data.module_ids,
|
||||
[]
|
||||
);
|
||||
|
||||
const quickAddIssueIndex = this.issues[projectId].findIndex((_issueId) => _issueId === data.id);
|
||||
if (quickAddIssueIndex >= 0)
|
||||
|
@ -43,15 +43,28 @@
|
||||
--color-primary-800: 19, 35, 76;
|
||||
--color-primary-900: 13, 24, 51;
|
||||
|
||||
--color-error-10: 255 252 252;
|
||||
--color-error-20: 255 247 247;
|
||||
--color-error-30: 255, 219, 220;
|
||||
--color-error-100: 244 170 170;
|
||||
--color-error-200: 220 62 62;
|
||||
--color-error-500: 140 51 58;
|
||||
|
||||
--color-background-100: 255, 255, 255; /* primary bg */
|
||||
--color-background-90: 247, 247, 247; /* secondary bg */
|
||||
--color-background-80: 232, 232, 232; /* tertiary bg */
|
||||
|
||||
--color-background-primary: var(--color-primary-10);
|
||||
--color-background-error: var(--color-error-20);
|
||||
|
||||
--color-text-100: 23, 23, 23; /* primary text */
|
||||
--color-text-200: 58, 58, 58; /* secondary text */
|
||||
--color-text-300: 82, 82, 82; /* tertiary text */
|
||||
--color-text-400: 163, 163, 163; /* placeholder text */
|
||||
|
||||
--color-text-primary: var(--color-primary-100);
|
||||
--color-text-error: var(--color-error-200);
|
||||
|
||||
--color-scrollbar: 163, 163, 163; /* scrollbar thumb */
|
||||
|
||||
--color-border-100: 245, 245, 245; /* subtle border= 1 */
|
||||
@ -59,6 +72,9 @@
|
||||
--color-border-300: 212, 212, 212; /* strong border- 1 */
|
||||
--color-border-400: 185, 185, 185; /* strong border- 2 */
|
||||
|
||||
--color-border-primary: var(--color-primary-40);
|
||||
--color-border-error: var(--color-error-100);
|
||||
|
||||
--color-shadow-2xs: 0px 0px 1px 0px rgba(23, 23, 23, 0.06), 0px 1px 2px 0px rgba(23, 23, 23, 0.06),
|
||||
0px 1px 2px 0px rgba(23, 23, 23, 0.14);
|
||||
--color-shadow-xs: 0px 1px 2px 0px rgba(0, 0, 0, 0.16), 0px 2px 4px 0px rgba(16, 24, 40, 0.12),
|
||||
@ -151,7 +167,7 @@
|
||||
|
||||
/* toast theme */
|
||||
--color-toast-success-text: 62, 155, 79;
|
||||
--color-toast-error-text: 220, 62, 66;
|
||||
--color-toast-error-text: var(--color-error-200);
|
||||
--color-toast-warning-text: 255, 186, 24;
|
||||
--color-toast-info-text: 51, 88, 212;
|
||||
--color-toast-loading-text: 28, 32, 36;
|
||||
@ -159,13 +175,13 @@
|
||||
--color-toast-tertiary-text: 96, 100, 108;
|
||||
|
||||
--color-toast-success-background: 253, 253, 254;
|
||||
--color-toast-error-background: 255, 252, 252;
|
||||
--color-toast-error-background: var(--color-error-10);
|
||||
--color-toast-warning-background: 254, 253, 251;
|
||||
--color-toast-info-background: 253, 253, 254;
|
||||
--color-toast-loading-background: 253, 253, 254;
|
||||
|
||||
--color-toast-success-border: 218, 241, 219;
|
||||
--color-toast-error-border: 255, 219, 220;
|
||||
--color-toast-error-border: var(--color-error-30);
|
||||
--color-toast-warning-border: 255, 247, 194;
|
||||
--color-toast-info-border: 210, 222, 255;
|
||||
--color-toast-loading-border: 224, 225, 230;
|
||||
@ -193,6 +209,15 @@
|
||||
--color-background-90: 32, 32, 32; /* secondary bg */
|
||||
--color-background-80: 44, 44, 44; /* tertiary bg */
|
||||
|
||||
--color-background-primary: var(--color-background-90);
|
||||
--color-background-error: var(--color-background-90);
|
||||
|
||||
--color-text-primary: var(--color-primary-40);
|
||||
--color-text-error: var(--color-error-100);
|
||||
|
||||
--color-border-primary: var(--color-primary-200);
|
||||
--color-border-error: var(--color-error-500);
|
||||
|
||||
--color-shadow-2xs: 0px 0px 1px 0px rgba(0, 0, 0, 0.15), 0px 1px 3px 0px rgba(0, 0, 0, 0.5);
|
||||
--color-shadow-xs: 0px 0px 2px 0px rgba(0, 0, 0, 0.2), 0px 2px 4px 0px rgba(0, 0, 0, 0.5);
|
||||
--color-shadow-sm: 0px 0px 4px 0px rgba(0, 0, 0, 0.2), 0px 2px 6px 0px rgba(0, 0, 0, 0.5);
|
||||
|
Loading…
Reference in New Issue
Block a user