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"),
|
800: convertToRGB("--color-background-800"),
|
||||||
900: convertToRGB("--color-background-900"),
|
900: convertToRGB("--color-background-900"),
|
||||||
1000: "rgb(0, 0, 0)",
|
1000: "rgb(0, 0, 0)",
|
||||||
|
primary: convertToRGB(" --color-background-primary"),
|
||||||
|
error: convertToRGB(" --color-background-error"),
|
||||||
DEFAULT: convertToRGB("--color-background-100"),
|
DEFAULT: convertToRGB("--color-background-100"),
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
@ -110,6 +112,8 @@ module.exports = {
|
|||||||
800: convertToRGB("--color-text-800"),
|
800: convertToRGB("--color-text-800"),
|
||||||
900: convertToRGB("--color-text-900"),
|
900: convertToRGB("--color-text-900"),
|
||||||
1000: "rgb(0, 0, 0)",
|
1000: "rgb(0, 0, 0)",
|
||||||
|
primary: convertToRGB("--color-text-primary"),
|
||||||
|
error: convertToRGB("--color-text-error"),
|
||||||
DEFAULT: convertToRGB("--color-text-100"),
|
DEFAULT: convertToRGB("--color-text-100"),
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
@ -119,8 +123,18 @@ module.exports = {
|
|||||||
300: convertToRGB("--color-border-300"),
|
300: convertToRGB("--color-border-300"),
|
||||||
400: convertToRGB("--color-border-400"),
|
400: convertToRGB("--color-border-400"),
|
||||||
1000: "rgb(0, 0, 0)",
|
1000: "rgb(0, 0, 0)",
|
||||||
|
primary: convertToRGB("--color-border-primary"),
|
||||||
|
error: convertToRGB("--color-border-error"),
|
||||||
DEFAULT: convertToRGB("--color-border-200"),
|
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: {
|
sidebar: {
|
||||||
background: {
|
background: {
|
||||||
0: "rgb(255, 255, 255)",
|
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;
|
name: string;
|
||||||
icon: ReactElement | undefined;
|
icon: ReactElement | undefined;
|
||||||
payload: Partial<TIssue>;
|
payload: Partial<TIssue>;
|
||||||
|
isDropDisabled?: boolean;
|
||||||
|
dropErrorMessage?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIssueMap {
|
export interface IIssueMap {
|
||||||
|
@ -12,7 +12,6 @@ type Props = {
|
|||||||
alwaysRender?: boolean;
|
alwaysRender?: boolean;
|
||||||
placeholderChildren?: ReactNode;
|
placeholderChildren?: ReactNode;
|
||||||
pauseHeightUpdateWhileRendering?: boolean;
|
pauseHeightUpdateWhileRendering?: boolean;
|
||||||
changingReference?: any;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const RenderIfVisible: React.FC<Props> = (props) => {
|
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
|
alwaysRender = false, //render the children even if it is not visible in root
|
||||||
placeholderChildren = null, //placeholder children
|
placeholderChildren = null, //placeholder children
|
||||||
pauseHeightUpdateWhileRendering = false, //while this is true the height of the blocks are maintained
|
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;
|
} = props;
|
||||||
const [shouldVisible, setShouldVisible] = useState<boolean>(alwaysRender);
|
const [shouldVisible, setShouldVisible] = useState<boolean>(alwaysRender);
|
||||||
const placeholderHeight = useRef<string>(defaultHeight);
|
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
|
//Set height after render
|
||||||
useEffect(() => {
|
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 { autoScrollForElements } from "@atlaskit/pragmatic-drag-and-drop-auto-scroll/element";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
// hooks
|
import { TIssue } from "@plane/types";
|
||||||
import { Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
import { Spinner, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
import { DeleteIssueModal } from "@/components/issues";
|
import { DeleteIssueModal } from "@/components/issues";
|
||||||
import { ISSUE_DELETED } from "@/constants/event-tracker";
|
import { ISSUE_DELETED } from "@/constants/event-tracker";
|
||||||
import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue";
|
import { EIssueFilterType, EIssuesStoreType } from "@/constants/issue";
|
||||||
import { EUserProjectRoles } from "@/constants/project";
|
import { EUserProjectRoles } from "@/constants/project";
|
||||||
|
// hooks
|
||||||
import { useEventTracker, useIssueDetail, useIssues, useKanbanView, useUser } from "@/hooks/store";
|
import { useEventTracker, useIssueDetail, useIssues, useKanbanView, useUser } from "@/hooks/store";
|
||||||
import { useIssuesActions } from "@/hooks/use-issues-actions";
|
import { useIssuesActions } from "@/hooks/use-issues-actions";
|
||||||
|
// store
|
||||||
|
import { ISSUE_FILTER_DEFAULT_DATA } from "@/store/issue/helpers/issue-helper.store";
|
||||||
// ui
|
// ui
|
||||||
// types
|
// types
|
||||||
import { IQuickActionProps, TRenderQuickActions } from "../list/list-view-types";
|
import { IQuickActionProps, TRenderQuickActions } from "../list/list-view-types";
|
||||||
//components
|
//components
|
||||||
|
import { GroupDropLocation, handleGroupDragDrop, getSourceFromDropPayload } from "../utils";
|
||||||
import { KanBan } from "./default";
|
import { KanBan } from "./default";
|
||||||
import { KanBanSwimLanes } from "./swimlanes";
|
import { KanBanSwimLanes } from "./swimlanes";
|
||||||
import { KanbanDropLocation, handleDragDrop, getSourceFromDropPayload } from "./utils";
|
|
||||||
|
|
||||||
export type KanbanStoreType =
|
export type KanbanStoreType =
|
||||||
| EIssuesStoreType.PROJECT
|
| EIssuesStoreType.PROJECT
|
||||||
@ -61,6 +65,12 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||||||
} = useIssueDetail();
|
} = useIssueDetail();
|
||||||
const { updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue, updateFilters } =
|
const { updateIssue, removeIssue, removeIssueFromView, archiveIssue, restoreIssue, updateFilters } =
|
||||||
useIssuesActions(storeType);
|
useIssuesActions(storeType);
|
||||||
|
const {
|
||||||
|
issues: { addCycleToIssue, removeCycleFromIssue },
|
||||||
|
} = useIssues(EIssuesStoreType.CYCLE);
|
||||||
|
const {
|
||||||
|
issues: { changeModulesInIssue },
|
||||||
|
} = useIssues(EIssuesStoreType.MODULE);
|
||||||
|
|
||||||
const deleteAreaRef = useRef<HTMLDivElement | null>(null);
|
const deleteAreaRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [isDragOverDelete, setIsDragOverDelete] = useState(false);
|
const [isDragOverDelete, setIsDragOverDelete] = useState(false);
|
||||||
@ -143,7 +153,60 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||||||
);
|
);
|
||||||
}, [deleteAreaRef?.current, setIsDragOverDelete, setDraggedIssueId, setDeleteIssueModal]);
|
}, [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 (
|
if (
|
||||||
source.columnId &&
|
source.columnId &&
|
||||||
destination.columnId &&
|
destination.columnId &&
|
||||||
@ -152,12 +215,12 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await handleDragDrop(
|
await handleGroupDragDrop(
|
||||||
source,
|
source,
|
||||||
destination,
|
destination,
|
||||||
getIssueById,
|
getIssueById,
|
||||||
issues.getIssueIds,
|
issues.getIssueIds,
|
||||||
updateIssue,
|
updateIssueOnDrop,
|
||||||
group_by,
|
group_by,
|
||||||
sub_group_by,
|
sub_group_by,
|
||||||
orderBy !== "sort_order"
|
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) => {
|
const handleKanbanFilters = (toggle: "group_by" | "sub_group_by", value: string) => {
|
||||||
if (workspaceSlug && projectId) {
|
if (workspaceSlug && projectId) {
|
||||||
let kanbanFilters = issuesFilter?.issueFilters?.kanbanFilters?.[toggle] || [];
|
let kanbanFilters = issuesFilter?.issueFilters?.kanbanFilters?.[toggle] || [];
|
||||||
if (kanbanFilters.includes(value)) kanbanFilters = kanbanFilters.filter((_value) => _value != value);
|
if (kanbanFilters.includes(value)) {
|
||||||
else kanbanFilters.push(value);
|
kanbanFilters = kanbanFilters.filter((_value) => _value != value);
|
||||||
|
} else {
|
||||||
|
kanbanFilters.push(value);
|
||||||
|
}
|
||||||
updateFilters(projectId.toString(), EIssueFilterType.KANBAN_FILTERS, {
|
updateFilters(projectId.toString(), EIssueFilterType.KANBAN_FILTERS, {
|
||||||
[toggle]: kanbanFilters,
|
[toggle]: kanbanFilters,
|
||||||
});
|
});
|
||||||
|
@ -16,12 +16,15 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
|
|||||||
import { TRenderQuickActions } from "../list/list-view-types";
|
import { TRenderQuickActions } from "../list/list-view-types";
|
||||||
import { IssueProperties } from "../properties/all-properties";
|
import { IssueProperties } from "../properties/all-properties";
|
||||||
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
|
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
|
||||||
|
import { getIssueBlockId } from "../utils";
|
||||||
// ui
|
// ui
|
||||||
// types
|
// types
|
||||||
// helper
|
// helper
|
||||||
|
|
||||||
interface IssueBlockProps {
|
interface IssueBlockProps {
|
||||||
issueId: string;
|
issueId: string;
|
||||||
|
groupId: string;
|
||||||
|
subGroupId: string;
|
||||||
issuesMap: IIssueMap;
|
issuesMap: IIssueMap;
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
isDragDisabled: boolean;
|
isDragDisabled: boolean;
|
||||||
@ -30,7 +33,6 @@ interface IssueBlockProps {
|
|||||||
quickActions: TRenderQuickActions;
|
quickActions: TRenderQuickActions;
|
||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||||
issueIds: string[]; //DO NOT REMOVE< needed to force render for virtualization
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IssueDetailsBlockProps {
|
interface IssueDetailsBlockProps {
|
||||||
@ -99,6 +101,8 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
|
|||||||
export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
||||||
const {
|
const {
|
||||||
issueId,
|
issueId,
|
||||||
|
groupId,
|
||||||
|
subGroupId,
|
||||||
issuesMap,
|
issuesMap,
|
||||||
displayProperties,
|
displayProperties,
|
||||||
isDragDisabled,
|
isDragDisabled,
|
||||||
@ -106,7 +110,6 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
|||||||
quickActions,
|
quickActions,
|
||||||
canEditProperties,
|
canEditProperties,
|
||||||
scrollableContainerRef,
|
scrollableContainerRef,
|
||||||
issueIds,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const cardRef = useRef<HTMLAnchorElement | null>(null);
|
const cardRef = useRef<HTMLAnchorElement | null>(null);
|
||||||
@ -194,7 +197,7 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ControlLink
|
<ControlLink
|
||||||
id={`issue-${issue.id}`}
|
id={getIssueBlockId(issueId, groupId, subGroupId)}
|
||||||
href={`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}issues/${
|
href={`/${workspaceSlug}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}issues/${
|
||||||
issue.id
|
issue.id
|
||||||
}`}
|
}`}
|
||||||
@ -214,7 +217,6 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = observer((props) => {
|
|||||||
root={scrollableContainerRef}
|
root={scrollableContainerRef}
|
||||||
defaultHeight="100px"
|
defaultHeight="100px"
|
||||||
horizontalOffset={50}
|
horizontalOffset={50}
|
||||||
changingReference={issueIds}
|
|
||||||
>
|
>
|
||||||
<KanbanIssueDetailsBlock
|
<KanbanIssueDetailsBlock
|
||||||
cardRef={cardRef}
|
cardRef={cardRef}
|
||||||
|
@ -7,7 +7,7 @@ import { TRenderQuickActions } from "../list/list-view-types";
|
|||||||
|
|
||||||
interface IssueBlocksListProps {
|
interface IssueBlocksListProps {
|
||||||
sub_group_id: string;
|
sub_group_id: string;
|
||||||
columnId: string;
|
groupId: string;
|
||||||
issuesMap: IIssueMap;
|
issuesMap: IIssueMap;
|
||||||
issueIds: string[];
|
issueIds: string[];
|
||||||
displayProperties: IIssueDisplayProperties | undefined;
|
displayProperties: IIssueDisplayProperties | undefined;
|
||||||
@ -21,7 +21,7 @@ interface IssueBlocksListProps {
|
|||||||
const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
|
const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
sub_group_id,
|
sub_group_id,
|
||||||
columnId,
|
groupId,
|
||||||
issuesMap,
|
issuesMap,
|
||||||
issueIds,
|
issueIds,
|
||||||
displayProperties,
|
displayProperties,
|
||||||
@ -40,13 +40,15 @@ const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
|
|||||||
if (!issueId) return null;
|
if (!issueId) return null;
|
||||||
|
|
||||||
let draggableId = issueId;
|
let draggableId = issueId;
|
||||||
if (columnId) draggableId = `${draggableId}__${columnId}`;
|
if (groupId) draggableId = `${draggableId}__${groupId}`;
|
||||||
if (sub_group_id) draggableId = `${draggableId}__${sub_group_id}`;
|
if (sub_group_id) draggableId = `${draggableId}__${sub_group_id}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KanbanIssueBlock
|
<KanbanIssueBlock
|
||||||
key={draggableId}
|
key={draggableId}
|
||||||
issueId={issueId}
|
issueId={issueId}
|
||||||
|
groupId={groupId}
|
||||||
|
subGroupId={sub_group_id}
|
||||||
issuesMap={issuesMap}
|
issuesMap={issuesMap}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
updateIssue={updateIssue}
|
updateIssue={updateIssue}
|
||||||
@ -55,7 +57,6 @@ const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
|
|||||||
isDragDisabled={isDragDisabled}
|
isDragDisabled={isDragDisabled}
|
||||||
canEditProperties={canEditProperties}
|
canEditProperties={canEditProperties}
|
||||||
scrollableContainerRef={scrollableContainerRef}
|
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
|
// types
|
||||||
// parent components
|
// parent components
|
||||||
import { TRenderQuickActions } from "../list/list-view-types";
|
import { TRenderQuickActions } from "../list/list-view-types";
|
||||||
import { getGroupByColumns, isWorkspaceLevel } from "../utils";
|
import { getGroupByColumns, isWorkspaceLevel, GroupDropLocation } from "../utils";
|
||||||
// components
|
// components
|
||||||
import { KanbanStoreType } from "./base-kanban-root";
|
import { KanbanStoreType } from "./base-kanban-root";
|
||||||
import { HeaderGroupByCard } from "./headers/group-by-card";
|
import { HeaderGroupByCard } from "./headers/group-by-card";
|
||||||
import { KanbanGroup } from "./kanban-group";
|
import { KanbanGroup } from "./kanban-group";
|
||||||
import { KanbanDropLocation } from "./utils";
|
|
||||||
|
|
||||||
export interface IGroupByKanBan {
|
export interface IGroupByKanBan {
|
||||||
issuesMap: IIssueMap;
|
issuesMap: IIssueMap;
|
||||||
@ -52,7 +51,7 @@ export interface IGroupByKanBan {
|
|||||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||||
handleOnDrop: (source: KanbanDropLocation, destination: KanbanDropLocation) => Promise<void>;
|
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||||
showEmptyGroup?: boolean;
|
showEmptyGroup?: boolean;
|
||||||
subGroupIssueHeaderCount?: (listId: string) => number;
|
subGroupIssueHeaderCount?: (listId: string) => number;
|
||||||
}
|
}
|
||||||
@ -176,6 +175,8 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||||||
orderBy={orderBy}
|
orderBy={orderBy}
|
||||||
sub_group_id={sub_group_id}
|
sub_group_id={sub_group_id}
|
||||||
isDragDisabled={isDragDisabled}
|
isDragDisabled={isDragDisabled}
|
||||||
|
isDropDisabled={!!subList.isDropDisabled}
|
||||||
|
dropErrorMessage={subList.dropErrorMessage}
|
||||||
updateIssue={updateIssue}
|
updateIssue={updateIssue}
|
||||||
quickActions={quickActions}
|
quickActions={quickActions}
|
||||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||||
@ -220,7 +221,7 @@ export interface IKanBan {
|
|||||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||||
handleOnDrop: (source: KanbanDropLocation, destination: KanbanDropLocation) => Promise<void>;
|
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||||
subGroupIssueHeaderCount?: (listId: string) => number;
|
subGroupIssueHeaderCount?: (listId: string) => number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import { cn } from "@/helpers/common.helper";
|
|||||||
import { useProjectState } from "@/hooks/store";
|
import { useProjectState } from "@/hooks/store";
|
||||||
//components
|
//components
|
||||||
import { TRenderQuickActions } from "../list/list-view-types";
|
import { TRenderQuickActions } from "../list/list-view-types";
|
||||||
import { KanbanDropLocation, getSourceFromDropPayload, getDestinationFromDropPayload } from "./utils";
|
import { GroupDropLocation, getSourceFromDropPayload, getDestinationFromDropPayload, getIssueBlockId } from "../utils";
|
||||||
import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from ".";
|
import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from ".";
|
||||||
|
|
||||||
interface IKanbanGroup {
|
interface IKanbanGroup {
|
||||||
@ -33,6 +33,8 @@ interface IKanbanGroup {
|
|||||||
group_by: TIssueGroupByOptions | undefined;
|
group_by: TIssueGroupByOptions | undefined;
|
||||||
sub_group_id: string;
|
sub_group_id: string;
|
||||||
isDragDisabled: boolean;
|
isDragDisabled: boolean;
|
||||||
|
isDropDisabled: boolean;
|
||||||
|
dropErrorMessage: string | undefined;
|
||||||
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
updateIssue: ((projectId: string, issueId: string, data: Partial<TIssue>) => Promise<void>) | undefined;
|
||||||
quickActions: TRenderQuickActions;
|
quickActions: TRenderQuickActions;
|
||||||
enableQuickIssueCreate?: boolean;
|
enableQuickIssueCreate?: boolean;
|
||||||
@ -47,7 +49,7 @@ interface IKanbanGroup {
|
|||||||
canEditProperties: (projectId: string | undefined) => boolean;
|
canEditProperties: (projectId: string | undefined) => boolean;
|
||||||
groupByVisibilityToggle?: boolean;
|
groupByVisibilityToggle?: boolean;
|
||||||
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
scrollableContainerRef?: MutableRefObject<HTMLDivElement | null>;
|
||||||
handleOnDrop: (source: KanbanDropLocation, destination: KanbanDropLocation) => Promise<void>;
|
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||||
orderBy: TIssueOrderByOptions | undefined;
|
orderBy: TIssueOrderByOptions | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,6 +64,8 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
|||||||
displayProperties,
|
displayProperties,
|
||||||
issueIds,
|
issueIds,
|
||||||
isDragDisabled,
|
isDragDisabled,
|
||||||
|
isDropDisabled,
|
||||||
|
dropErrorMessage,
|
||||||
updateIssue,
|
updateIssue,
|
||||||
quickActions,
|
quickActions,
|
||||||
canEditProperties,
|
canEditProperties,
|
||||||
@ -103,18 +107,21 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
|||||||
const source = getSourceFromDropPayload(payload);
|
const source = getSourceFromDropPayload(payload);
|
||||||
const destination = getDestinationFromDropPayload(payload);
|
const destination = getDestinationFromDropPayload(payload);
|
||||||
|
|
||||||
if (!source || !destination) return;
|
if (!source || !destination || isDropDisabled) return;
|
||||||
|
|
||||||
handleOnDrop(source, destination);
|
handleOnDrop(source, destination);
|
||||||
|
|
||||||
highlightIssueOnDrop(payload.source.element.id, orderBy !== "sort_order");
|
highlightIssueOnDrop(
|
||||||
|
getIssueBlockId(source.id, destination?.groupId, destination?.subGroupId),
|
||||||
|
orderBy !== "sort_order"
|
||||||
|
);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
autoScrollForElements({
|
autoScrollForElements({
|
||||||
element,
|
element,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}, [columnRef?.current, groupId, sub_group_id, setIsDraggingOverColumn, orderBy]);
|
}, [columnRef?.current, groupId, sub_group_id, setIsDraggingOverColumn, orderBy, isDropDisabled, handleOnDrop]);
|
||||||
|
|
||||||
const prePopulateQuickAddData = (
|
const prePopulateQuickAddData = (
|
||||||
groupByKey: string | undefined,
|
groupByKey: string | undefined,
|
||||||
@ -168,7 +175,7 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
|||||||
return preloadedData;
|
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;
|
const readableOrderBy = ISSUE_ORDER_BY_OPTIONS.find((orderByObj) => orderByObj.key === orderBy)?.title;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -184,20 +191,38 @@ export const KanbanGroup = (props: IKanbanGroup) => {
|
|||||||
<div
|
<div
|
||||||
//column overlay when issues are not sorted by manual
|
//column overlay when issues are not sorted by manual
|
||||||
className={cn(
|
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 },
|
{ hidden: !shouldOverlay },
|
||||||
{ "justify-center": !sub_group_by }
|
{ "justify-center": !sub_group_by }
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{readableOrderBy && <span className="pt-6">The layout is ordered by {readableOrderBy}.</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>
|
<span>Drop here to move the issue.</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<KanbanIssueBlocksList
|
<KanbanIssueBlocksList
|
||||||
sub_group_id={sub_group_id}
|
sub_group_id={sub_group_id}
|
||||||
columnId={groupId}
|
groupId={groupId}
|
||||||
issuesMap={issuesMap}
|
issuesMap={issuesMap}
|
||||||
issueIds={(issueIds as TGroupedIssues)?.[groupId] || []}
|
issueIds={(issueIds as TGroupedIssues)?.[groupId] || []}
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
|
@ -16,12 +16,11 @@ import {
|
|||||||
// components
|
// components
|
||||||
import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "@/hooks/store";
|
import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "@/hooks/store";
|
||||||
import { TRenderQuickActions } from "../list/list-view-types";
|
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 { KanbanStoreType } from "./base-kanban-root";
|
||||||
import { KanBan } from "./default";
|
import { KanBan } from "./default";
|
||||||
import { HeaderGroupByCard } from "./headers/group-by-card";
|
import { HeaderGroupByCard } from "./headers/group-by-card";
|
||||||
import { HeaderSubGroupByCard } from "./headers/sub-group-by-card";
|
import { HeaderSubGroupByCard } from "./headers/sub-group-by-card";
|
||||||
import { KanbanDropLocation } from "./utils";
|
|
||||||
// types
|
// types
|
||||||
// constants
|
// constants
|
||||||
|
|
||||||
@ -111,7 +110,7 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
|||||||
quickActions: TRenderQuickActions;
|
quickActions: TRenderQuickActions;
|
||||||
kanbanFilters: TIssueKanbanFilters;
|
kanbanFilters: TIssueKanbanFilters;
|
||||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
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;
|
disableIssueCreation?: boolean;
|
||||||
storeType: KanbanStoreType;
|
storeType: KanbanStoreType;
|
||||||
enableQuickIssueCreate: boolean;
|
enableQuickIssueCreate: boolean;
|
||||||
@ -244,7 +243,7 @@ export interface IKanBanSwimLanes {
|
|||||||
kanbanFilters: TIssueKanbanFilters;
|
kanbanFilters: TIssueKanbanFilters;
|
||||||
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
handleKanbanFilters: (toggle: "group_by" | "sub_group_by", value: string) => void;
|
||||||
showEmptyGroup: boolean;
|
showEmptyGroup: boolean;
|
||||||
handleOnDrop: (source: KanbanDropLocation, destination: KanbanDropLocation) => Promise<void>;
|
handleOnDrop: (source: GroupDropLocation, destination: GroupDropLocation) => Promise<void>;
|
||||||
disableIssueCreation?: boolean;
|
disableIssueCreation?: boolean;
|
||||||
storeType: KanbanStoreType;
|
storeType: KanbanStoreType;
|
||||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
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"
|
defaultHeight="3rem"
|
||||||
root={containerRef}
|
root={containerRef}
|
||||||
classNames="relative border-b border-b-custom-border-200 last:border-b-transparent"
|
classNames="relative border-b border-b-custom-border-200 last:border-b-transparent"
|
||||||
changingReference={issueIds}
|
|
||||||
>
|
>
|
||||||
<IssueBlock
|
<IssueBlock
|
||||||
issueId={issueId}
|
issueId={issueId}
|
||||||
|
@ -49,10 +49,10 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
const { labelMap } = useLabel();
|
const { labelMap } = useLabel();
|
||||||
const { captureIssueEvent } = useEventTracker();
|
const { captureIssueEvent } = useEventTracker();
|
||||||
const {
|
const {
|
||||||
issues: { addModulesToIssue, removeModulesFromIssue },
|
issues: { changeModulesInIssue },
|
||||||
} = useIssues(EIssuesStoreType.MODULE);
|
} = useIssues(EIssuesStoreType.MODULE);
|
||||||
const {
|
const {
|
||||||
issues: { addIssueToCycle, removeIssueFromCycle },
|
issues: { addCycleToIssue, removeCycleFromIssue },
|
||||||
} = useIssues(EIssuesStoreType.CYCLE);
|
} = useIssues(EIssuesStoreType.CYCLE);
|
||||||
const { areEstimatesEnabledForCurrentProject } = useEstimate();
|
const { areEstimatesEnabledForCurrentProject } = useEstimate();
|
||||||
const { getStateById } = useProjectState();
|
const { getStateById } = useProjectState();
|
||||||
@ -69,22 +69,22 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
() => ({
|
() => ({
|
||||||
addModulesToIssue: async (moduleIds: string[]) => {
|
addModulesToIssue: async (moduleIds: string[]) => {
|
||||||
if (!workspaceSlug || !issue.project_id || !issue.id) return;
|
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[]) => {
|
removeModulesFromIssue: async (moduleIds: string[]) => {
|
||||||
if (!workspaceSlug || !issue.project_id || !issue.id) return;
|
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) => {
|
addIssueToCycle: async (cycleId: string) => {
|
||||||
if (!workspaceSlug || !issue.project_id || !issue.id) return;
|
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;
|
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) => {
|
const handleState = (stateId: string) => {
|
||||||
@ -174,7 +174,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
(cycleId: string | null) => {
|
(cycleId: string | null) => {
|
||||||
if (!issue || issue.cycle_id === cycleId) return;
|
if (!issue || issue.cycle_id === cycleId) return;
|
||||||
if (cycleId) issueOperations.addIssueToCycle?.(cycleId);
|
if (cycleId) issueOperations.addIssueToCycle?.(cycleId);
|
||||||
else issueOperations.removeIssueFromCycle?.(issue.cycle_id ?? "");
|
else issueOperations.removeIssueFromCycle?.();
|
||||||
|
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: ISSUE_UPDATED,
|
eventName: ISSUE_UPDATED,
|
||||||
|
@ -25,14 +25,14 @@ export const SpreadsheetCycleColumn: React.FC<Props> = observer((props) => {
|
|||||||
// hooks
|
// hooks
|
||||||
const { captureIssueEvent } = useEventTracker();
|
const { captureIssueEvent } = useEventTracker();
|
||||||
const {
|
const {
|
||||||
issues: { addIssueToCycle, removeIssueFromCycle },
|
issues: { addCycleToIssue, removeCycleFromIssue },
|
||||||
} = useIssues(EIssuesStoreType.CYCLE);
|
} = useIssues(EIssuesStoreType.CYCLE);
|
||||||
|
|
||||||
const handleCycle = useCallback(
|
const handleCycle = useCallback(
|
||||||
async (cycleId: string | null) => {
|
async (cycleId: string | null) => {
|
||||||
if (!workspaceSlug || !issue || issue.cycle_id === cycleId) return;
|
if (!workspaceSlug || !issue || issue.cycle_id === cycleId) return;
|
||||||
if (cycleId) await addIssueToCycle(workspaceSlug.toString(), issue.project_id, cycleId, [issue.id]);
|
if (cycleId) await addCycleToIssue(workspaceSlug.toString(), issue.project_id, cycleId, issue.id);
|
||||||
else await removeIssueFromCycle(workspaceSlug.toString(), issue.project_id, issue.cycle_id ?? "", issue.id);
|
else await removeCycleFromIssue(workspaceSlug.toString(), issue.project_id, issue.id);
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: "Issue updated",
|
||||||
payload: {
|
payload: {
|
||||||
@ -44,7 +44,7 @@ export const SpreadsheetCycleColumn: React.FC<Props> = observer((props) => {
|
|||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[workspaceSlug, issue, addIssueToCycle, removeIssueFromCycle, captureIssueEvent, router.asPath]
|
[workspaceSlug, issue, addCycleToIssue, removeCycleFromIssue, captureIssueEvent, router.asPath]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -26,7 +26,7 @@ export const SpreadsheetModuleColumn: React.FC<Props> = observer((props) => {
|
|||||||
// hooks
|
// hooks
|
||||||
const { captureIssueEvent } = useEventTracker();
|
const { captureIssueEvent } = useEventTracker();
|
||||||
const {
|
const {
|
||||||
issues: { addModulesToIssue, removeModulesFromIssue },
|
issues: { changeModulesInIssue },
|
||||||
} = useIssues(EIssuesStoreType.MODULE);
|
} = useIssues(EIssuesStoreType.MODULE);
|
||||||
|
|
||||||
const handleModule = useCallback(
|
const handleModule = useCallback(
|
||||||
@ -36,13 +36,11 @@ export const SpreadsheetModuleColumn: React.FC<Props> = observer((props) => {
|
|||||||
const updatedModuleIds = xor(issue.module_ids, moduleIds);
|
const updatedModuleIds = xor(issue.module_ids, moduleIds);
|
||||||
const modulesToAdd: string[] = [];
|
const modulesToAdd: string[] = [];
|
||||||
const modulesToRemove: string[] = [];
|
const modulesToRemove: string[] = [];
|
||||||
for (const moduleId of updatedModuleIds)
|
for (const moduleId of updatedModuleIds) {
|
||||||
if (issue.module_ids.includes(moduleId)) modulesToRemove.push(moduleId);
|
if (issue.module_ids.includes(moduleId)) modulesToRemove.push(moduleId);
|
||||||
else modulesToAdd.push(moduleId);
|
else modulesToAdd.push(moduleId);
|
||||||
if (modulesToAdd.length > 0)
|
}
|
||||||
addModulesToIssue(workspaceSlug.toString(), issue.project_id, issue.id, modulesToAdd);
|
changeModulesInIssue(workspaceSlug.toString(), issue.project_id, issue.id, modulesToAdd, modulesToRemove);
|
||||||
if (modulesToRemove.length > 0)
|
|
||||||
removeModulesFromIssue(workspaceSlug.toString(), issue.project_id, issue.id, modulesToRemove);
|
|
||||||
|
|
||||||
captureIssueEvent({
|
captureIssueEvent({
|
||||||
eventName: "Issue updated",
|
eventName: "Issue updated",
|
||||||
@ -55,7 +53,7 @@ export const SpreadsheetModuleColumn: React.FC<Props> = observer((props) => {
|
|||||||
path: router.asPath,
|
path: router.asPath,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[workspaceSlug, issue, addModulesToIssue, removeModulesFromIssue, captureIssueEvent, router.asPath]
|
[workspaceSlug, issue, changeModulesInIssue, captureIssueEvent, router.asPath]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -66,7 +66,6 @@ export const SpreadsheetIssueRow = observer((props: Props) => {
|
|||||||
defaultHeight="calc(2.75rem - 1px)"
|
defaultHeight="calc(2.75rem - 1px)"
|
||||||
root={containerRef}
|
root={containerRef}
|
||||||
placeholderChildren={<td colSpan={100} className="border-b-[0.5px] border-custom-border-200" />}
|
placeholderChildren={<td colSpan={100} className="border-b-[0.5px] border-custom-border-200" />}
|
||||||
changingReference={issueIds}
|
|
||||||
>
|
>
|
||||||
<IssueRowDetails
|
<IssueRowDetails
|
||||||
issueId={issueId}
|
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 scrollIntoView from "smooth-scroll-into-view-if-needed";
|
||||||
import { ContrastIcon } from "lucide-react";
|
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";
|
import { Avatar, CycleGroupIcon, DiceIcon, PriorityIcon, StateGroupIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { ProjectLogo } from "@/components/project";
|
import { ProjectLogo } from "@/components/project";
|
||||||
@ -8,6 +19,7 @@ import { ProjectLogo } from "@/components/project";
|
|||||||
import { ISSUE_PRIORITIES, EIssuesStoreType } from "@/constants/issue";
|
import { ISSUE_PRIORITIES, EIssuesStoreType } from "@/constants/issue";
|
||||||
import { STATE_GROUPS } from "@/constants/state";
|
import { STATE_GROUPS } from "@/constants/state";
|
||||||
import { ICycleStore } from "@/store/cycle.store";
|
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 { ILabelStore } from "@/store/label.store";
|
||||||
import { IMemberRootStore } from "@/store/member";
|
import { IMemberRootStore } from "@/store/member";
|
||||||
import { IModuleStore } from "@/store/module.store";
|
import { IModuleStore } from "@/store/module.store";
|
||||||
@ -20,6 +32,20 @@ import { IStateStore } from "@/store/state.store";
|
|||||||
export const HIGHLIGHT_CLASS = "highlight";
|
export const HIGHLIGHT_CLASS = "highlight";
|
||||||
export const HIGHLIGHT_WITH_LINE = "highlight-with-line";
|
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) =>
|
export const isWorkspaceLevel = (type: EIssuesStoreType) =>
|
||||||
[EIssuesStoreType.PROFILE, EIssuesStoreType.GLOBAL].includes(type) ? true : false;
|
[EIssuesStoreType.PROFILE, EIssuesStoreType.GLOBAL].includes(type) ? true : false;
|
||||||
|
|
||||||
@ -96,11 +122,14 @@ const getCycleColumns = (projectStore: IProjectStore, cycleStore: ICycleStore):
|
|||||||
const cycle = getCycleById(cycleId);
|
const cycle = getCycleById(cycleId);
|
||||||
if (cycle) {
|
if (cycle) {
|
||||||
const cycleStatus = cycle.status ? (cycle.status.toLocaleLowerCase() as TCycleGroups) : "draft";
|
const cycleStatus = cycle.status ? (cycle.status.toLocaleLowerCase() as TCycleGroups) : "draft";
|
||||||
|
const isDropDisabled = cycleStatus === "completed";
|
||||||
cycles.push({
|
cycles.push({
|
||||||
id: cycle.id,
|
id: cycle.id,
|
||||||
name: cycle.name,
|
name: cycle.name,
|
||||||
icon: <CycleGroupIcon cycleGroup={cycleStatus as TCycleGroups} className="h-3.5 w-3.5" />,
|
icon: <CycleGroupIcon cycleGroup={cycleStatus as TCycleGroups} className="h-3.5 w-3.5" />,
|
||||||
payload: { cycle_id: cycle.id },
|
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 });
|
await scrollIntoView(sourceElement, { behavior: "smooth", block: "center", duration: 1500 });
|
||||||
}, 200);
|
}, 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[]) => {
|
const addIssueToModule = async (issue: TIssue, moduleIds: string[]) => {
|
||||||
if (!workspaceSlug || !activeProjectId) return;
|
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));
|
moduleIds.forEach((moduleId) => fetchModuleDetails(workspaceSlug, activeProjectId, moduleId));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ export interface ICycleIssues {
|
|||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
|
removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise<void>;
|
||||||
addCycleToIssue: (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: (
|
transferIssuesFromCycle: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
@ -273,7 +274,13 @@ export class CycleIssues extends IssueHelperStore implements ICycleIssues {
|
|||||||
const response = await this.createIssue(workspaceSlug, projectId, data, cycleId);
|
const response = await this.createIssue(workspaceSlug, projectId, data, cycleId);
|
||||||
|
|
||||||
if (data.module_ids && data.module_ids.length > 0)
|
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);
|
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) => {
|
removeIssueFromCycle = async (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => {
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
|
@ -197,11 +197,12 @@ export class IssueStore implements IIssueStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
addModulesToIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
|
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,
|
workspaceSlug,
|
||||||
projectId,
|
projectId,
|
||||||
issueId,
|
issueId,
|
||||||
moduleIds
|
moduleIds,
|
||||||
|
[]
|
||||||
);
|
);
|
||||||
if (moduleIds && moduleIds.length > 0)
|
if (moduleIds && moduleIds.length > 0)
|
||||||
await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
|
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[]) => {
|
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,
|
workspaceSlug,
|
||||||
projectId,
|
projectId,
|
||||||
issueId,
|
issueId,
|
||||||
|
[],
|
||||||
moduleIds
|
moduleIds
|
||||||
);
|
);
|
||||||
await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
|
await this.rootIssueDetailStore.activity.fetchActivities(workspaceSlug, projectId, issueId);
|
||||||
|
@ -22,6 +22,8 @@ export interface IIssueKanBanViewStore {
|
|||||||
setIsDragging: (isDragging: boolean) => void;
|
setIsDragging: (isDragging: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DRAG_ALLOWED_GROUPS: TIssueGroupByOptions[] = ["state", "priority", "assignees", "labels", "module", "cycle"];
|
||||||
|
|
||||||
export class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
export class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
||||||
kanBanToggle: {
|
kanBanToggle: {
|
||||||
groupByHeaderMinMax: string[];
|
groupByHeaderMinMax: string[];
|
||||||
@ -53,9 +55,9 @@ export class IssueKanBanViewStore implements IIssueKanBanViewStore {
|
|||||||
|
|
||||||
getCanUserDragDrop = computedFn(
|
getCanUserDragDrop = computedFn(
|
||||||
(group_by: TIssueGroupByOptions | undefined, sub_group_by: TIssueGroupByOptions | undefined) => {
|
(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) 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import concat from "lodash/concat";
|
import concat from "lodash/concat";
|
||||||
import pull from "lodash/pull";
|
import pull from "lodash/pull";
|
||||||
|
import isEmpty from "lodash/isEmpty";
|
||||||
import set from "lodash/set";
|
import set from "lodash/set";
|
||||||
import uniq from "lodash/uniq";
|
import uniq from "lodash/uniq";
|
||||||
import update from "lodash/update";
|
import update from "lodash/update";
|
||||||
@ -63,12 +64,12 @@ export interface IModuleIssues {
|
|||||||
moduleId: string,
|
moduleId: string,
|
||||||
issueIds: string[]
|
issueIds: string[]
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
addModulesToIssue: (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => Promise<void>;
|
changeModulesInIssue: (
|
||||||
removeModulesFromIssue: (
|
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectId: string,
|
||||||
issueId: string,
|
issueId: string,
|
||||||
moduleIds: string[]
|
addModuleIds: string[],
|
||||||
|
removeModuleIds: string[]
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,8 +105,7 @@ export class ModuleIssues extends IssueHelperStore implements IModuleIssues {
|
|||||||
quickAddIssue: action,
|
quickAddIssue: action,
|
||||||
addIssuesToModule: action,
|
addIssuesToModule: action,
|
||||||
removeIssuesFromModule: action,
|
removeIssuesFromModule: action,
|
||||||
addModulesToIssue: action,
|
changeModulesInIssue: action,
|
||||||
removeModulesFromIssue: action,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rootIssueStore = _rootStore;
|
this.rootIssueStore = _rootStore;
|
||||||
@ -368,66 +368,77 @@ 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
|
// 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 {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
// add the new issue ids to the module issues map
|
// remove the new issue id to the module issues map
|
||||||
moduleIds.forEach((moduleId) => {
|
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 = []) => {
|
update(this.issues, moduleId, (moduleIssueIds = []) => {
|
||||||
if (moduleIssueIds.includes(issueId)) return moduleIssueIds;
|
if (moduleIssueIds.includes(issueId)) return moduleIssueIds;
|
||||||
else return uniq(concat(moduleIssueIds, [issueId]));
|
else return uniq(concat(moduleIssueIds, [issueId]));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
if(originalModuleIds){
|
||||||
// update the root issue map with the new module ids
|
// update the root issue map with the new module ids
|
||||||
update(this.rootStore.issues.issuesMap, [issueId, "module_ids"], (issueModuleIds = []) =>
|
let currentModuleIds = concat([...originalModuleIds], addModuleIds);
|
||||||
uniq(concat(issueModuleIds, moduleIds))
|
currentModuleIds = pull(currentModuleIds, ...removeModuleIds);
|
||||||
);
|
this.rootStore.issues.updateIssue(issueId, { module_ids: uniq(currentModuleIds) });
|
||||||
});
|
}
|
||||||
|
|
||||||
const issueToModule = await this.moduleService.addModulesToIssue(workspaceSlug, projectId, issueId, {
|
//Perform API calls
|
||||||
modules: moduleIds,
|
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) {
|
} catch (error) {
|
||||||
// revert the issue back to its original module ids
|
// revert the issue back to its original module ids
|
||||||
set(this.rootStore.issues.issuesMap, [issueId, "module_ids"], originalModuleIds);
|
set(this.rootStore.issues.issuesMap, [issueId, "module_ids"], originalModuleIds);
|
||||||
// remove the new issue ids from the module issues map
|
// add the removed issue id to the module issues map
|
||||||
moduleIds.forEach((moduleId) => {
|
addModuleIds.forEach((moduleId) => {
|
||||||
runInAction(() => {
|
|
||||||
update(this.issues, moduleId, (moduleIssueIds = []) => pull(moduleIssueIds, issueId));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
removeModulesFromIssue = async (workspaceSlug: string, projectId: string, issueId: string, moduleIds: string[]) => {
|
|
||||||
try {
|
|
||||||
runInAction(() => {
|
|
||||||
moduleIds.forEach((moduleId) => {
|
|
||||||
update(this.issues, moduleId, (moduleIssueIds = []) => {
|
update(this.issues, moduleId, (moduleIssueIds = []) => {
|
||||||
if (moduleIssueIds.includes(issueId)) return pull(moduleIssueIds, issueId);
|
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]));
|
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;
|
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]);
|
await this.rootStore.cycleIssues.addIssueToCycle(workspaceSlug, projectId, data.cycle_id, [response.id]);
|
||||||
|
|
||||||
if (data.module_ids && data.module_ids.length > 0)
|
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);
|
const quickAddIssueIndex = this.issues[viewId].findIndex((_issueId) => _issueId === data.id);
|
||||||
if (quickAddIssueIndex >= 0)
|
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]);
|
await this.rootStore.cycleIssues.addIssueToCycle(workspaceSlug, projectId, data.cycle_id, [response.id]);
|
||||||
|
|
||||||
if (data.module_ids && data.module_ids.length > 0)
|
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);
|
const quickAddIssueIndex = this.issues[projectId].findIndex((_issueId) => _issueId === data.id);
|
||||||
if (quickAddIssueIndex >= 0)
|
if (quickAddIssueIndex >= 0)
|
||||||
|
@ -43,15 +43,28 @@
|
|||||||
--color-primary-800: 19, 35, 76;
|
--color-primary-800: 19, 35, 76;
|
||||||
--color-primary-900: 13, 24, 51;
|
--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-100: 255, 255, 255; /* primary bg */
|
||||||
--color-background-90: 247, 247, 247; /* secondary bg */
|
--color-background-90: 247, 247, 247; /* secondary bg */
|
||||||
--color-background-80: 232, 232, 232; /* tertiary 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-100: 23, 23, 23; /* primary text */
|
||||||
--color-text-200: 58, 58, 58; /* secondary text */
|
--color-text-200: 58, 58, 58; /* secondary text */
|
||||||
--color-text-300: 82, 82, 82; /* tertiary text */
|
--color-text-300: 82, 82, 82; /* tertiary text */
|
||||||
--color-text-400: 163, 163, 163; /* placeholder 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-scrollbar: 163, 163, 163; /* scrollbar thumb */
|
||||||
|
|
||||||
--color-border-100: 245, 245, 245; /* subtle border= 1 */
|
--color-border-100: 245, 245, 245; /* subtle border= 1 */
|
||||||
@ -59,6 +72,9 @@
|
|||||||
--color-border-300: 212, 212, 212; /* strong border- 1 */
|
--color-border-300: 212, 212, 212; /* strong border- 1 */
|
||||||
--color-border-400: 185, 185, 185; /* strong border- 2 */
|
--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),
|
--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);
|
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),
|
--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 */
|
/* toast theme */
|
||||||
--color-toast-success-text: 62, 155, 79;
|
--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-warning-text: 255, 186, 24;
|
||||||
--color-toast-info-text: 51, 88, 212;
|
--color-toast-info-text: 51, 88, 212;
|
||||||
--color-toast-loading-text: 28, 32, 36;
|
--color-toast-loading-text: 28, 32, 36;
|
||||||
@ -159,13 +175,13 @@
|
|||||||
--color-toast-tertiary-text: 96, 100, 108;
|
--color-toast-tertiary-text: 96, 100, 108;
|
||||||
|
|
||||||
--color-toast-success-background: 253, 253, 254;
|
--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-warning-background: 254, 253, 251;
|
||||||
--color-toast-info-background: 253, 253, 254;
|
--color-toast-info-background: 253, 253, 254;
|
||||||
--color-toast-loading-background: 253, 253, 254;
|
--color-toast-loading-background: 253, 253, 254;
|
||||||
|
|
||||||
--color-toast-success-border: 218, 241, 219;
|
--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-warning-border: 255, 247, 194;
|
||||||
--color-toast-info-border: 210, 222, 255;
|
--color-toast-info-border: 210, 222, 255;
|
||||||
--color-toast-loading-border: 224, 225, 230;
|
--color-toast-loading-border: 224, 225, 230;
|
||||||
@ -193,6 +209,15 @@
|
|||||||
--color-background-90: 32, 32, 32; /* secondary bg */
|
--color-background-90: 32, 32, 32; /* secondary bg */
|
||||||
--color-background-80: 44, 44, 44; /* tertiary 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-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-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);
|
--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