Skip to content

Commit 0af36a3

Browse files
henit-chobisaRahul R
authored andcommitted
Improvement: High Performance MobX Integration for Pages ✈︎ (#3397)
* fix: removed parameters `workspace`, `project` & `id` from the patch calls * feat: modified components to work with new pages hooks * feat: modified stores * feat: modified initial component * feat: component implementation changes * feat: store implementation * refactor pages store * feat: updated page store to perform async operations faster * fix: added types for archive and restore pages * feat: implemented archive and restore pages * fix: page creating twice when form submit * feat: updated create-page-modal * feat: updated page form and delete page modal * fix: create page modal not updating isSubmitted prop * feat: list items and list view refactored for pages * feat: refactored project-page-store for inserting computed pagesids * chore: renamed project pages hook * feat: added favourite pages implementation * fix: implemented store for archived pages * fix: project page store for recent pages * fix: issue suggestions breaking pages * fix: issue embeds and suggestions breaking * feat: implemented page store and project page store in page editor * chore: lock file changes * fix: modified page details header to catch mobx updates instead of swr calls * fix: modified usePage hook to fetch page details when reloaded directly on page * fix: fixed deleting pages * fix: removed render on props changed * feat: implemented page store inside page details * fix: role change in pages archives * fix: rerending of pages on tab change * fix: reimplementation of peek overview inside pages * chore: typo fixes * fix: issue suggestion widget selecting wrong issues on click * feat: added labels in pages * fix: deepsource errors fixed * fix: build errors * fix: review comments * fix: removed swr hooks from the `usePage` store hook and refactored `issueEmbed` hook * fix: resolved reviewed comments --------- Co-authored-by: Rahul R <[email protected]>
1 parent 7edb49f commit 0af36a3

32 files changed

+941
-1081
lines changed

apiserver/plane/app/views/page.py

+10-21
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,25 @@
11
# Python imports
2-
from datetime import timedelta, date, datetime
2+
from datetime import date, datetime, timedelta
33

44
# Django imports
55
from django.db import connection
66
from django.db.models import Exists, OuterRef, Q
77
from django.utils import timezone
88
from django.utils.decorators import method_decorator
99
from django.views.decorators.gzip import gzip_page
10-
1110
# Third party imports
1211
from rest_framework import status
1312
from rest_framework.response import Response
1413

15-
# Module imports
16-
from .base import BaseViewSet, BaseAPIView
1714
from plane.app.permissions import ProjectEntityPermission
18-
from plane.db.models import (
19-
Page,
20-
PageFavorite,
21-
Issue,
22-
IssueAssignee,
23-
IssueActivity,
24-
PageLog,
25-
ProjectMember,
26-
)
27-
from plane.app.serializers import (
28-
PageSerializer,
29-
PageFavoriteSerializer,
30-
PageLogSerializer,
31-
IssueLiteSerializer,
32-
SubPageSerializer,
33-
)
15+
from plane.app.serializers import (IssueLiteSerializer, PageFavoriteSerializer,
16+
PageLogSerializer, PageSerializer,
17+
SubPageSerializer)
18+
from plane.db.models import (Issue, IssueActivity, IssueAssignee, Page,
19+
PageFavorite, PageLog, ProjectMember)
20+
21+
# Module imports
22+
from .base import BaseAPIView, BaseViewSet
3423

3524

3625
def unarchive_archive_page_and_descendants(page_id, archived_at):
@@ -175,7 +164,7 @@ def archive(self, request, slug, project_id, page_id):
175164
project_id=project_id,
176165
member=request.user,
177166
is_active=True,
178-
role__gt=20,
167+
role__gte=20,
179168
).exists()
180169
or request.user.id != page.owned_by_id
181170
):

packages/editor/document-editor/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@plane/editor-core": "*",
3333
"@plane/editor-extensions": "*",
3434
"@plane/ui": "*",
35+
"@tippyjs/react": "^4.2.6",
3536
"@tiptap/core": "^2.1.13",
3637
"@tiptap/extension-placeholder": "^2.1.13",
3738
"@tiptap/pm": "^2.1.13",

packages/editor/document-editor/src/ui/components/page-renderer.tsx

+2-16
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818

