Skip to content

Commit 99f6b48

Browse files
anmolsinghbhatiaClarenceChen0627
authored andcommitted
[WEB-5170] feat: navigation revamp (makeplane#8162)
1 parent 19d0d03 commit 99f6b48

File tree

110 files changed

+3781
-758
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+3781
-758
lines changed

apps/api/plane/app/urls/project.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
ProjectPublicCoverImagesEndpoint,
1515
UserProjectRolesEndpoint,
1616
ProjectArchiveUnarchiveEndpoint,
17+
ProjectMemberPreferenceEndpoint,
1718
)
1819

1920

@@ -125,4 +126,9 @@
125126
ProjectArchiveUnarchiveEndpoint.as_view(),
126127
name="project-archive-unarchive",
127128
),
129+
path(
130+
"workspaces/<str:slug>/projects/<uuid:project_id>/preferences/member/<uuid:member_id>/",
131+
ProjectMemberPreferenceEndpoint.as_view(),
132+
name="project-member-preference",
133+
),
128134
]

apps/api/plane/app/urls/workspace.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,4 @@
253253
WorkspaceUserPreferenceViewSet.as_view(),
254254
name="workspace-user-preference",
255255
),
256-
path(
257-
"workspaces/<str:slug>/sidebar-preferences/<str:key>/",
258-
WorkspaceUserPreferenceViewSet.as_view(),
259-
name="workspace-user-preference",
260-
),
261256
]

apps/api/plane/app/views/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
ProjectMemberViewSet,
1919
ProjectMemberUserEndpoint,
2020
UserProjectRolesEndpoint,
21+
ProjectMemberPreferenceEndpoint,
2122
)
2223

2324
from .user.base import (

apps/api/plane/app/views/project/member.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,3 +300,37 @@ def get(self, request, slug):
300300

301301
project_members = {str(member["project_id"]): member["role"] for member in project_members}
302302
return Response(project_members, status=status.HTTP_200_OK)
303+
304+
305+
class ProjectMemberPreferenceEndpoint(BaseAPIView):
306+
def get_project_member(self, slug, project_id, member_id):
307+
return ProjectMember.objects.get(
308+
project_id=project_id,
309+
member_id=member_id,
310+
workspace__slug=slug,
311+
)
312+
313+
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
314+
def patch(self, request, slug, project_id, member_id):
315+
project_member = self.get_project_member(slug, project_id, member_id)
316+
317+
current_preferences = project_member.preferences or {}
318+
current_preferences["navigation"] = request.data["navigation"]
319+
320+
project_member.preferences = current_preferences
321+
project_member.save(update_fields=["preferences"])
322+
323+
return Response({"preferences": project_member.preferences}, status=status.HTTP_200_OK)
324+
325+
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
326+
def get(self, request, slug, project_id, member_id):
327+
project_member = self.get_project_member(slug, project_id, member_id)
328+
329+
response = {
330+
"preferences": project_member.preferences,
331+
"project_id": project_member.project_id,
332+
"member_id": project_member.member_id,
333+
"workspace_id": project_member.workspace_id,
334+
}
335+
336+
return Response(response, status=status.HTTP_200_OK)

apps/api/plane/app/views/workspace/user_preference.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,23 @@ def get(self, request, slug):
6565
)
6666

6767
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
68-
def patch(self, request, slug, key):
69-
preference = WorkspaceUserPreference.objects.filter(key=key, workspace__slug=slug, user=request.user).first()
68+
def patch(self, request, slug):
69+
for data in request.data:
70+
key = data.pop("key", None)
71+
if not key:
72+
continue
7073

71-
if preference:
72-
serializer = WorkspaceUserPreferenceSerializer(preference, data=request.data, partial=True)
74+
preference = WorkspaceUserPreference.objects.filter(key=key, workspace__slug=slug).first()
7375

74-
if serializer.is_valid():
75-
serializer.save()
76-
return Response(serializer.data, status=status.HTTP_200_OK)
77-
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
76+
if not preference:
77+
continue
7878

