Merge branch 'preview' of gurusainath:makeplane/plane into chore-issue-description-version

This commit is contained in:
gurusainath
2024-12-11 18:35:04 +05:30
57 changed files with 578 additions and 575 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "admin",
"version": "0.24.0",
"version": "0.24.1",
"private": true,
"scripts": {
"dev": "turbo run develop",

View File

@@ -4,7 +4,7 @@ FROM python:3.12.5-alpine AS backend
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
ENV INSTANCE_CHANGELOG_URL https://api.plane.so/api/public/anchor/8e1c2e4c7bc5493eb7731be3862f6960/pages/
ENV INSTANCE_CHANGELOG_URL https://sites.plane.so/pages/691ef037bcfe416a902e48cb55f59891/
WORKDIR /code

View File

@@ -4,7 +4,7 @@ FROM python:3.12.5-alpine AS backend
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV PIP_DISABLE_PIP_VERSION_CHECK=1
ENV INSTANCE_CHANGELOG_URL https://api.plane.so/api/public/anchor/8e1c2e4c7bc5493eb7731be3862f6960/pages/
ENV INSTANCE_CHANGELOG_URL https://sites.plane.so/pages/691ef037bcfe416a902e48cb55f59891/
RUN apk --no-cache add \
"bash~=5.2" \

View File

@@ -1,4 +1,4 @@
{
"name": "plane-api",
"version": "0.24.0"
"version": "0.24.1"
}

View File

@@ -1,6 +1,6 @@
{
"name": "live",
"version": "0.24.0",
"version": "0.24.1",
"description": "",
"main": "./src/server.ts",
"private": true,

View File

@@ -1,6 +1,6 @@
{
"repository": "https://github.com/makeplane/plane.git",
"version": "0.24.0",
"version": "0.24.1",
"license": "AGPL-3.0",
"private": true,
"workspaces": [

View File

@@ -1,6 +1,6 @@
{
"name": "@plane/constants",
"version": "0.24.0",
"version": "0.24.1",
"private": true,
"main": "./src/index.ts"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@plane/editor",
"version": "0.24.0",
"version": "0.24.1",
"description": "Core Editor that powers Plane",
"private": true,
"main": "./dist/index.mjs",

View File

@@ -1,7 +1,7 @@
{
"name": "@plane/eslint-config",
"private": true,
"version": "0.24.0",
"version": "0.24.1",
"files": [
"library.js",
"next.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@plane/hooks",
"version": "0.24.0",
"version": "0.24.1",
"description": "React hooks that are shared across multiple apps internally",
"private": true,
"main": "./dist/index.js",

View File

@@ -1,6 +1,6 @@
{
"name": "tailwind-config-custom",
"version": "0.24.0",
"version": "0.24.1",
"description": "common tailwind configuration across monorepo",
"main": "index.js",
"private": true,

View File

@@ -1,6 +1,6 @@
{
"name": "@plane/types",
"version": "0.24.0",
"version": "0.24.1",
"private": true,
"types": "./src/index.d.ts",
"main": "./src/index.d.ts"

15
packages/types/src/command-palette.d.ts vendored Normal file
View File

@@ -0,0 +1,15 @@
export type TCommandPaletteActionList = Record<
string,
{ title: string; description: string; action: () => void }
>;
export type TCommandPaletteShortcutList = {
key: string;
title: string;
shortcuts: TCommandPaletteShortcut[];
};
export type TCommandPaletteShortcut = {
keys: string; // comma separated keys
description: string;
};

View File

@@ -32,3 +32,4 @@ export * from "./workspace-notifications";
export * from "./favorite";
export * from "./file";
export * from "./workspace-draft-issues/base";
export * from "./command-palette";

View File

@@ -216,7 +216,7 @@ export type GroupByColumnTypes =
export interface IGroupByColumn {
id: string;
name: string;
icon: ReactElement | undefined;
icon?: ReactElement | undefined;
payload: Partial<TIssue>;
isDropDisabled?: boolean;
dropErrorMessage?: string;

View File

@@ -35,7 +35,7 @@ export type TNotificationData = {
};
export type TNotification = {
id: string | undefined;
id: string;
title: string | undefined;
data: TNotificationData | undefined;
entity_identifier: string | undefined;

View File

@@ -1,6 +1,6 @@
{
"name": "@plane/typescript-config",
"version": "0.24.0",
"version": "0.24.1",
"private": true,
"files": [
"base.json",

View File

@@ -2,7 +2,7 @@
"name": "@plane/ui",
"description": "UI components shared across multiple apps internally",
"private": true,
"version": "0.24.0",
"version": "0.24.1",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",

View File

@@ -1,6 +1,6 @@
{
"name": "@plane/utils",
"version": "0.24.0",
"version": "0.24.1",
"description": "Helper functions shared across multiple apps internally",
"private": true,
"main": "./dist/index.js",

View File

@@ -1,6 +1,6 @@
{
"name": "space",
"version": "0.24.0",
"version": "0.24.1",
"private": true,
"scripts": {
"dev": "turbo run develop",

View File

@@ -1,7 +1,7 @@
"use client";
// components
import { NotificationsSidebarRoot } from "@/plane-web/components/workspace-notifications";
import { NotificationsSidebarRoot } from "@/components/workspace-notifications";
export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) {
return (

View File

@@ -0,0 +1,3 @@
export * from "./workspace-level";
export * from "./project-level";
export * from "./issue-level";

View File

@@ -0,0 +1,73 @@
import { observer } from "mobx-react";
import { useParams, usePathname } from "next/navigation";
import useSWR from "swr";
// components
import { BulkDeleteIssuesModal } from "@/components/core";
import { CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
// constants
import { ISSUE_DETAILS } from "@/constants/fetch-keys";
// hooks
import { useCommandPalette, useUser } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
import { useIssuesStore } from "@/hooks/use-issue-layout-store";
// services
import { IssueService } from "@/services/issue";
// services
const issueService = new IssueService();
export const IssueLevelModals = observer(() => {
// router
const pathname = usePathname();
const { workspaceSlug, projectId, issueId, cycleId, moduleId } = useParams();
const router = useAppRouter();
// store hooks
const { data: currentUser } = useUser();
const {
issues: { removeIssue },
} = useIssuesStore();
const {
isCreateIssueModalOpen,
toggleCreateIssueModal,
isDeleteIssueModalOpen,
toggleDeleteIssueModal,
isBulkDeleteIssueModalOpen,
toggleBulkDeleteIssueModal,
} = useCommandPalette();
// derived values
const isDraftIssue = pathname?.includes("draft-issues") || false;
const { data: issueDetails } = useSWR(
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null,
workspaceSlug && projectId && issueId
? () => issueService.retrieve(workspaceSlug as string, projectId as string, issueId as string)
: null
);
return (
<>
<CreateUpdateIssueModal
isOpen={isCreateIssueModalOpen}
onClose={() => toggleCreateIssueModal(false)}
data={cycleId ? { cycle_id: cycleId.toString() } : moduleId ? { module_ids: [moduleId.toString()] } : undefined}
isDraft={isDraftIssue}
/>
{workspaceSlug && projectId && issueId && issueDetails && (
<DeleteIssueModal
handleClose={() => toggleDeleteIssueModal(false)}
isOpen={isDeleteIssueModalOpen}
data={issueDetails}
onSubmit={async () => {
await removeIssue(workspaceSlug.toString(), projectId.toString(), issueId.toString());
router.push(`/${workspaceSlug}/projects/${projectId}/issues`);
}}
/>
)}
<BulkDeleteIssuesModal
isOpen={isBulkDeleteIssueModalOpen}
onClose={() => toggleBulkDeleteIssueModal(false)}
user={currentUser}
/>
</>
);
});

View File

@@ -0,0 +1,59 @@
import { observer } from "mobx-react";
// components
import { CycleCreateUpdateModal } from "@/components/cycles";
import { CreateUpdateModuleModal } from "@/components/modules";
import { CreatePageModal } from "@/components/pages";
import { CreateUpdateProjectViewModal } from "@/components/views";
// hooks
import { useCommandPalette } from "@/hooks/store";
export type TProjectLevelModalsProps = {
workspaceSlug: string;
projectId: string;
};
export const ProjectLevelModals = observer((props: TProjectLevelModalsProps) => {
const { workspaceSlug, projectId } = props;
// store hooks
const {
isCreateCycleModalOpen,
toggleCreateCycleModal,
isCreateModuleModalOpen,
toggleCreateModuleModal,
isCreateViewModalOpen,
toggleCreateViewModal,
createPageModal,
toggleCreatePageModal,
} = useCommandPalette();
return (
<>
<CycleCreateUpdateModal
isOpen={isCreateCycleModalOpen}
handleClose={() => toggleCreateCycleModal(false)}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
/>
<CreateUpdateModuleModal
isOpen={isCreateModuleModalOpen}
onClose={() => toggleCreateModuleModal(false)}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
/>
<CreateUpdateProjectViewModal
isOpen={isCreateViewModalOpen}
onClose={() => toggleCreateViewModal(false)}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
/>
<CreatePageModal
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
isModalOpen={createPageModal.isOpen}
pageAccess={createPageModal.pageAccess}
handleModalClose={() => toggleCreatePageModal({ isOpen: false })}
redirectionEnabled
/>
</>
);
});

View File

@@ -0,0 +1,25 @@
import { observer } from "mobx-react";
// components
import { CreateProjectModal } from "@/components/project";
// hooks
import { useCommandPalette } from "@/hooks/store";
export type TWorkspaceLevelModalsProps = {
workspaceSlug: string;
};
export const WorkspaceLevelModals = observer((props: TWorkspaceLevelModalsProps) => {
const { workspaceSlug } = props;
// store hooks
const { isCreateProjectModalOpen, toggleCreateProjectModal } = useCommandPalette();
return (
<>
<CreateProjectModal
isOpen={isCreateProjectModalOpen}
onClose={() => toggleCreateProjectModal(false)}
workspaceSlug={workspaceSlug.toString()}
/>
</>
);
});

View File

@@ -1 +1 @@
export * from './root'
export * from "./notification-card/root";

View File

@@ -0,0 +1,95 @@
// types
import { TCommandPaletteActionList, TCommandPaletteShortcut, TCommandPaletteShortcutList } from "@plane/types";
// store
import { store } from "@/lib/store-context";
export const getGlobalShortcutsList: () => TCommandPaletteActionList = () => {
const { toggleCreateIssueModal } = store.commandPalette;
return {
c: {
title: "Create a new issue",
description: "Create a new issue in the current project",
action: () => toggleCreateIssueModal(true),
},
};
};
export const getWorkspaceShortcutsList: () => TCommandPaletteActionList = () => {
const { toggleCreateProjectModal } = store.commandPalette;
return {
p: {
title: "Create a new project",
description: "Create a new project in the current workspace",
action: () => toggleCreateProjectModal(true),
},
};
};
export const getProjectShortcutsList: () => TCommandPaletteActionList = () => {
const {
toggleCreatePageModal,
toggleCreateModuleModal,
toggleCreateCycleModal,
toggleCreateViewModal,
toggleBulkDeleteIssueModal,
} = store.commandPalette;
return {
d: {
title: "Create a new page",
description: "Create a new page in the current project",
action: () => toggleCreatePageModal({ isOpen: true }),
},
m: {
title: "Create a new module",
description: "Create a new module in the current project",
action: () => toggleCreateModuleModal(true),
},
q: {
title: "Create a new cycle",
description: "Create a new cycle in the current project",
action: () => toggleCreateCycleModal(true),
},
v: {
title: "Create a new view",
description: "Create a new view in the current project",
action: () => toggleCreateViewModal(true),
},
backspace: {
title: "Bulk delete issues",
description: "Bulk delete issues in the current project",
action: () => toggleBulkDeleteIssueModal(true),
},
delete: {
title: "Bulk delete issues",
description: "Bulk delete issues in the current project",
action: () => toggleBulkDeleteIssueModal(true),
},
};
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const handleAdditionalKeyDownEvents = (e: KeyboardEvent) => null;
export const getNavigationShortcutsList = (): TCommandPaletteShortcut[] => [
{ keys: "Ctrl,K", description: "Open command menu" },
];
export const getCommonShortcutsList = (platform: string): TCommandPaletteShortcut[] => [
{ keys: "P", description: "Create project" },
{ keys: "C", description: "Create issue" },
{ keys: "Q", description: "Create cycle" },
{ keys: "M", description: "Create module" },
{ keys: "V", description: "Create view" },
{ keys: "D", description: "Create page" },
{ keys: "Delete", description: "Bulk delete issues" },
{ keys: "Shift,/", description: "Open shortcuts guide" },
{
keys: platform === "MacOS" ? "Ctrl,control,C" : "Ctrl,Alt,C",
description: "Copy issue URL from the issue details page",
},
];
export const getAdditionalShortcutsList = (): TCommandPaletteShortcutList[] => [];

View File

@@ -0,0 +1,12 @@
import { makeObservable } from "mobx";
// types / constants
import { BaseCommandPaletteStore, IBaseCommandPaletteStore } from "@/store/base-command-palette.store";
export type ICommandPaletteStore = IBaseCommandPaletteStore;
export class CommandPaletteStore extends BaseCommandPaletteStore implements ICommandPaletteStore {
constructor() {
super();
makeObservable(this, {});
}
}

View File

@@ -2,87 +2,43 @@
import React, { useCallback, useEffect, FC, useMemo } from "react";
import { observer } from "mobx-react";
import { useParams, usePathname } from "next/navigation";
import useSWR from "swr";
import { useParams } from "next/navigation";
// ui
import { TOAST_TYPE, setToast } from "@plane/ui";
// components
import { CommandModal, ShortcutsModal } from "@/components/command-palette";
import { BulkDeleteIssuesModal } from "@/components/core";
import { CycleCreateUpdateModal } from "@/components/cycles";
import { CreateUpdateIssueModal, DeleteIssueModal } from "@/components/issues";
import { CreateUpdateModuleModal } from "@/components/modules";
import { CreatePageModal } from "@/components/pages";
import { CreateProjectModal } from "@/components/project";
import { CreateUpdateProjectViewModal } from "@/components/views";
// constants
import { ISSUE_DETAILS } from "@/constants/fetch-keys";
// helpers
import { copyTextToClipboard } from "@/helpers/string.helper";
// hooks
import { useEventTracker, useUser, useAppTheme, useCommandPalette, useUserPermissions } from "@/hooks/store";
import { useAppRouter } from "@/hooks/use-app-router";
import { useIssuesStore } from "@/hooks/use-issue-layout-store";
import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web components
import {
IssueLevelModals,
ProjectLevelModals,
WorkspaceLevelModals,
} from "@/plane-web/components/command-palette/modals";
// plane web constants
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
// services
import { IssueService } from "@/services/issue";
// services
const issueService = new IssueService();
// plane web helpers
import {
getGlobalShortcutsList,
getProjectShortcutsList,
getWorkspaceShortcutsList,
handleAdditionalKeyDownEvents,
} from "@/plane-web/helpers/command-palette";
export const CommandPalette: FC = observer(() => {
// router
const router = useAppRouter();
// router params
const { workspaceSlug, projectId, issueId, cycleId, moduleId } = useParams();
// pathname
const pathname = usePathname();
const { workspaceSlug, projectId, issueId } = useParams();
// store hooks
const { toggleSidebar } = useAppTheme();
const { setTrackElement } = useEventTracker();
const { platform } = usePlatformOS();
const {
data: currentUser,
// canPerformProjectMemberActions,
// canPerformWorkspaceMemberActions,
canPerformAnyCreateAction,
// canPerformProjectAdminActions,
} = useUser();
const {
issues: { removeIssue },
} = useIssuesStore();
const {
toggleCommandPaletteModal,
isCreateIssueModalOpen,
toggleCreateIssueModal,
isCreateCycleModalOpen,
toggleCreateCycleModal,
createPageModal,
toggleCreatePageModal,
isCreateProjectModalOpen,
toggleCreateProjectModal,
isCreateModuleModalOpen,
toggleCreateModuleModal,
isCreateViewModalOpen,
toggleCreateViewModal,
isShortcutModalOpen,
toggleShortcutModal,
isBulkDeleteIssueModalOpen,
toggleBulkDeleteIssueModal,
isDeleteIssueModalOpen,
toggleDeleteIssueModal,
isAnyModalOpen,
} = useCommandPalette();
const { data: currentUser, canPerformAnyCreateAction } = useUser();
const { toggleCommandPaletteModal, isShortcutModalOpen, toggleShortcutModal, isAnyModalOpen } = useCommandPalette();
const { allowPermissions } = useUserPermissions();
const { data: issueDetails } = useSWR(
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null,
workspaceSlug && projectId && issueId
? () => issueService.retrieve(workspaceSlug as string, projectId as string, issueId as string)
: null
);
// derived values
const canPerformWorkspaceMemberActions = allowPermissions(
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
@@ -170,62 +126,11 @@ export const CommandPalette: FC = observer(() => {
project: Record<string, { title: string; description: string; action: () => void }>;
} = useMemo(
() => ({
global: {
c: {
title: "Create a new issue",
description: "Create a new issue in the current project",
action: () => toggleCreateIssueModal(true),
},
},
workspace: {
p: {
title: "Create a new project",
description: "Create a new project in the current workspace",
action: () => toggleCreateProjectModal(true),
},
},
project: {
d: {
title: "Create a new page",
description: "Create a new page in the current project",
action: () => toggleCreatePageModal({ isOpen: true }),
},
m: {
title: "Create a new module",
description: "Create a new module in the current project",
action: () => toggleCreateModuleModal(true),
},
q: {
title: "Create a new cycle",
description: "Create a new cycle in the current project",
action: () => toggleCreateCycleModal(true),
},
v: {
title: "Create a new view",
description: "Create a new view in the current project",
action: () => toggleCreateViewModal(true),
},
backspace: {
title: "Bulk delete issues",
description: "Bulk delete issues in the current project",
action: () => toggleBulkDeleteIssueModal(true),
},
delete: {
title: "Bulk delete issues",
description: "Bulk delete issues in the current project",
action: () => toggleBulkDeleteIssueModal(true),
},
},
global: getGlobalShortcutsList(),
workspace: getWorkspaceShortcutsList(),
project: getProjectShortcutsList(),
}),
[
toggleBulkDeleteIssueModal,
toggleCreateCycleModal,
toggleCreateIssueModal,
toggleCreateModuleModal,
toggleCreatePageModal,
toggleCreateProjectModal,
toggleCreateViewModal,
]
[]
);
const handleKeyDown = useCallback(
@@ -296,6 +201,8 @@ export const CommandPalette: FC = observer(() => {
shortcutsList.project[keyPressed].action();
}
}
// Additional keydown events
handleAdditionalKeyDownEvents(e);
},
[
copyIssueUrlToClipboard,
@@ -320,75 +227,16 @@ export const CommandPalette: FC = observer(() => {
return () => document.removeEventListener("keydown", handleKeyDown);
}, [handleKeyDown]);
const isDraftIssue = pathname?.includes("draft-issues") || false;
if (!currentUser) return null;
return (
<>
<ShortcutsModal isOpen={isShortcutModalOpen} onClose={() => toggleShortcutModal(false)} />
{workspaceSlug && (
<CreateProjectModal
isOpen={isCreateProjectModalOpen}
onClose={() => toggleCreateProjectModal(false)}
workspaceSlug={workspaceSlug.toString()}
/>
)}
{workspaceSlug && <WorkspaceLevelModals workspaceSlug={workspaceSlug.toString()} />}
{workspaceSlug && projectId && (
<>
<CycleCreateUpdateModal
isOpen={isCreateCycleModalOpen}
handleClose={() => toggleCreateCycleModal(false)}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
/>
<CreateUpdateModuleModal
isOpen={isCreateModuleModalOpen}
onClose={() => toggleCreateModuleModal(false)}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
/>
<CreateUpdateProjectViewModal
isOpen={isCreateViewModalOpen}
onClose={() => toggleCreateViewModal(false)}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
/>
<CreatePageModal
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
isModalOpen={createPageModal.isOpen}
pageAccess={createPageModal.pageAccess}
handleModalClose={() => toggleCreatePageModal({ isOpen: false })}
redirectionEnabled
/>
</>
<ProjectLevelModals workspaceSlug={workspaceSlug.toString()} projectId={projectId.toString()} />
)}
<CreateUpdateIssueModal
isOpen={isCreateIssueModalOpen}
onClose={() => toggleCreateIssueModal(false)}
data={cycleId ? { cycle_id: cycleId.toString() } : moduleId ? { module_ids: [moduleId.toString()] } : undefined}
isDraft={isDraftIssue}
/>
{workspaceSlug && projectId && issueId && issueDetails && (
<DeleteIssueModal
handleClose={() => toggleDeleteIssueModal(false)}
isOpen={isDeleteIssueModalOpen}
data={issueDetails}
onSubmit={async () => {
await removeIssue(workspaceSlug.toString(), projectId.toString(), issueId.toString());
router.push(`/${workspaceSlug}/projects/${projectId}/issues`);
}}
/>
)}
<BulkDeleteIssuesModal
isOpen={isBulkDeleteIssueModalOpen}
onClose={() => toggleBulkDeleteIssueModal(false)}
user={currentUser}
/>
<IssueLevelModals />
<CommandModal />
</>
);

View File

@@ -3,6 +3,12 @@ import { Command } from "lucide-react";
import { substringMatch } from "@/helpers/string.helper";
// hooks
import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web helpers
import {
getAdditionalShortcutsList,
getCommonShortcutsList,
getNavigationShortcutsList,
} from "@/plane-web/helpers/command-palette";
type Props = {
searchQuery: string;
@@ -16,26 +22,14 @@ export const ShortcutCommandsList: React.FC<Props> = (props) => {
{
key: "navigation",
title: "Navigation",
shortcuts: [{ keys: "Ctrl,K", description: "Open command menu" }],
shortcuts: getNavigationShortcutsList(),
},
{
key: "common",
title: "Common",
shortcuts: [
{ keys: "P", description: "Create project" },
{ keys: "C", description: "Create issue" },
{ keys: "Q", description: "Create cycle" },
{ keys: "M", description: "Create module" },
{ keys: "V", description: "Create view" },
{ keys: "D", description: "Create page" },
{ keys: "Delete", description: "Bulk delete issues" },
{ keys: "Shift,/", description: "Open shortcuts guide" },
{
keys: platform === "MacOS" ? "Ctrl,control,C" : "Ctrl,Alt,C",
description: "Copy issue URL from the issue details page",
},
],
shortcuts: getCommonShortcutsList(platform),
},
...getAdditionalShortcutsList(),
];
const filteredShortcuts = KEYBOARD_SHORTCUTS.map((category) => {
@@ -69,7 +63,11 @@ export const ShortcutCommandsList: React.FC<Props> = (props) => {
<div key={key} className="flex items-center gap-1">
{key === "Ctrl" ? (
<div className="grid h-6 min-w-[1.5rem] place-items-center rounded-sm border-[0.5px] border-custom-border-200 bg-custom-background-90 px-1.5 text-[10px] text-custom-text-200">
{ platform === "MacOS" ? <Command className="h-2.5 w-2.5 text-custom-text-200" /> : 'Ctrl'}
{platform === "MacOS" ? (
<Command className="h-2.5 w-2.5 text-custom-text-200" />
) : (
"Ctrl"
)}
</div>
) : (
<kbd className="grid h-6 min-w-[1.5rem] place-items-center rounded-sm border-[0.5px] border-custom-border-200 bg-custom-background-90 px-1.5 text-[10px] text-custom-text-200">

View File

@@ -1,6 +1,6 @@
import { useRef, useState } from "react";
// Plane
import { setToast } from "@plane/ui";
import { setToast, TOAST_TYPE } from "@plane/ui";
// hooks
import { useTimeLineChartStore } from "@/hooks/use-timeline-chart";
//
@@ -103,7 +103,7 @@ export const useGanttResizable = (
const deltaWidth = Math.round((width - (block.position?.width ?? 0)) / dayWidth) * dayWidth;
// call update blockPosition
if (deltaWidth || deltaLeft) updateBlockPosition(block.id, deltaLeft, deltaWidth, dragDirection !== "move");
if (deltaWidth || deltaLeft) updateBlockPosition(block.id, deltaLeft, deltaWidth);
};
// remove event listeners and call updateBlockDates
@@ -119,10 +119,14 @@ export const useGanttResizable = (
(dragDirection === "left" && !block.start_date) || (dragDirection === "right" && !block.target_date);
try {
const blockUpdates = getUpdatedPositionAfterDrag(block.id, shouldUpdateHalfBlock, dragDirection !== "move");
updateBlockDates && updateBlockDates(blockUpdates);
} catch (e) {
setToast;
const blockUpdates = getUpdatedPositionAfterDrag(block.id, shouldUpdateHalfBlock);
if (updateBlockDates) updateBlockDates(blockUpdates);
} catch {
setToast({
type: TOAST_TYPE.ERROR,
title: "Error",
message: "Something went wrong while updating block dates",
});
}
setIsDragging(false);

View File

@@ -82,6 +82,7 @@ export const IssueActivityItem: FC<TIssueActivityItem> = observer((props) => {
return <IssueAttachmentActivity {...componentDefaultProps} showIssue={false} />;
case "archived_at":
return <IssueArchivedAtActivity {...componentDefaultProps} />;
case "intake":
case "inbox":
return <IssueInboxActivity {...componentDefaultProps} />;
case "type":

View File

@@ -18,7 +18,7 @@ import { ContentWrapper } from "@plane/ui";
import RenderIfVisible from "@/components/core/render-if-visible-HOC";
import { KanbanColumnLoader } from "@/components/ui";
// hooks
import { useCycle, useKanbanView, useLabel, useMember, useModule, useProject, useProjectState } from "@/hooks/store";
import { useKanbanView } from "@/hooks/store";
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
// types
// parent components
@@ -87,30 +87,16 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
dropErrorMessage,
subGroupIndex = 0,
} = props;
// store hooks
const storeType = useIssueStoreType();
const member = useMember();
const project = useProject();
const label = useLabel();
const cycle = useCycle();
const moduleInfo = useModule();
const projectState = useProjectState();
const issueKanBanView = useKanbanView();
// derived values
const isDragDisabled = !issueKanBanView?.getCanUserDragDrop(group_by, sub_group_by);
const list = getGroupByColumns(
group_by as GroupByColumnTypes,
project,
cycle,
moduleInfo,
label,
projectState,
member,
true,
isWorkspaceLevel(storeType)
);
const list = getGroupByColumns({
groupBy: group_by as GroupByColumnTypes,
includeNone: true,
isWorkspaceLevel: isWorkspaceLevel(storeType),
});
if (!list) return null;

View File

@@ -15,7 +15,6 @@ import {
// UI
import { Row } from "@plane/ui";
// hooks
import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "@/hooks/store";
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
// components
import { TRenderQuickActions } from "../list/list-view-types";
@@ -262,38 +261,19 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
quickAddCallback,
scrollableContainerRef,
} = props;
// store hooks
const storeType = useIssueStoreType();
const member = useMember();
const project = useProject();
const label = useLabel();
const cycle = useCycle();
const projectModule = useModule();
const projectState = useProjectState();
const groupByList = getGroupByColumns(
group_by as GroupByColumnTypes,
project,
cycle,
projectModule,
label,
projectState,
member,
true,
isWorkspaceLevel(storeType)
);
const subGroupByList = getGroupByColumns(
sub_group_by as GroupByColumnTypes,
project,
cycle,
projectModule,
label,
projectState,
member,
true,
isWorkspaceLevel(storeType)
);
// derived values
const groupByList = getGroupByColumns({
groupBy: group_by as GroupByColumnTypes,
includeNone: true,
isWorkspaceLevel: isWorkspaceLevel(storeType),
});
const subGroupByList = getGroupByColumns({
groupBy: sub_group_by as GroupByColumnTypes,
includeNone: true,
isWorkspaceLevel: isWorkspaceLevel(storeType),
});
if (!groupByList || !subGroupByList) return null;

View File

@@ -18,9 +18,7 @@ import {
} from "@plane/types";
// components
import { MultipleSelectGroup } from "@/components/core";
// hooks
import { useCycle, useLabel, useMember, useModule, useProject, useProjectState } from "@/hooks/store";
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
// plane web components
import { IssueBulkOperationsRoot } from "@/plane-web/components/issues";
@@ -75,29 +73,16 @@ export const List: React.FC<IList> = observer((props) => {
} = props;
const storeType = useIssueStoreType();
// store hooks
const member = useMember();
const project = useProject();
const label = useLabel();
const projectState = useProjectState();
const cycle = useCycle();
const projectModule = useModule();
// plane web hooks
const isBulkOperationsEnabled = useBulkOperationStatus();
const containerRef = useRef<HTMLDivElement | null>(null);
const groups = getGroupByColumns(
group_by as GroupByColumnTypes,
project,
cycle,
projectModule,
label,
projectState,
member,
true,
isWorkspaceLevel(storeType)
);
const groups = getGroupByColumns({
groupBy: group_by as GroupByColumnTypes,
includeNone: true,
isWorkspaceLevel: isWorkspaceLevel(storeType),
});
// Enable Auto Scroll for Main Kanban
useEffect(() => {

View File

@@ -36,13 +36,8 @@ import { STATE_GROUPS } from "@/constants/state";
import { renderFormattedDate } from "@/helpers/date-time.helper";
import { getFileURL } from "@/helpers/file.helper";
// store
import { ICycleStore } from "@/store/cycle.store";
import { store } from "@/lib/store-context";
import { ISSUE_FILTER_DEFAULT_DATA } from "@/store/issue/helpers/base-issues.store";
import { ILabelStore } from "@/store/label.store";
import { IMemberRootStore } from "@/store/member";
import { IModuleStore } from "@/store/module.store";
import { IProjectStore } from "@/store/project/project.store";
import { IStateStore } from "@/store/state.store";
export const HIGHLIGHT_CLASS = "highlight";
export const HIGHLIGHT_WITH_LINE = "highlight-with-line";
@@ -65,51 +60,61 @@ export type IssueUpdates = {
export const isWorkspaceLevel = (type: EIssuesStoreType) =>
[EIssuesStoreType.PROFILE, EIssuesStoreType.GLOBAL].includes(type) ? true : false;
export const getGroupByColumns = (
groupBy: GroupByColumnTypes | null,
project: IProjectStore,
cycle: ICycleStore,
module: IModuleStore,
label: ILabelStore,
projectState: IStateStore,
member: IMemberRootStore,
includeNone?: boolean,
isWorkspaceLevel?: boolean
): IGroupByColumn[] | undefined => {
switch (groupBy) {
case "project":
return getProjectColumns(project);
case "cycle":
return getCycleColumns(project, cycle);
case "module":
return getModuleColumns(project, module);
case "state":
return getStateColumns(projectState);
case "state_detail.group":
return getStateGroupColumns();
case "priority":
return getPriorityColumns();
case "labels":
return getLabelsColumns(label, isWorkspaceLevel) as any;
case "assignees":
return getAssigneeColumns(member) as any;
case "created_by":
return getCreatedByColumns(member) as any;
default:
if (includeNone) return [{ id: `All Issues`, name: `All Issues`, payload: {}, icon: undefined }];
}
type TGetGroupByColumns = {
groupBy: GroupByColumnTypes | null;
includeNone: boolean;
isWorkspaceLevel: boolean;
};
const getProjectColumns = (project: IProjectStore): IGroupByColumn[] | undefined => {
const { workspaceProjectIds: projectIds, projectMap } = project;
// NOTE: Type of groupBy is different compared to what's being passed from the components.
// We are using `as` to typecast it to the expected type.
// It can break the includeNone logic if not handled properly.
export const getGroupByColumns = ({
groupBy,
includeNone,
isWorkspaceLevel,
}: TGetGroupByColumns): IGroupByColumn[] | undefined => {
// If no groupBy is specified and includeNone is true, return "All Issues" group
if (!groupBy && includeNone) {
return [
{
id: "All Issues",
name: "All Issues",
payload: {},
icon: undefined,
},
];
}
// Return undefined if no valid groupBy
if (!groupBy) return undefined;
// Map of group by options to their corresponding column getter functions
const groupByColumnMap: Record<GroupByColumnTypes, () => IGroupByColumn[] | undefined> = {
project: getProjectColumns,
cycle: getCycleColumns,
module: getModuleColumns,
state: getStateColumns,
"state_detail.group": getStateGroupColumns,
priority: getPriorityColumns,
labels: () => getLabelsColumns(isWorkspaceLevel),
assignees: getAssigneeColumns,
created_by: getCreatedByColumns,
};
// Get and return the columns for the specified group by option
return groupByColumnMap[groupBy]?.();
};
const getProjectColumns = (): IGroupByColumn[] | undefined => {
const { joinedProjectIds: projectIds, projectMap } = store.projectRoot.project;
// Return undefined if no project ids
if (!projectIds) return;
// Map project ids to project columns
return projectIds
.filter((projectId) => !!projectMap[projectId])
.map((projectId) => {
.map((projectId: string) => {
const project = projectMap[projectId];
if (!project) return;
return {
id: project.id,
name: project.name,
@@ -120,78 +125,71 @@ const getProjectColumns = (project: IProjectStore): IGroupByColumn[] | undefined
),
payload: { project_id: project.id },
};
}) as any;
})
.filter((column) => column !== undefined) as IGroupByColumn[];
};
const getCycleColumns = (projectStore: IProjectStore, cycleStore: ICycleStore): IGroupByColumn[] | undefined => {
const { currentProjectDetails } = projectStore;
const { getProjectCycleIds, getCycleById } = cycleStore;
const getCycleColumns = (): IGroupByColumn[] | undefined => {
const { currentProjectDetails } = store.projectRoot.project;
// Check for the current project details
if (!currentProjectDetails || !currentProjectDetails?.id) return;
const cycleIds = currentProjectDetails?.id ? getProjectCycleIds(currentProjectDetails?.id) : undefined;
if (!cycleIds) return;
const cycles = [];
cycleIds.map((cycleId) => {
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,
});
}
const { getProjectCycleDetails } = store.cycle;
// Get the cycle details for the current project
const cycleDetails = currentProjectDetails?.id ? getProjectCycleDetails(currentProjectDetails?.id) : undefined;
// Map the cycle details to the group by columns
const cycles: IGroupByColumn[] = [];
cycleDetails?.map((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,
});
});
cycles.push({
id: "None",
name: "None",
icon: <ContrastIcon className="h-3.5 w-3.5" />,
payload: {},
});
return cycles as any;
return cycles;
};
const getModuleColumns = (projectStore: IProjectStore, moduleStore: IModuleStore): IGroupByColumn[] | undefined => {
const { currentProjectDetails } = projectStore;
const { getProjectModuleIds, getModuleById } = moduleStore;
const getModuleColumns = (): IGroupByColumn[] | undefined => {
// get current project details
const { currentProjectDetails } = store.projectRoot.project;
if (!currentProjectDetails || !currentProjectDetails?.id) return;
const moduleIds = currentProjectDetails?.id ? getProjectModuleIds(currentProjectDetails?.id) : undefined;
if (!moduleIds) return;
const modules = [];
moduleIds.map((moduleId) => {
const moduleInfo = getModuleById(moduleId);
if (moduleInfo)
modules.push({
id: moduleInfo.id,
name: moduleInfo.name,
icon: <DiceIcon className="h-3.5 w-3.5" />,
payload: { module_ids: [moduleInfo.id] },
});
}) as any;
// get project module ids and module details
const { getProjectModuleDetails } = store.module;
// get module details
const moduleDetails = currentProjectDetails?.id ? getProjectModuleDetails(currentProjectDetails?.id) : undefined;
// map module details to group by columns
const modules: IGroupByColumn[] = [];
moduleDetails?.map((module) => {
modules.push({
id: module.id,
name: module.name,
icon: <DiceIcon className="h-3.5 w-3.5" />,
payload: { module_ids: [module.id] },
});
});
modules.push({
id: "None",
name: "None",
icon: <DiceIcon className="h-3.5 w-3.5" />,
payload: {},
});
return modules as any;
return modules;
};
const getStateColumns = (projectState: IStateStore): IGroupByColumn[] | undefined => {
const { projectStates } = projectState;
const getStateColumns = (): IGroupByColumn[] | undefined => {
const { projectStates } = store.state;
if (!projectStates) return;
// map project states to group by columns
return projectStates.map((state) => ({
id: state.id,
name: state.name,
@@ -201,12 +199,12 @@ const getStateColumns = (projectState: IStateStore): IGroupByColumn[] | undefine
</div>
),
payload: { state_id: state.id },
})) as any;
}));
};
const getStateGroupColumns = () => {
const getStateGroupColumns = (): IGroupByColumn[] => {
const stateGroups = STATE_GROUPS;
// map state groups to group by columns
return Object.values(stateGroups).map((stateGroup) => ({
id: stateGroup.key,
name: stateGroup.label,
@@ -219,9 +217,9 @@ const getStateGroupColumns = () => {
}));
};
const getPriorityColumns = () => {
const getPriorityColumns = (): IGroupByColumn[] => {
const priorities = ISSUE_PRIORITIES;
// map priorities to group by columns
return priorities.map((priority) => ({
id: priority.key,
name: priority.title,
@@ -230,14 +228,14 @@ const getPriorityColumns = () => {
}));
};
const getLabelsColumns = (label: ILabelStore, isWorkspaceLevel: boolean = false) => {
const { workspaceLabels, projectLabels } = label;
const getLabelsColumns = (isWorkspaceLevel: boolean = false): IGroupByColumn[] => {
const { workspaceLabels, projectLabels } = store.label;
// map labels to group by columns
const labels = [
...(isWorkspaceLevel ? workspaceLabels || [] : projectLabels || []),
{ id: "None", name: "None", color: "#666" },
];
// map labels to group by columns
return labels.map((label) => ({
id: label.id,
name: label.name,
@@ -248,15 +246,14 @@ const getLabelsColumns = (label: ILabelStore, isWorkspaceLevel: boolean = false)
}));
};
const getAssigneeColumns = (member: IMemberRootStore) => {
const getAssigneeColumns = (): IGroupByColumn[] | undefined => {
const {
project: { projectMemberIds },
getUserDetails,
} = member;
} = store.memberRoot;
if (!projectMemberIds) return;
const assigneeColumns: any = projectMemberIds.map((memberId) => {
// Map project member ids to group by assignee columns
const assigneeColumns: IGroupByColumn[] = projectMemberIds.map((memberId) => {
const member = getUserDetails(memberId);
return {
id: memberId,
@@ -265,20 +262,17 @@ const getAssigneeColumns = (member: IMemberRootStore) => {
payload: { assignee_ids: [memberId] },
};
});
assigneeColumns.push({ id: "None", name: "None", icon: <Avatar size="md" />, payload: {} });
return assigneeColumns;
};
const getCreatedByColumns = (member: IMemberRootStore) => {
const getCreatedByColumns = (): IGroupByColumn[] | undefined => {
const {
project: { projectMemberIds },
getUserDetails,
} = member;
} = store.memberRoot;
if (!projectMemberIds) return;
// Map project member ids to group by created by columns
return projectMemberIds.map((memberId) => {
const member = getUserDetails(memberId);
return {

View File

@@ -1,2 +1,3 @@
export * from "./notification-app-sidebar-option";
export * from "./sidebar";
export * from "./root";

View File

@@ -11,7 +11,6 @@ import {
NotificationEmptyState,
NotificationSidebarHeader,
AppliedFilters,
NotificationCardListRoot,
} from "@/components/workspace-notifications";
// constants
import { NOTIFICATION_TABS, TNotificationTab } from "@/constants/notification";
@@ -21,6 +20,8 @@ import { getNumberCount } from "@/helpers/string.helper";
// hooks
import { useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
import { NotificationCardListRoot } from "@/plane-web/components/workspace-notifications";
export const NotificationsSidebarRoot: FC = observer(() => {
const { workspaceSlug } = useParams();
// hooks

View File

@@ -1,3 +1,2 @@
export * from "./root";
export * from "./item";
export * from "./options";

View File

@@ -27,6 +27,7 @@ import { CustomMenu, Tooltip, DropIndicator, FavoriteFolderIcon, DragHandle } fr
import { cn } from "@/helpers/common.helper";
// hooks
import { useAppTheme } from "@/hooks/store";
import { useFavorite } from "@/hooks/store/use-favorite";
import { usePlatformOS } from "@/hooks/use-platform-os";
// constants
import { FavoriteRoot } from "./favorite-items";
@@ -45,7 +46,7 @@ export const FavoriteFolder: React.FC<Props> = (props) => {
const { favorite, handleRemoveFromFavorites, isLastChild, handleDrop } = props;
// store hooks
const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme();
const { getGroupedFavorites } = useFavorite();
const { isMobile } = usePlatformOS();
const { workspaceSlug } = useParams();
// states
@@ -58,6 +59,12 @@ export const FavoriteFolder: React.FC<Props> = (props) => {
const actionSectionRef = useRef<HTMLDivElement | null>(null);
const elementRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (favorite.children === undefined && workspaceSlug) {
getGroupedFavorites(workspaceSlug.toString(), favorite.id);
}
}, [favorite.id, favorite.children, workspaceSlug, getGroupedFavorites]);
useEffect(() => {
const element = elementRef.current;
@@ -123,7 +130,7 @@ export const FavoriteFolder: React.FC<Props> = (props) => {
})
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDragging, favorite.id]);
}, [isDragging, favorite.id, isLastChild, favorite.id]);
useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false));

View File

@@ -131,7 +131,7 @@ export const FavoriteRoot: FC<Props> = observer((props) => {
})
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [elementRef?.current, isDragging]);
}, [elementRef?.current, isDragging, isLastChild, favorite.id]);
useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false));

View File

@@ -87,33 +87,27 @@ export const SidebarFavoritesMenu = observer(() => {
const sourceData = source.data as TargetData;
if (!sourceData.id) return;
if (isFolder) {
// handle move to a new parent folder if dropped on a folder
if (parentId && parentId !== sourceData.parentId) {
handleMoveToFolder(sourceData.id, parentId);
handleMoveToFolder(sourceData.id, parentId); /**parent id */
}
//handle remove from folder if dropped outside of the folder
if (parentId && parentId !== sourceData.parentId && sourceData.isChild) {
handleRemoveFromFavoritesFolder(sourceData.id);
}
// handle reordering at root level
if (droppedFavId) {
if (instruction != "make-child") {
handleReorder(sourceData.id, droppedFavId, instruction);
handleReorder(sourceData.id, droppedFavId, instruction); /** sequence */
}
}
} else {
//handling reordering for favorites
if (droppedFavId) {
handleReorder(sourceData.id, droppedFavId, instruction);
handleReorder(sourceData.id, droppedFavId, instruction); /** sequence */
}
}
// handle removal from folder if dropped outside a folder
if (!parentId && sourceData.isChild) {
handleRemoveFromFavoritesFolder(sourceData.id);
}
/**remove if dropped outside and source is a child */
if (!parentId && sourceData.isChild) {
handleRemoveFromFavoritesFolder(sourceData.id); /**parent null */
}
};

View File

@@ -2,7 +2,7 @@ import { useContext } from "react";
// mobx store
import { StoreContext } from "@/lib/store-context";
// types
import { ICommandPaletteStore } from "@/store/command-palette.store";
import { ICommandPaletteStore } from "@/plane-web/store/command-palette.store";
export const useCommandPalette = (): ICommandPaletteStore => {
const context = useContext(StoreContext);

View File

@@ -9,9 +9,8 @@ export interface ModalData {
viewId: string;
}
export interface ICommandPaletteStore {
export interface IBaseCommandPaletteStore {
// observables
isCommandPaletteOpen: boolean;
isShortcutModalOpen: boolean;
isCreateProjectModalOpen: boolean;
@@ -22,6 +21,7 @@ export interface ICommandPaletteStore {
isCreateIssueModalOpen: boolean;
isDeleteIssueModalOpen: boolean;
isBulkDeleteIssueModalOpen: boolean;
createIssueStoreType: TCreateModalStoreTypes;
// computed
isAnyModalOpen: boolean;
// toggle actions
@@ -35,11 +35,9 @@ export interface ICommandPaletteStore {
toggleCreateModuleModal: (value?: boolean) => void;
toggleDeleteIssueModal: (value?: boolean) => void;
toggleBulkDeleteIssueModal: (value?: boolean) => void;
createIssueStoreType: TCreateModalStoreTypes;
}
export class CommandPaletteStore implements ICommandPaletteStore {
export abstract class BaseCommandPaletteStore implements IBaseCommandPaletteStore {
// observables
isCommandPaletteOpen: boolean = false;
isShortcutModalOpen: boolean = false;
@@ -51,7 +49,6 @@ export class CommandPaletteStore implements ICommandPaletteStore {
isDeleteIssueModalOpen: boolean = false;
isBulkDeleteIssueModalOpen: boolean = false;
createPageModal: TCreatePageModal = DEFAULT_CREATE_PAGE_MODAL_DATA;
createIssueStoreType: TCreateModalStoreTypes = EIssuesStoreType.PROJECT;
constructor() {
@@ -67,6 +64,7 @@ export class CommandPaletteStore implements ICommandPaletteStore {
isDeleteIssueModalOpen: observable.ref,
isBulkDeleteIssueModalOpen: observable.ref,
createPageModal: observable,
createIssueStoreType: observable,
// computed
isAnyModalOpen: computed,
// projectPages: computed,
@@ -85,20 +83,20 @@ export class CommandPaletteStore implements ICommandPaletteStore {
}
/**
* Checks whether any modal is open or not.
* Checks whether any modal is open or not in the base command palette.
* @returns boolean
*/
get isAnyModalOpen() {
return Boolean(
this.isCreateIssueModalOpen ||
this.isCreateCycleModalOpen ||
this.isCreateProjectModalOpen ||
this.isCreateModuleModalOpen ||
this.isCreateViewModalOpen ||
this.isShortcutModalOpen ||
this.isBulkDeleteIssueModalOpen ||
this.isDeleteIssueModalOpen ||
this.createPageModal.isOpen
this.isCreateCycleModalOpen ||
this.isCreateProjectModalOpen ||
this.isCreateModuleModalOpen ||
this.isCreateViewModalOpen ||
this.isShortcutModalOpen ||
this.isBulkDeleteIssueModalOpen ||
this.isDeleteIssueModalOpen ||
this.createPageModal.isOpen
);
}

View File

@@ -1,4 +1,4 @@
import { isFuture, isPast, isToday } from "date-fns";
import { isPast, isToday } from "date-fns";
import isEmpty from "lodash/isEmpty";
import set from "lodash/set";
import sortBy from "lodash/sortBy";
@@ -7,16 +7,14 @@ import { computedFn } from "mobx-utils";
// types
import {
ICycle,
CycleDateCheckData,
TCyclePlotType,
TProgressSnapshot,
TCycleEstimateDistribution,
TCycleDistribution,
TCycleEstimateType,
TCycleProgress,
} from "@plane/types";
// helpers
import { orderCycles, shouldFilterCycle, formatActiveCycle } from "@/helpers/cycle.helper";
import { orderCycles, shouldFilterCycle } from "@/helpers/cycle.helper";
import { getDate } from "@/helpers/date-time.helper";
import { DistributionUpdates, updateDistribution } from "@/helpers/distribution-update.helper";
// services
@@ -42,21 +40,18 @@ export interface ICycleStore {
// computed
currentProjectCycleIds: string[] | null;
currentProjectCompletedCycleIds: string[] | null;
currentProjectUpcomingCycleIds: string[] | null;
currentProjectIncompleteCycleIds: string[] | null;
currentProjectDraftCycleIds: string[] | null;
currentProjectActiveCycleId: string | null;
currentProjectArchivedCycleIds: string[] | null;
currentProjectActiveCycle: ICycle | null;
// computed actions
getActiveCycleProgress: (cycleId?: string) => { cycle: ICycle; isBurnDown: boolean; isTypeIssue: boolean } | null;
getFilteredCycleIds: (projectId: string, sortByManual: boolean) => string[] | null;
getFilteredCompletedCycleIds: (projectId: string) => string[] | null;
getFilteredArchivedCycleIds: (projectId: string) => string[] | null;
getCycleById: (cycleId: string) => ICycle | null;
getCycleNameById: (cycleId: string) => string | undefined;
getActiveCycleById: (cycleId: string) => ICycle | null;
getProjectCycleDetails: (projectId: string) => ICycle[] | null;
getProjectCycleIds: (projectId: string) => string[] | null;
getPlotTypeByCycleId: (cycleId: string) => TCyclePlotType;
getEstimateTypeByCycleId: (cycleId: string) => TCycleEstimateType;
@@ -64,7 +59,6 @@ export interface ICycleStore {
// actions
updateCycleDistribution: (distributionUpdates: DistributionUpdates, cycleId: string) => void;
validateDate: (workspaceSlug: string, projectId: string, payload: CycleDateCheckData) => Promise<any>;
setPlotType: (cycleId: string, plotType: TCyclePlotType) => void;
setEstimateType: (cycleId: string, estimateType: TCycleEstimateType) => void;
// fetch
@@ -130,15 +124,12 @@ export class CycleStore implements ICycleStore {
// computed
currentProjectCycleIds: computed,
currentProjectCompletedCycleIds: computed,
currentProjectUpcomingCycleIds: computed,
currentProjectIncompleteCycleIds: computed,
currentProjectDraftCycleIds: computed,
currentProjectActiveCycleId: computed,
currentProjectArchivedCycleIds: computed,
currentProjectActiveCycle: computed,
// actions
setPlotType: action,
setEstimateType: action,
fetchWorkspaceCycles: action,
fetchAllCycles: action,
@@ -195,22 +186,6 @@ export class CycleStore implements ICycleStore {
return completedCycleIds;
}
/**
* returns all upcoming cycle ids for a project
*/
get currentProjectUpcomingCycleIds() {
const projectId = this.rootStore.router.projectId;
if (!projectId || !this.fetchedMap[projectId]) return null;
let upcomingCycles = Object.values(this.cycleMap ?? {}).filter((c) => {
const startDate = getDate(c.start_date);
const isStartDateUpcoming = startDate && isFuture(startDate);
return c.project_id === projectId && isStartDateUpcoming && !c?.archived_at;
});
upcomingCycles = sortBy(upcomingCycles, [(c) => c.sort_order]);
const upcomingCycleIds = upcomingCycles.map((c) => c.id);
return upcomingCycleIds;
}
/**
* returns all incomplete cycle ids for a project
*/
@@ -227,20 +202,6 @@ export class CycleStore implements ICycleStore {
return incompleteCycleIds;
}
/**
* returns all draft cycle ids for a project
*/
get currentProjectDraftCycleIds() {
const projectId = this.rootStore.router.projectId;
if (!projectId || !this.fetchedMap[projectId]) return null;
let draftCycles = Object.values(this.cycleMap ?? {}).filter(
(c) => c.project_id === projectId && !c.start_date && !c.end_date && !c?.archived_at
);
draftCycles = sortBy(draftCycles, [(c) => c.sort_order]);
const draftCycleIds = draftCycles.map((c) => c.id);
return draftCycleIds;
}
/**
* returns active cycle id for a project
*/
@@ -285,19 +246,6 @@ export class CycleStore implements ICycleStore {
} else return false;
});
/**
* returns active cycle progress for a project
*/
getActiveCycleProgress = computedFn((cycleId?: string) => {
const cycle = cycleId ? this.cycleMap[cycleId] : this.currentProjectActiveCycle;
if (!cycle) return null;
const isTypeIssue = this.getEstimateTypeByCycleId(cycle.id) === "issues";
const isBurnDown = this.getPlotTypeByCycleId(cycle.id) === "burndown";
return { cycle, isTypeIssue, isBurnDown };
});
/**
* @description returns filtered cycle ids based on display filters and filters
* @param {TCycleDisplayFilters} displayFilters
@@ -379,37 +327,28 @@ export class CycleStore implements ICycleStore {
getCycleNameById = computedFn((cycleId: string): string => this.cycleMap?.[cycleId]?.name);
/**
* @description returns active cycle details by cycle id
* @param cycleId
* @returns
* @description returns list of cycle details of the project id passed as argument
* @param projectId
*/
getActiveCycleById = computedFn((cycleId: string): ICycle | null =>
this.activeCycleIdMap?.[cycleId] && this.cycleMap?.[cycleId] ? this.cycleMap?.[cycleId] : null
);
getProjectCycleDetails = computedFn((projectId: string): ICycle[] | null => {
if (!this.fetchedMap[projectId]) return null;
let cycles = Object.values(this.cycleMap ?? {}).filter((c) => c.project_id === projectId && !c?.archived_at);
cycles = sortBy(cycles, [(c) => c.sort_order]);
return cycles || null;
});
/**
* @description returns list of cycle ids of the project id passed as argument
* @param projectId
*/
getProjectCycleIds = computedFn((projectId: string): string[] | null => {
if (!this.fetchedMap[projectId]) return null;
let cycles = Object.values(this.cycleMap ?? {}).filter((c) => c.project_id === projectId && !c?.archived_at);
cycles = sortBy(cycles, [(c) => c.sort_order]);
const cycles = this.getProjectCycleDetails(projectId);
if (!cycles) return null;
const cycleIds = cycles.map((c) => c.id);
return cycleIds || null;
});
/**
* @description validates cycle dates
* @param workspaceSlug
* @param projectId
* @param payload
* @returns
*/
validateDate = async (workspaceSlug: string, projectId: string, payload: CycleDateCheckData) =>
await this.cycleService.cycleDateCheck(workspaceSlug, projectId, payload);
/**
* @description gets the plot type for the cycle store
* @param {TCyclePlotType} plotType
@@ -473,14 +412,16 @@ export class CycleStore implements ICycleStore {
runInAction(() => {
response.forEach((cycle) => {
set(this.cycleMap, [cycle.id], cycle);
cycle.status?.toLowerCase() === "current" && set(this.activeCycleIdMap, [cycle.id], true);
if (cycle.status?.toLowerCase() === "current") {
set(this.activeCycleIdMap, [cycle.id], true);
}
});
set(this.fetchedMap, projectId, true);
this.loader = false;
});
return response;
});
} catch (error) {
} catch {
this.loader = false;
return undefined;
}
@@ -553,6 +494,7 @@ export class CycleStore implements ICycleStore {
* @param cycleId
* @returns
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
fetchActiveCycleProgressPro = action(async (workspaceSlug: string, projectId: string, cycleId: string) => {});
/**

View File

@@ -174,23 +174,14 @@ export class FavoriteStore implements IFavoriteStore {
* @returns Promise<void>
*/
moveFavoriteToFolder = async (workspaceSlug: string, favoriteId: string, data: Partial<IFavorite>) => {
const oldParent = this.favoriteMap[favoriteId].parent;
try {
await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, data);
runInAction(() => {
// add parent of the favorite
set(this.favoriteMap, [favoriteId, "parent"], data.parent);
});
await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, data);
} catch (error) {
console.error("Failed to move favorite from favorite store");
// revert the changes
runInAction(() => {
if (!data.parent) return;
// revert the parent
set(this.favoriteMap, [favoriteId, "parent"], oldParent);
});
console.error("Failed to move favorite to folder", error);
throw error;
}
};
@@ -201,7 +192,6 @@ export class FavoriteStore implements IFavoriteStore {
destinationId: string,
edge: string | undefined
) => {
const initialSequence = this.favoriteMap[favoriteId].sequence;
try {
let resultSequence = 10000;
if (edge) {
@@ -221,35 +211,27 @@ export class FavoriteStore implements IFavoriteStore {
}
}
}
await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, { sequence: resultSequence });
runInAction(() => {
set(this.favoriteMap, [favoriteId, "sequence"], resultSequence);
});
await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, { sequence: resultSequence });
} catch (error) {
console.error("Failed to move favorite folder");
runInAction(() => {
set(this.favoriteMap, [favoriteId, "sequence"], initialSequence);
throw error;
});
throw error;
}
};
removeFromFavoriteFolder = async (workspaceSlug: string, favoriteId: string) => {
const parent = this.favoriteMap[favoriteId].parent;
try {
await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, { parent: null });
runInAction(() => {
//remove parent
set(this.favoriteMap, [favoriteId, "parent"], null);
});
await this.favoriteService.updateFavorite(workspaceSlug, favoriteId, { parent: null });
} catch (error) {
console.error("Failed to move favorite");
runInAction(() => {
set(this.favoriteMap, [favoriteId, "parent"], parent);
throw error;
});
throw error;
}
};
@@ -384,7 +366,7 @@ export class FavoriteStore implements IFavoriteStore {
set(this.favoriteMap, [favorite.id], favorite);
this.favoriteIds.push(favorite.id);
this.favoriteIds = uniqBy(this.favoriteIds, (id) => id);
favorite.entity_identifier && set(this.entityMap, [favorite.entity_identifier], favorite);
if (favorite.entity_identifier) set(this.entityMap, [favorite.entity_identifier], favorite);
});
});

View File

@@ -98,7 +98,7 @@ export class InboxIssueStore implements IInboxIssueStore {
// If issue accepted sync issue to local db
if (status === EInboxIssueStatus.ACCEPTED) {
addIssueToPersistanceLayer(inboxIssue.issue);
addIssueToPersistanceLayer({ ...this.issue, ...inboxIssue.issue });
}
} catch {
runInAction(() => set(this, "status", previousData.status));

View File

@@ -352,7 +352,7 @@ export class WorkspaceDraftIssues implements IWorkspaceDraftIssues {
}
// sync issue to local db
addIssueToPersistanceLayer(response);
addIssueToPersistanceLayer({ ...payload, ...response });
// Update draft issue count in workspaceUserInfo
this.updateWorkspaceUserDraftIssueCount(workspaceSlug, -1);

View File

@@ -32,6 +32,7 @@ export interface IModuleStore {
getFilteredArchivedModuleIds: (projectId: string) => string[] | null;
getModuleById: (moduleId: string) => IModule | null;
getModuleNameById: (moduleId: string) => string;
getProjectModuleDetails: (projectId: string) => IModule[] | null;
getProjectModuleIds: (projectId: string) => string[] | null;
getPlotTypeByModuleId: (moduleId: string) => TModulePlotType;
// actions
@@ -210,15 +211,24 @@ export class ModulesStore implements IModuleStore {
*/
getModuleNameById = computedFn((moduleId: string) => this.moduleMap?.[moduleId]?.name);
/**
* @description returns list of module details of the project id passed as argument
* @param projectId
*/
getProjectModuleDetails = computedFn((projectId: string) => {
if (!this.fetchedMap[projectId]) return null;
let projectModules = Object.values(this.moduleMap).filter((m) => m.project_id === projectId && !m.archived_at);
projectModules = sortBy(projectModules, [(m) => m.sort_order]);
return projectModules;
});
/**
* @description returns list of module ids of the project id passed as argument
* @param projectId
*/
getProjectModuleIds = computedFn((projectId: string) => {
if (!this.fetchedMap[projectId]) return null;
let projectModules = Object.values(this.moduleMap).filter((m) => m.project_id === projectId && !m.archived_at);
projectModules = sortBy(projectModules, [(m) => m.sort_order]);
const projectModules = this.getProjectModuleDetails(projectId);
if (!projectModules) return null;
const projectModuleIds = projectModules.map((m) => m.id);
return projectModuleIds;
});
@@ -282,7 +292,7 @@ export class ModulesStore implements IModuleStore {
});
return response;
});
} catch (error) {
} catch {
this.loader = false;
return undefined;
}
@@ -308,7 +318,7 @@ export class ModulesStore implements IModuleStore {
});
return projectModules;
});
} catch (error) {
} catch {
this.loader = false;
return undefined;
}

View File

@@ -26,7 +26,7 @@ export interface INotification extends TNotification {
export class Notification implements INotification {
// observables
id: string | undefined = undefined;
id: string;
title: string | undefined = undefined;
data: TNotificationData | undefined = undefined;
entity_identifier: string | undefined = undefined;
@@ -54,6 +54,7 @@ export class Notification implements INotification {
private store: CoreRootStore,
private notification: TNotification
) {
this.id = this.notification.id;
makeObservable(this, {
// observables
id: observable.ref,
@@ -90,7 +91,6 @@ export class Notification implements INotification {
snoozeNotification: action,
unSnoozeNotification: action,
});
this.id = this.notification.id;
this.title = this.notification.title;
this.data = this.notification.data;
this.entity_identifier = this.notification.entity_identifier;
@@ -169,8 +169,6 @@ export class Notification implements INotification {
workspaceSlug: string,
payload: Partial<TNotification>
): Promise<TNotification | undefined> => {
if (!this.id) return undefined;
try {
const notification = await workspaceNotificationService.updateNotificationById(workspaceSlug, this.id, payload);
if (notification) {
@@ -188,8 +186,6 @@ export class Notification implements INotification {
* @returns { TNotification | undefined }
*/
markNotificationAsRead = async (workspaceSlug: string): Promise<TNotification | undefined> => {
if (!this.id) return undefined;
const currentNotificationReadAt = this.read_at;
try {
const payload: Partial<TNotification> = {
@@ -215,8 +211,6 @@ export class Notification implements INotification {
* @returns { TNotification | undefined }
*/
markNotificationAsUnRead = async (workspaceSlug: string): Promise<TNotification | undefined> => {
if (!this.id) return undefined;
const currentNotificationReadAt = this.read_at;
try {
const payload: Partial<TNotification> = {
@@ -242,8 +236,6 @@ export class Notification implements INotification {
* @returns { TNotification | undefined }
*/
archiveNotification = async (workspaceSlug: string): Promise<TNotification | undefined> => {
if (!this.id) return undefined;
const currentNotificationArchivedAt = this.archived_at;
try {
const payload: Partial<TNotification> = {
@@ -267,8 +259,6 @@ export class Notification implements INotification {
* @returns { TNotification | undefined }
*/
unArchiveNotification = async (workspaceSlug: string): Promise<TNotification | undefined> => {
if (!this.id) return undefined;
const currentNotificationArchivedAt = this.archived_at;
try {
const payload: Partial<TNotification> = {
@@ -293,8 +283,6 @@ export class Notification implements INotification {
* @returns { TNotification | undefined }
*/
snoozeNotification = async (workspaceSlug: string, snoozeTill: Date): Promise<TNotification | undefined> => {
if (!this.id) return undefined;
const currentNotificationSnoozeTill = this.snoozed_till;
try {
const payload: Partial<TNotification> = {
@@ -315,8 +303,6 @@ export class Notification implements INotification {
* @returns { TNotification | undefined }
*/
unSnoozeNotification = async (workspaceSlug: string): Promise<TNotification | undefined> => {
if (!this.id) return undefined;
const currentNotificationSnoozeTill = this.snoozed_till;
try {
const payload: Partial<TNotification> = {

View File

@@ -1,9 +1,9 @@
import { enableStaticRendering } from "mobx-react";
// plane web store
import { CommandPaletteStore, ICommandPaletteStore } from "@/plane-web/store/command-palette.store";
import { RootStore } from "@/plane-web/store/root.store";
import { IStateStore, StateStore } from "@/plane-web/store/state.store";
// stores
import { CommandPaletteStore, ICommandPaletteStore } from "./command-palette.store";
import { CycleStore, ICycleStore } from "./cycle.store";
import { CycleFilterStore, ICycleFilterStore } from "./cycle_filter.store";
import { DashboardStore, IDashboardStore } from "./dashboard.store";

View File

@@ -0,0 +1 @@
export * from "ce/components/command-palette/modals";

View File

@@ -0,0 +1 @@
export * from "ce/helpers/command-palette";

View File

@@ -0,0 +1 @@
export * from "ce/store/command-palette.store";

View File

@@ -0,0 +1 @@
export * from "ce/helpers/command-palette";

View File

@@ -1,6 +1,6 @@
{
"name": "web",
"version": "0.24.0",
"version": "0.24.1",
"private": true,
"scripts": {
"dev": "turbo run develop",