1919
type IPageRenderer = {
2020
documentDetails: DocumentDetails;
21-
updatePageTitle: (title: string) => Promise<void>;
21+
updatePageTitle: (title: string) => void;
2222
editor: Editor;
2323
onActionCompleteHandler: (action: {
2424
title: string;
@@ -30,18 +30,6 @@ type IPageRenderer = {
3030
readonly: boolean;
3131
};
3232

33-
const debounce = (func: (...args: any[]) => void, wait: number) => {
34-
let timeout: NodeJS.Timeout | null = null;
35-
return function executedFunction(...args: any[]) {
36-
const later = () => {
37-
if (timeout) clearTimeout(timeout);
38-
func(...args);
39-
};
40-
if (timeout) clearTimeout(timeout);
41-
timeout = setTimeout(later, wait);
42-
};
43-
};
44-
4533
export const PageRenderer = (props: IPageRenderer) => {
4634
const { documentDetails, editor, editorClassNames, editorContentCustomClassNames, updatePageTitle, readonly } = props;
4735

@@ -64,11 +52,9 @@ export const PageRenderer = (props: IPageRenderer) => {
6452

6553
const { getFloatingProps } = useInteractions([dismiss]);
6654

67-
const debouncedUpdatePageTitle = debounce(updatePageTitle, 300);
68-
6955
const handlePageTitleChange = (title: string) => {
7056
setPagetitle(title);
71-
debouncedUpdatePageTitle(title);
57+
updatePageTitle(title);
7258
};
7359

7460
const [cleanup, setcleanup] = useState(() => () => {});

packages/editor/document-editor/src/ui/extensions/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const DocumentEditorExtensions = (
2626
.focus()
2727
.insertContentAt(
2828
range,
29-
"<p class='text-sm bg-gray-300 w-fit pl-3 pr-3 pt-1 pb-1 rounded shadow-sm'>#issue_</p>"
29+
"<p class='text-sm bg-gray-300 w-fit pl-3 pr-3 pt-1 pb-1 rounded shadow-sm'>#issue_</p>\n"
3030
)
3131
.run();
3232
},

packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const IssueSuggestions = (suggestions: any[]) => {
2424
title: suggestion.name,
2525
priority: suggestion.priority.toString(),
2626
identifier: `${suggestion.project_detail.identifier}-${suggestion.sequence_id}`,
27-
state: suggestion.state_detail.name,
27+
state: suggestion.state_detail && suggestion.state_detail.name ? suggestion.state_detail.name : "Todo",
2828
command: ({ editor, range }) => {
2929
editor
3030
.chain()

packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-extension.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export const IssueEmbedSuggestions = Extension.create({
99
addOptions() {
1010
return {
1111
suggestion: {
12+
char: "#issue_",
13+
allowSpaces: true,
1214
command: ({ editor, range, props }: { editor: Editor; range: Range; props: any }) => {
1315
props.command({ editor, range });
1416
},
@@ -18,11 +20,8 @@ export const IssueEmbedSuggestions = Extension.create({
1820
addProseMirrorPlugins() {
1921
return [
2022
Suggestion({
21-
char: "#issue_",
2223
pluginKey: new PluginKey("issue-embed-suggestions"),
2324
editor: this.editor,
24-
allowSpaces: true,
25-
2625
...this.options.suggestion,
2726
}),
2827
];

packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-suggestion-list/issue-suggestion-renderer.tsx

+12-8
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const IssueSuggestionList = ({
5353
const commandListContainer = useRef<HTMLDivElement>(null);
5454

5555
useEffect(() => {
56-
let newDisplayedItems: { [key: string]: IssueSuggestionProps[] } = {};
56+
const newDisplayedItems: { [key: string]: IssueSuggestionProps[] } = {};
5757
let totalLength = 0;
5858
sections.forEach((section) => {
5959
newDisplayedItems[section] = items.filter((item) => item.state === section).slice(0, 5);
@@ -65,8 +65,8 @@ const IssueSuggestionList = ({
6565
}, [items]);
6666

6767
const selectItem = useCallback(
68-
(index: number) => {
69-
const item = displayedItems[currentSection][index];
68+
(section: string, index: number) => {
69+
const item = displayedItems[section][index];
7070
if (item) {
7171
command(item);
7272
}
@@ -87,6 +87,7 @@ const IssueSuggestionList = ({
8787
setSelectedIndex(
8888
(selectedIndex + displayedItems[currentSection].length - 1) % displayedItems[currentSection].length
8989
);
90+
e.stopPropagation();
9091
return true;
9192
}
9293
if (e.key === "ArrowDown") {
@@ -101,17 +102,20 @@ const IssueSuggestionList = ({
101102
[currentSection]: [...prevItems[currentSection], ...nextItems],
102103
}));
103104
}
105+
e.stopPropagation();
104106
return true;
105107
}
106108
if (e.key === "Enter") {
107-
selectItem(selectedIndex);
109+
selectItem(currentSection, selectedIndex);
110+
e.stopPropagation();
108111
return true;
109112
}
110113
if (e.key === "Tab") {
111114
const currentSectionIndex = sections.indexOf(currentSection);
112115
const nextSectionIndex = (currentSectionIndex + 1) % sections.length;
113116
setCurrentSection(sections[nextSectionIndex]);
114117
setSelectedIndex(0);
118+
e.stopPropagation();
115119
return true;
116120
}
117121
return false;
@@ -172,7 +176,7 @@ const IssueSuggestionList = ({
172176
}
173177
)}
174178
key={item.identifier}
175-
onClick={() => selectItem(index)}
179+
onClick={() => selectItem(section, index)}
176180
>
177181
<h5 className="whitespace-nowrap text-xs text-custom-text-300">{item.identifier}</h5>
178182
<PriorityIcon priority={item.priority} />
@@ -195,7 +199,7 @@ export const IssueListRenderer = () => {
195199
let popup: any | null = null;
196200

197201
return {
198-
onStart: (props: { editor: Editor; clientRect: DOMRect }) => {
202+
onStart: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => {
199203
component = new ReactRenderer(IssueSuggestionList, {
200204
props,
201205
// @ts-ignore
@@ -210,10 +214,10 @@ export const IssueListRenderer = () => {
210214
showOnCreate: true,
211215
interactive: true,
212216
trigger: "manual",
213-
placement: "right",
217+
placement: "bottom-start",
214218
});
215219
},
216-
onUpdate: (props: { editor: Editor; clientRect: DOMRect }) => {
220+
onUpdate: (props: { editor: Editor; clientRect?: (() => DOMRect | null) | null }) => {
217221
component?.updateProps(props);
218222

219223
popup &&

packages/editor/document-editor/src/ui/extensions/widgets/issue-embed-widget/issue-widget-card.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ export const IssueWidgetCard = (props) => {
1515
setIssueDetails(issue);
1616
setLoading(0);
1717
})
18-
.catch((error) => {
19-
console.log(error);
18+
.catch(() => {
2019
setLoading(-1);
2120
});
2221
}, []);
@@ -30,7 +29,9 @@ export const IssueWidgetCard = (props) => {
3029
{loading == 0 ? (
3130
<div
3231
onClick={completeIssueEmbedAction}
33-
className="w-full cursor-pointer space-y-2 rounded-md border-[0.5px] border-custom-border-200 p-3 shadow-custom-shadow-2xs"
32+
className={`${
33+
props.selected ? "border-custom-primary-200 border-[2px]" : ""
34+
} w-full cursor-pointer space-y-2 rounded-md border-[0.5px] border-custom-border-200 p-3 shadow-custom-shadow-2xs`}
3435
>
3536
<h5 className="text-xs text-custom-text-300">
3637
{issueDetails.project_detail.identifier}-{issueDetails.sequence_id}

packages/editor/document-editor/src/ui/index.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ interface IDocumentEditor {
1616
// document info
1717
documentDetails: DocumentDetails;
1818
value: string;
19-
rerenderOnPropsChange: {
19+
rerenderOnPropsChange?: {
2020
id: string;
2121
description_html: string;
2222
};
@@ -39,7 +39,7 @@ interface IDocumentEditor {
3939
setIsSubmitting?: (isSubmitting: "submitting" | "submitted" | "saved") => void;
4040
setShouldShowAlert?: (showAlert: boolean) => void;
4141
forwardedRef?: any;
42-
updatePageTitle: (title: string) => Promise<void>;
42+
updatePageTitle: (title: string) => void;
4343
debouncedUpdatesEnabled?: boolean;
4444
isSubmitting: "submitting" | "submitted" | "saved";
4545

web/components/headers/page-details.tsx

+2-13
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,17 @@
11
import { FC } from "react";
22
import { useRouter } from "next/router";
33
import { observer } from "mobx-react-lite";
4-
import useSWR from "swr";
54
import { FileText, Plus } from "lucide-react";
65
// hooks
7-
import { useApplication, useProject } from "hooks/store";
8-
// services
9-
import { PageService } from "services/page.service";
6+
import { useApplication, usePage, useProject } from "hooks/store";
107
// ui
118
import { Breadcrumbs, Button } from "@plane/ui";
129
// helpers
1310
import { renderEmoji } from "helpers/emoji.helper";
14-
// fetch-keys
15-
import { PAGE_DETAILS } from "constants/fetch-keys";
1611

1712
export interface IPagesHeaderProps {
1813
showButton?: boolean;
1914
}
20-
const pageService = new PageService();
2115

2216
export const PageDetailsHeader: FC<IPagesHeaderProps> = observer((props) => {
2317
const { showButton = false } = props;
@@ -28,12 +22,7 @@ export const PageDetailsHeader: FC<IPagesHeaderProps> = observer((props) => {
2822
const { commandPalette: commandPaletteStore } = useApplication();
2923
const { currentProjectDetails } = useProject();
3024

31-
const { data: pageDetails } = useSWR(
32-
workspaceSlug && currentProjectDetails?.id && pageId ? PAGE_DETAILS(pageId as string) : null,
33-
workspaceSlug && currentProjectDetails?.id
34-
? () => pageService.getPageDetails(workspaceSlug as string, currentProjectDetails.id, pageId as string)
35-
: null
36-
);
25+
const pageDetails = usePage(pageId as string);
3726

3827
return (
3928
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">

0 commit comments

Comments
 (0)