79-
return Response({"detail": "Preference not found"}, status=status.HTTP_404_NOT_FOUND)
79+
if "is_pinned" in data:
80+
preference.is_pinned = data["is_pinned"]
81+
82+
if "sort_order" in data:
83+
preference.sort_order = data["sort_order"]
84+
85+
preference.save(update_fields=["is_pinned", "sort_order"])
86+
87+
return Response({"message": "Successfully updated"}, status=status.HTTP_200_OK)

apps/api/plane/db/models/project.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def get_default_props():
5959

6060

6161
def get_default_preferences():
62-
return {"pages": {"block_display": True}}
62+
return {"pages": {"block_display": True}, "navigation": {"default_tab": "work_items", "hide_in_more_menu": []}}
6363

6464

6565
class Project(BaseModel):

apps/api/plane/db/models/workspace.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ class UserPreferenceKeys(models.TextChoices):
417417
DRAFTS = "drafts", "Drafts"
418418
YOUR_WORK = "your_work", "Your Work"
419419
ARCHIVES = "archives", "Archives"
420+
STICKIES = "stickies", "Stickies"
420421

421422
workspace = models.ForeignKey(
422423
"db.Workspace",

apps/web/app/(all)/[workspaceSlug]/(projects)/_sidebar.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import type { FC } from "react";
22
import { useState } from "react";
33
import { observer } from "mobx-react";
44
// plane imports
5+
import { useParams, usePathname } from "next/navigation";
56
import { SIDEBAR_WIDTH } from "@plane/constants";
67
import { useLocalStorage } from "@plane/hooks";
78
// components
89
import { ResizableSidebar } from "@/components/sidebar/resizable-sidebar";
910
// hooks
1011
import { useAppTheme } from "@/hooks/store/use-app-theme";
11-
import { useAppRail } from "@/hooks/use-app-rail";
1212
// local imports
1313
import { ExtendedAppSidebar } from "./extended-sidebar";
1414
import { AppSidebar } from "./sidebar";
@@ -26,14 +26,19 @@ export const ProjectAppSidebar = observer(function ProjectAppSidebar() {
2626
const { storedValue, setValue } = useLocalStorage("sidebarWidth", SIDEBAR_WIDTH);
2727
// states
2828
const [sidebarWidth, setSidebarWidth] = useState<number>(storedValue ?? SIDEBAR_WIDTH);
29-
// hooks
30-
const { shouldRenderAppRail } = useAppRail();
29+
// routes
30+
const { workspaceSlug } = useParams();
31+
const pathname = usePathname();
3132
// derived values
3233
const isAnyExtendedSidebarOpen = isExtendedSidebarOpened;
3334

35+
const isNotificationsPath = pathname.includes(`/${workspaceSlug}/notifications`);
36+
3437
// handlers
3538
const handleWidthChange = (width: number) => setValue(width);
3639

40+
if (isNotificationsPath) return null;
41+
3742
return (
3843
<>
3944
<ResizableSidebar
@@ -55,7 +60,6 @@ export const ProjectAppSidebar = observer(function ProjectAppSidebar() {
5560
}
5661
isAnyExtendedSidebarExpanded={isAnyExtendedSidebarOpen}
5762
isAnySidebarDropdownOpen={isAnySidebarDropdownOpen}
58-
disablePeekTrigger={shouldRenderAppRail}
5963
>
6064
<AppSidebar />
6165
</ResizableSidebar>
Lines changed: 26 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,43 @@
11
import { observer } from "mobx-react";
22
import { useParams } from "next/navigation";
3-
// plane imports
4-
import { EProjectFeatureKey } from "@plane/constants";
5-
import { Breadcrumbs, Header } from "@plane/ui";
6-
// components
7-
import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
8-
import { IssueDetailQuickActions } from "@/components/issues/issue-detail/issue-detail-quick-actions";
93
// hooks
4+
import { Header, Row } from "@plane/ui";
5+
import { AppHeader } from "@/components/core/app-header";
6+
import { TabNavigationRoot } from "@/components/navigation";
107
import { useIssueDetail } from "@/hooks/store/use-issue-detail";
11-
import { useProject } from "@/hooks/store/use-project";
12-
import { useAppRouter } from "@/hooks/use-app-router";
13-
import { CommonProjectBreadcrumbs } from "@/plane-web/components/breadcrumbs/common";
8+
// local components
9+
import { WorkItemDetailsHeader } from "./work-item-header";
1410

15-
export const ProjectIssueDetailsHeader = observer(function ProjectIssueDetailsHeader() {
11+
export const ProjectWorkItemDetailsHeader = observer(function ProjectWorkItemDetailsHeader() {
1612
// router
17-
const router = useAppRouter();
1813
const { workspaceSlug, workItem } = useParams();
1914
// store hooks
20-
const { getProjectById, loader } = useProject();
2115
const {
2216
issue: { getIssueById, getIssueIdByIdentifier },
2317
} = useIssueDetail();
2418
// derived values
2519
const issueId = getIssueIdByIdentifier(workItem?.toString());
26-
const issueDetails = issueId ? getIssueById(issueId.toString()) : undefined;
27-
const projectId = issueDetails ? issueDetails?.project_id : undefined;
28-
const projectDetails = projectId ? getProjectById(projectId?.toString()) : undefined;
29-
30-
if (!workspaceSlug || !projectId || !issueId) return null;
20+
const issueDetails = issueId ? getIssueById(issueId?.toString()) : undefined;
3121

3222
return (
33-
<Header>
34-
<Header.LeftItem>
35-
<Breadcrumbs onBack={router.back} isLoading={loader === "init-loader"}>
36-
<CommonProjectBreadcrumbs
37-
workspaceSlug={workspaceSlug?.toString()}
38-
projectId={projectId?.toString()}
39-
featureKey={EProjectFeatureKey.WORK_ITEMS}
40-
/>
41-
<Breadcrumbs.Item
42-
component={
43-
<BreadcrumbLink
44-
label={projectDetails && issueDetails ? `${projectDetails.identifier}-${issueDetails.sequence_id}` : ""}
45-
/>
46-
}
47-
/>
48-
</Breadcrumbs>
49-
</Header.LeftItem>
50-
<Header.RightItem>
51-
{projectId && issueId && (
52-
<IssueDetailQuickActions
53-
workspaceSlug={workspaceSlug?.toString()}
54-
projectId={projectId?.toString()}
55-
issueId={issueId?.toString()}
56-
/>
57-
)}
58-
</Header.RightItem>
59-
</Header>
23+
<>
24+
<div className="z-20">
25+
<Row className="h-header flex gap-2 w-full items-center border-b border-custom-border-200 bg-custom-sidebar-background-100">
26+
<div className="flex items-center gap-2 divide-x divide-custom-border-100 h-full w-full">
27+
<div className="flex items-center h-full w-full flex-1">
28+
<Header className="h-full">
29+
<Header.LeftItem className="h-full max-w-full">
30+
<TabNavigationRoot
31+
workspaceSlug={workspaceSlug}
32+
projectId={issueDetails?.project_id?.toString() ?? ""}
33+
/>
34+
</Header.LeftItem>
35+
</Header>
36+
</div>
37+
</div>
38+
</Row>
39+
</div>
40+
<AppHeader header={<WorkItemDetailsHeader />} />
41+
</>
6042
);
6143
});

apps/web/app/(all)/[workspaceSlug]/(projects)/browse/[workItem]/layout.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
// components
22
import { Outlet } from "react-router";
3-
import { AppHeader } from "@/components/core/app-header";
43
import { ContentWrapper } from "@/components/core/content-wrapper";
5-
import { ProjectIssueDetailsHeader } from "./header";
4+
import { ProjectWorkItemDetailsHeader } from "./header";
65

76
export default function ProjectIssueDetailsLayout() {
87
return (
98
<>
10-
<AppHeader header={<ProjectIssueDetailsHeader />} />
9+
<ProjectWorkItemDetailsHeader />
1110
<ContentWrapper className="overflow-hidden">
1211
<Outlet />
1312
</ContentWrapper>

0 commit comments

Comments
 (0)