mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'develop' of github.com:makeplane/plane into preview
This commit is contained in:
commit
c950e3d3f7
@ -1,4 +1,4 @@
|
||||
{
|
||||
"name": "plane-api",
|
||||
"version": "0.15.1"
|
||||
"version": "0.16.0"
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ from plane.app.serializers import (
|
||||
IssueSerializer,
|
||||
InboxSerializer,
|
||||
InboxIssueSerializer,
|
||||
IssueDetailSerializer,
|
||||
)
|
||||
from plane.utils.issue_filters import issue_filters
|
||||
from plane.bgtasks.issue_activites_task import issue_activity
|
||||
@ -426,11 +427,10 @@ class InboxIssueViewSet(BaseViewSet):
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
if issue is None:
|
||||
return Response({"error": "Requested object was not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
serializer = IssueSerializer(issue)
|
||||
serializer = IssueDetailSerializer(issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def destroy(self, request, slug, project_id, inbox_id, issue_id):
|
||||
|
@ -77,6 +77,12 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||
]
|
||||
|
||||
def get_queryset(self):
|
||||
sort_order = ProjectMember.objects.filter(
|
||||
member=self.request.user,
|
||||
project_id=OuterRef("pk"),
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
is_active=True,
|
||||
).values("sort_order")
|
||||
return self.filter_queryset(
|
||||
super()
|
||||
.get_queryset()
|
||||
@ -147,6 +153,7 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||
)
|
||||
)
|
||||
)
|
||||
.annotate(sort_order=Subquery(sort_order))
|
||||
.prefetch_related(
|
||||
Prefetch(
|
||||
"project_projectmember",
|
||||
@ -166,16 +173,8 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||
for field in request.GET.get("fields", "").split(",")
|
||||
if field
|
||||
]
|
||||
|
||||
sort_order_query = ProjectMember.objects.filter(
|
||||
member=request.user,
|
||||
project_id=OuterRef("pk"),
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
is_active=True,
|
||||
).values("sort_order")
|
||||
projects = (
|
||||
self.get_queryset()
|
||||
.annotate(sort_order=Subquery(sort_order_query))
|
||||
.order_by("sort_order", "name")
|
||||
)
|
||||
if request.GET.get("per_page", False) and request.GET.get(
|
||||
@ -204,7 +203,7 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
|
||||
serializer.save()
|
||||
|
||||
# Add the user as Administrator to the project
|
||||
project_member = ProjectMember.objects.create(
|
||||
_ = ProjectMember.objects.create(
|
||||
project_id=serializer.data["id"],
|
||||
member=request.user,
|
||||
role=20,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"repository": "https://github.com/makeplane/plane.git",
|
||||
"version": "0.15.1",
|
||||
"version": "0.16.0",
|
||||
"license": "AGPL-3.0",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/editor-core",
|
||||
"version": "0.15.1",
|
||||
"version": "0.16.0",
|
||||
"description": "Core Editor that powers Plane",
|
||||
"private": true,
|
||||
"main": "./dist/index.mjs",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/document-editor",
|
||||
"version": "0.15.1",
|
||||
"version": "0.16.0",
|
||||
"description": "Package that powers Plane's Pages Editor",
|
||||
"main": "./dist/index.mjs",
|
||||
"module": "./dist/index.mjs",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/editor-extensions",
|
||||
"version": "0.15.1",
|
||||
"version": "0.16.0",
|
||||
"description": "Package that powers Plane's Editor with extensions",
|
||||
"private": true,
|
||||
"main": "./dist/index.mjs",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/lite-text-editor",
|
||||
"version": "0.15.1",
|
||||
"version": "0.16.0",
|
||||
"description": "Package that powers Plane's Comment Editor",
|
||||
"private": true,
|
||||
"main": "./dist/index.mjs",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/rich-text-editor",
|
||||
"version": "0.15.1",
|
||||
"version": "0.16.0",
|
||||
"description": "Rich Text Editor that powers Plane",
|
||||
"private": true,
|
||||
"main": "./dist/index.mjs",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "eslint-config-custom",
|
||||
"private": true,
|
||||
"version": "0.15.1",
|
||||
"version": "0.16.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tailwind-config-custom",
|
||||
"version": "0.15.1",
|
||||
"version": "0.16.0",
|
||||
"description": "common tailwind configuration across monorepo",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tsconfig",
|
||||
"version": "0.15.1",
|
||||
"version": "0.16.0",
|
||||
"private": true,
|
||||
"files": [
|
||||
"base.json",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@plane/types",
|
||||
"version": "0.15.1",
|
||||
"version": "0.16.0",
|
||||
"private": true,
|
||||
"main": "./src/index.d.ts"
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
"name": "@plane/ui",
|
||||
"description": "UI components shared across multiple apps internally",
|
||||
"private": true,
|
||||
"version": "0.15.1",
|
||||
"version": "0.16.0",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "space",
|
||||
"version": "0.15.1",
|
||||
"version": "0.16.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "turbo run develop",
|
||||
|
@ -148,6 +148,13 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
|
||||
toggleDropdown();
|
||||
};
|
||||
|
||||
const searchInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (query !== "" && e.key === "Escape") {
|
||||
e.stopPropagation();
|
||||
setQuery("");
|
||||
}
|
||||
};
|
||||
|
||||
useOutsideClickDetector(dropdownRef, handleClose);
|
||||
|
||||
useEffect(() => {
|
||||
@ -231,6 +238,7 @@ export const CycleDropdown: React.FC<Props> = observer((props) => {
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search"
|
||||
displayValue={(assigned: any) => assigned?.name}
|
||||
onKeyDown={searchInputKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
|
||||
|
@ -137,6 +137,13 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
|
||||
toggleDropdown();
|
||||
};
|
||||
|
||||
const searchInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (query !== "" && e.key === "Escape") {
|
||||
e.stopPropagation();
|
||||
setQuery("");
|
||||
}
|
||||
};
|
||||
|
||||
useOutsideClickDetector(dropdownRef, handleClose);
|
||||
|
||||
useEffect(() => {
|
||||
@ -217,6 +224,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search"
|
||||
displayValue={(assigned: any) => assigned?.name}
|
||||
onKeyDown={searchInputKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
|
||||
|
@ -130,6 +130,13 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
|
||||
toggleDropdown();
|
||||
};
|
||||
|
||||
const searchInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (query !== "" && e.key === "Escape") {
|
||||
e.stopPropagation();
|
||||
setQuery("");
|
||||
}
|
||||
};
|
||||
|
||||
useOutsideClickDetector(dropdownRef, handleClose);
|
||||
|
||||
useEffect(() => {
|
||||
@ -215,6 +222,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search"
|
||||
displayValue={(assigned: any) => assigned?.name}
|
||||
onKeyDown={searchInputKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
|
||||
|
@ -249,6 +249,13 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
|
||||
toggleDropdown();
|
||||
};
|
||||
|
||||
const searchInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (query !== "" && e.key === "Escape") {
|
||||
e.stopPropagation();
|
||||
setQuery("");
|
||||
}
|
||||
};
|
||||
|
||||
useOutsideClickDetector(dropdownRef, handleClose);
|
||||
|
||||
const comboboxProps: any = {
|
||||
@ -349,6 +356,7 @@ export const ModuleDropdown: React.FC<Props> = observer((props) => {
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search"
|
||||
displayValue={(assigned: any) => assigned?.name}
|
||||
onKeyDown={searchInputKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
|
||||
|
@ -329,6 +329,13 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
|
||||
toggleDropdown();
|
||||
};
|
||||
|
||||
const searchInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (query !== "" && e.key === "Escape") {
|
||||
e.stopPropagation();
|
||||
setQuery("");
|
||||
}
|
||||
};
|
||||
|
||||
useOutsideClickDetector(dropdownRef, handleClose);
|
||||
|
||||
const ButtonToRender = BORDER_BUTTON_VARIANTS.includes(buttonVariant)
|
||||
@ -417,6 +424,7 @@ export const PriorityDropdown: React.FC<Props> = (props) => {
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search"
|
||||
displayValue={(assigned: any) => assigned?.name}
|
||||
onKeyDown={searchInputKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
|
||||
|
@ -119,6 +119,13 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
||||
toggleDropdown();
|
||||
};
|
||||
|
||||
const searchInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (query !== "" && e.key === "Escape") {
|
||||
e.stopPropagation();
|
||||
setQuery("");
|
||||
}
|
||||
};
|
||||
|
||||
useOutsideClickDetector(dropdownRef, handleClose);
|
||||
|
||||
useEffect(() => {
|
||||
@ -205,6 +212,7 @@ export const StateDropdown: React.FC<Props> = observer((props) => {
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search"
|
||||
displayValue={(assigned: any) => assigned?.name}
|
||||
onKeyDown={searchInputKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
|
||||
|
@ -80,6 +80,13 @@ export const IssueLabelSelect: React.FC<IIssueLabelSelect> = observer((props) =>
|
||||
</div>
|
||||
);
|
||||
|
||||
const searchInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (query !== "" && e.key === "Escape") {
|
||||
e.stopPropagation();
|
||||
setQuery("");
|
||||
}
|
||||
};
|
||||
|
||||
if (!issue) return <></>;
|
||||
|
||||
return (
|
||||
@ -118,6 +125,7 @@ export const IssueLabelSelect: React.FC<IIssueLabelSelect> = observer((props) =>
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder="Search"
|
||||
displayValue={(assigned: any) => assigned?.name}
|
||||
onKeyDown={searchInputKeyDown}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -360,7 +360,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="h-full w-full sm:w-1/2 md:w-1/3 space-y-5 overflow-hidden border-l border-custom-border-300 py-5 fixed md:relative bg-custom-sidebar-background-100 right-0 z-[5]"
|
||||
className="h-full w-full min-w-[300px] lg:min-w-80 xl:min-w-96 sm:w-1/2 md:w-1/3 space-y-5 overflow-hidden border-l border-custom-border-300 py-5 fixed md:relative bg-custom-sidebar-background-100 right-0 z-[5]"
|
||||
style={themeStore.issueDetailSidebarCollapsed ? { right: `-${window?.innerWidth || 0}px` } : {}}
|
||||
>
|
||||
<IssueDetailsSidebar
|
||||
|
@ -133,7 +133,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-full w-full overflow-y-auto px-5">
|
||||
<div className="h-full w-full overflow-y-auto px-6">
|
||||
<h5 className="text-sm font-medium mt-6">Properties</h5>
|
||||
{/* TODO: render properties using a common component */}
|
||||
<div className={`mt-3 mb-2 space-y-2.5 ${!is_editable ? "opacity-60" : ""}`}>
|
||||
|
@ -67,7 +67,7 @@ export const IssueSubscription: FC<TIssueSubscription> = observer((props) => {
|
||||
>
|
||||
{loading ? (
|
||||
<span>
|
||||
<span className="hidden sm:block">Loading</span>...
|
||||
<span className="hidden sm:block">Loading...</span>
|
||||
</span>
|
||||
) : isSubscribed ? (
|
||||
<div className="hidden sm:block">Unsubscribe</div>
|
||||
|
@ -37,6 +37,7 @@ type Props = {
|
||||
snapshot?: DraggableStateSnapshot;
|
||||
handleCopyText: () => void;
|
||||
shortContextMenu?: boolean;
|
||||
disableDrag?: boolean;
|
||||
};
|
||||
|
||||
const navigation = (workspaceSlug: string, projectId: string) => [
|
||||
@ -79,7 +80,7 @@ const navigation = (workspaceSlug: string, projectId: string) => [
|
||||
|
||||
export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { projectId, provided, snapshot, handleCopyText, shortContextMenu = false } = props;
|
||||
const { projectId, provided, snapshot, handleCopyText, shortContextMenu = false, disableDrag } = props;
|
||||
// store hooks
|
||||
const { theme: themeStore } = useApplication();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
@ -163,7 +164,7 @@ export const ProjectSidebarListItem: React.FC<Props> = observer((props) => {
|
||||
snapshot?.isDragging ? "opacity-60" : ""
|
||||
} ${isMenuActive ? "!bg-custom-sidebar-background-80" : ""}`}
|
||||
>
|
||||
{provided && (
|
||||
{provided && !disableDrag && (
|
||||
<Tooltip
|
||||
tooltipContent={project.sort_order === null ? "Join the project to rearrange" : "Drag to rearrange"}
|
||||
position="top-right"
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, FC, useRef, useEffect } from "react";
|
||||
import { useState, FC, useRef, useEffect, useCallback } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { DragDropContext, Draggable, DropResult, Droppable } from "@hello-pangea/dnd";
|
||||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
@ -11,9 +11,11 @@ import useToast from "hooks/use-toast";
|
||||
import { CreateProjectModal, ProjectSidebarListItem } from "components/project";
|
||||
// helpers
|
||||
import { copyUrlToClipboard } from "helpers/string.helper";
|
||||
import { orderJoinedProjects } from "helpers/project.helper";
|
||||
import { cn } from "helpers/common.helper";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { IProject } from "@plane/types";
|
||||
|
||||
export const ProjectSidebarList: FC = observer(() => {
|
||||
// states
|
||||
@ -32,9 +34,9 @@ export const ProjectSidebarList: FC = observer(() => {
|
||||
membership: { currentWorkspaceRole },
|
||||
} = useUser();
|
||||
const {
|
||||
getProjectById,
|
||||
joinedProjectIds: joinedProjects,
|
||||
favoriteProjectIds: favoriteProjects,
|
||||
orderProjectsWithSortOrder,
|
||||
updateProjectView,
|
||||
} = useProject();
|
||||
// router
|
||||
@ -55,15 +57,20 @@ export const ProjectSidebarList: FC = observer(() => {
|
||||
});
|
||||
};
|
||||
|
||||
const onDragEnd = async (result: DropResult) => {
|
||||
const onDragEnd = (result: DropResult) => {
|
||||
const { source, destination, draggableId } = result;
|
||||
|
||||
if (!destination || !workspaceSlug) return;
|
||||
|
||||
if (source.index === destination.index) return;
|
||||
|
||||
const updatedSortOrder = orderProjectsWithSortOrder(source.index, destination.index, draggableId);
|
||||
const joinedProjectsList: IProject[] = [];
|
||||
joinedProjects.map((projectId) => {
|
||||
const _project = getProjectById(projectId);
|
||||
if (_project) joinedProjectsList.push(_project);
|
||||
});
|
||||
if (joinedProjectsList.length <= 0) return;
|
||||
|
||||
const updatedSortOrder = orderJoinedProjects(source.index, destination.index, draggableId, joinedProjectsList);
|
||||
if (updatedSortOrder != undefined)
|
||||
updateProjectView(workspaceSlug.toString(), draggableId, { sort_order: updatedSortOrder }).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
@ -176,6 +183,7 @@ export const ProjectSidebarList: FC = observer(() => {
|
||||
snapshot={snapshot}
|
||||
handleCopyText={() => handleCopyText(projectId)}
|
||||
shortContextMenu
|
||||
disableDrag
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
45
web/helpers/project.helper.ts
Normal file
45
web/helpers/project.helper.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import { IProject } from "@plane/types";
|
||||
|
||||
/**
|
||||
* Updates the sort order of the project.
|
||||
* @param sortIndex
|
||||
* @param destinationIndex
|
||||
* @param projectId
|
||||
* @returns number | undefined
|
||||
*/
|
||||
export const orderJoinedProjects = (
|
||||
sourceIndex: number,
|
||||
destinationIndex: number,
|
||||
currentProjectId: string,
|
||||
joinedProjects: IProject[]
|
||||
): number | undefined => {
|
||||
if (!currentProjectId || sourceIndex < 0 || destinationIndex < 0 || joinedProjects.length <= 0) return undefined;
|
||||
|
||||
let updatedSortOrder: number | undefined = undefined;
|
||||
const sortOrderDefaultValue = 10000;
|
||||
|
||||
if (destinationIndex === 0) {
|
||||
// updating project at the top of the project
|
||||
const currentSortOrder = joinedProjects[destinationIndex].sort_order || 0;
|
||||
updatedSortOrder = currentSortOrder - sortOrderDefaultValue;
|
||||
} else if (destinationIndex === joinedProjects.length - 1) {
|
||||
// updating project at the bottom of the project
|
||||
const currentSortOrder = joinedProjects[destinationIndex - 1].sort_order || 0;
|
||||
updatedSortOrder = currentSortOrder + sortOrderDefaultValue;
|
||||
} else {
|
||||
// updating project in the middle of the project
|
||||
if (sourceIndex > destinationIndex) {
|
||||
const destinationTopProjectSortOrder = joinedProjects[destinationIndex - 1].sort_order || 0;
|
||||
const destinationBottomProjectSortOrder = joinedProjects[destinationIndex].sort_order || 0;
|
||||
const updatedValue = (destinationTopProjectSortOrder + destinationBottomProjectSortOrder) / 2;
|
||||
updatedSortOrder = updatedValue;
|
||||
} else {
|
||||
const destinationTopProjectSortOrder = joinedProjects[destinationIndex].sort_order || 0;
|
||||
const destinationBottomProjectSortOrder = joinedProjects[destinationIndex + 1].sort_order || 0;
|
||||
const updatedValue = (destinationTopProjectSortOrder + destinationBottomProjectSortOrder) / 2;
|
||||
updatedSortOrder = updatedValue;
|
||||
}
|
||||
}
|
||||
|
||||
return updatedSortOrder;
|
||||
};
|
@ -50,7 +50,7 @@ export const AppProvider: FC<IAppProvider> = observer((props) => {
|
||||
<CrispWrapper user={currentUser}>
|
||||
<PostHogProvider
|
||||
user={currentUser}
|
||||
currentWorkspaceId= {currentWorkspace?.id}
|
||||
currentWorkspaceId={currentWorkspace?.id}
|
||||
workspaceRole={currentWorkspaceRole}
|
||||
projectRole={currentProjectRole}
|
||||
posthogAPIKey={envConfig?.posthog_api_key || null}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "web",
|
||||
"version": "0.15.1",
|
||||
"version": "0.16.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "turbo run develop",
|
||||
|
@ -7,6 +7,7 @@ import { useProject, useInboxIssues } from "hooks/store";
|
||||
// layouts
|
||||
import { AppLayout } from "layouts/app-layout";
|
||||
// components
|
||||
import { InboxLayoutLoader } from "components/ui";
|
||||
import { PageHead } from "components/core";
|
||||
import { ProjectInboxHeader } from "components/headers";
|
||||
import { InboxSidebarRoot, InboxContentRoot } from "components/inbox";
|
||||
@ -23,7 +24,7 @@ const ProjectInboxPage: NextPageWithLayout = observer(() => {
|
||||
issues: { fetchInboxIssues },
|
||||
} = useInboxIssues();
|
||||
// fetching the Inbox filters and issues
|
||||
useSWR(
|
||||
const { isLoading } = useSWR(
|
||||
workspaceSlug && projectId && currentProjectDetails && currentProjectDetails?.inbox_view
|
||||
? `INBOX_ISSUES_${workspaceSlug.toString()}_${projectId.toString()}`
|
||||
: null,
|
||||
@ -37,7 +38,12 @@ const ProjectInboxPage: NextPageWithLayout = observer(() => {
|
||||
// derived values
|
||||
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Inbox` : undefined;
|
||||
|
||||
if (!workspaceSlug || !projectId || !inboxId || !currentProjectDetails?.inbox_view) return <></>;
|
||||
if (!workspaceSlug || !projectId || !inboxId || !currentProjectDetails?.inbox_view || isLoading)
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<InboxLayoutLoader />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -72,9 +72,6 @@ export class ProjectService extends APIService {
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: {
|
||||
view_props?: IProjectViewProps;
|
||||
default_props?: IProjectViewProps;
|
||||
preferences?: ProjectPreferences;
|
||||
sort_order?: number;
|
||||
}
|
||||
): Promise<any> {
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||
import { computedFn } from "mobx-utils";
|
||||
import set from "lodash/set";
|
||||
import sortBy from "lodash/sortBy";
|
||||
// types
|
||||
import { RootStore } from "../root.store";
|
||||
import { IProject } from "@plane/types";
|
||||
// services
|
||||
import { IssueLabelService, IssueService } from "services/issue";
|
||||
import { ProjectService, ProjectStateService } from "services/project";
|
||||
import { cloneDeep, update } from "lodash";
|
||||
export interface IProjectStore {
|
||||
// observables
|
||||
searchQuery: string;
|
||||
@ -28,8 +30,6 @@ export interface IProjectStore {
|
||||
// favorites actions
|
||||
addProjectToFavorites: (workspaceSlug: string, projectId: string) => Promise<any>;
|
||||
removeProjectFromFavorites: (workspaceSlug: string, projectId: string) => Promise<any>;
|
||||
// project-order action
|
||||
orderProjectsWithSortOrder: (sourceIndex: number, destinationIndex: number, projectId: string) => number;
|
||||
// project-view action
|
||||
updateProjectView: (workspaceSlug: string, projectId: string, viewProps: any) => Promise<any>;
|
||||
// CRUD actions
|
||||
@ -71,8 +71,6 @@ export class ProjectStore implements IProjectStore {
|
||||
// favorites actions
|
||||
addProjectToFavorites: action,
|
||||
removeProjectFromFavorites: action,
|
||||
// project-order action
|
||||
orderProjectsWithSortOrder: action,
|
||||
// project-view action
|
||||
updateProjectView: action,
|
||||
// CRUD actions
|
||||
@ -128,7 +126,11 @@ export class ProjectStore implements IProjectStore {
|
||||
get joinedProjectIds() {
|
||||
const currentWorkspace = this.rootStore.workspaceRoot.currentWorkspace;
|
||||
if (!currentWorkspace) return [];
|
||||
const projectIds = Object.values(this.projectMap ?? {})
|
||||
|
||||
let projects = Object.values(this.projectMap ?? {});
|
||||
projects = sortBy(projects, "sort_order");
|
||||
|
||||
const projectIds = projects
|
||||
.filter((project) => project.workspace === currentWorkspace.id && project.is_member)
|
||||
.map((project) => project.id);
|
||||
return projectIds;
|
||||
@ -140,7 +142,11 @@ export class ProjectStore implements IProjectStore {
|
||||
get favoriteProjectIds() {
|
||||
const currentWorkspace = this.rootStore.workspaceRoot.currentWorkspace;
|
||||
if (!currentWorkspace) return [];
|
||||
const projectIds = Object.values(this.projectMap ?? {})
|
||||
|
||||
let projects = Object.values(this.projectMap ?? {});
|
||||
projects = sortBy(projects, "created_at");
|
||||
|
||||
const projectIds = projects
|
||||
.filter((project) => project.workspace === currentWorkspace.id && project.is_favorite)
|
||||
.map((project) => project.id);
|
||||
return projectIds;
|
||||
@ -253,41 +259,6 @@ export class ProjectStore implements IProjectStore {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the sort order of the project.
|
||||
* @param sortIndex
|
||||
* @param destinationIndex
|
||||
* @param projectId
|
||||
* @returns
|
||||
*/
|
||||
orderProjectsWithSortOrder = (sortIndex: number, destinationIndex: number, projectId: string) => {
|
||||
try {
|
||||
const workspaceSlug = this.rootStore.app.router.workspaceSlug;
|
||||
if (!workspaceSlug) return 0;
|
||||
const projectsList = Object.values(this.projectMap || {}) || [];
|
||||
let updatedSortOrder = projectsList[sortIndex].sort_order;
|
||||
if (destinationIndex === 0) updatedSortOrder = (projectsList[0].sort_order as number) - 1000;
|
||||
else if (destinationIndex === projectsList.length - 1)
|
||||
updatedSortOrder = (projectsList[projectsList.length - 1].sort_order as number) + 1000;
|
||||
else {
|
||||
const destinationSortingOrder = projectsList[destinationIndex].sort_order as number;
|
||||
const relativeDestinationSortingOrder =
|
||||
sortIndex < destinationIndex
|
||||
? (projectsList[destinationIndex + 1].sort_order as number)
|
||||
: (projectsList[destinationIndex - 1].sort_order as number);
|
||||
|
||||
updatedSortOrder = (destinationSortingOrder + relativeDestinationSortingOrder) / 2;
|
||||
}
|
||||
runInAction(() => {
|
||||
set(this.projectMap, [projectId, "sort_order"], updatedSortOrder);
|
||||
});
|
||||
return updatedSortOrder;
|
||||
} catch (error) {
|
||||
console.log("failed to update sort order of the projects");
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the project view
|
||||
* @param workspaceSlug
|
||||
@ -295,12 +266,18 @@ export class ProjectStore implements IProjectStore {
|
||||
* @param viewProps
|
||||
* @returns
|
||||
*/
|
||||
updateProjectView = async (workspaceSlug: string, projectId: string, viewProps: any) => {
|
||||
updateProjectView = async (workspaceSlug: string, projectId: string, viewProps: { sort_order: number }) => {
|
||||
const currentProjectSortOrder = this.getProjectById(projectId)?.sort_order;
|
||||
try {
|
||||
runInAction(() => {
|
||||
set(this.projectMap, [projectId, "sort_order"], viewProps?.sort_order);
|
||||
});
|
||||
const response = await this.projectService.setProjectView(workspaceSlug, projectId, viewProps);
|
||||
await this.fetchProjects(workspaceSlug);
|
||||
return response;
|
||||
} catch (error) {
|
||||
runInAction(() => {
|
||||
set(this.projectMap, [projectId, "sort_order"], currentProjectSortOrder);
|
||||
});
|
||||
console.log("Failed to update sort order of the projects");
|
||||
throw error;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user