plane/web/store/project-page.store.ts
Henit Chobisa e975abff21
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 <rahulr@Rahuls-MacBook-Pro.local>
2024-01-19 15:18:47 +05:30

262 lines
9.2 KiB
TypeScript

import { makeObservable, observable, runInAction, action, computed } from "mobx";
import { set } from "lodash";
// services
import { PageService } from "services/page.service";
// store
import { PageStore, IPageStore } from "store/page.store";
// types
import { IPage, IRecentPages } from "@plane/types";
import { RootStore } from "./root.store";
import { isThisWeek, isToday, isYesterday } from "date-fns";
export interface IProjectPageStore {
projectPageMap: Record<string, Record<string, IPageStore>>;
projectArchivedPageMap: Record<string, Record<string, IPageStore>>;
projectPageIds: string[] | undefined;
archivedPageIds: string[] | undefined;
favoriteProjectPageIds: string[] | undefined;
privateProjectPageIds: string[] | undefined;
publicProjectPageIds: string[] | undefined;
recentProjectPages: IRecentPages | undefined;
// fetch actions
fetchProjectPages: (workspaceSlug: string, projectId: string) => Promise<void>;
fetchArchivedProjectPages: (workspaceSlug: string, projectId: string) => Promise<void>;
// crud actions
createPage: (workspaceSlug: string, projectId: string, data: Partial<IPage>) => Promise<IPage>;
deletePage: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>;
archivePage: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>;
restorePage: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>;
}
export class ProjectPageStore implements IProjectPageStore {
projectPageMap: Record<string, Record<string, IPageStore>> = {}; // { projectId: [page1, page2] }
projectArchivedPageMap: Record<string, Record<string, IPageStore>> = {}; // { projectId: [page1, page2] }
// root store
rootStore;
pageService;
constructor(_rootStore: RootStore) {
makeObservable(this, {
projectPageMap: observable,
projectArchivedPageMap: observable,
projectPageIds: computed,
archivedPageIds: computed,
favoriteProjectPageIds: computed,
privateProjectPageIds: computed,
publicProjectPageIds: computed,
recentProjectPages: computed,
// fetch actions
fetchProjectPages: action,
fetchArchivedProjectPages: action,
// crud actions
createPage: action,
deletePage: action,
});
this.rootStore = _rootStore;
this.pageService = new PageService();
}
get projectPageIds() {
const projectId = this.rootStore.app.router.projectId;
if (!projectId || !this.projectPageMap?.[projectId]) return [];
const allProjectIds = Object.keys(this.projectPageMap[projectId]);
return allProjectIds.sort((a, b) => {
const dateA = new Date(this.projectPageMap[projectId][a].created_at).getTime();
const dateB = new Date(this.projectPageMap[projectId][b].created_at).getTime();
return dateB - dateA;
});
}
get archivedPageIds() {
const projectId = this.rootStore.app.router.projectId;
if (!projectId || !this.projectArchivedPageMap[projectId]) return [];
const archivedPages = Object.keys(this.projectArchivedPageMap[projectId]);
return archivedPages.sort((a, b) => {
const dateA = new Date(this.projectArchivedPageMap[projectId][a].created_at).getTime();
const dateB = new Date(this.projectArchivedPageMap[projectId][b].created_at).getTime();
return dateB - dateA;
});
}
get favoriteProjectPageIds() {
const projectId = this.rootStore.app.router.projectId;
if (!this.projectPageIds || !projectId) return [];
const favouritePages: string[] = this.projectPageIds.filter(
(page) => this.projectPageMap[projectId][page].is_favorite
);
return favouritePages;
}
get privateProjectPageIds() {
const projectId = this.rootStore.app.router.projectId;
if (!this.projectPageIds || !projectId) return [];
const privatePages: string[] = this.projectPageIds.filter(
(page) => this.projectPageMap[projectId][page].access === 1
);
return privatePages;
}
get publicProjectPageIds() {
const projectId = this.rootStore.app.router.projectId;
const userId = this.rootStore.user.currentUser?.id;
if (!this.projectPageIds || !projectId || !userId) return [];
const publicPages: string[] = this.projectPageIds.filter(
(page) =>
this.projectPageMap[projectId][page].access === 0 && this.projectPageMap[projectId][page].owned_by === userId
);
return publicPages;
}
get recentProjectPages() {
const projectId = this.rootStore.app.router.projectId;
if (!this.projectPageIds || !projectId) return;
const today: string[] = this.projectPageIds.filter((page) =>
isToday(new Date(this.projectPageMap[projectId][page].updated_at))
);
const yesterday: string[] = this.projectPageIds.filter((page) =>
isYesterday(new Date(this.projectPageMap[projectId][page].updated_at))
);
const this_week: string[] = this.projectPageIds.filter((page) => {
const pageUpdatedAt = this.projectPageMap[projectId][page].updated_at;
return (
isThisWeek(new Date(pageUpdatedAt)) &&
!isToday(new Date(pageUpdatedAt)) &&
!isYesterday(new Date(pageUpdatedAt))
);
});
const older: string[] = this.projectPageIds.filter((page) => {
const pageUpdatedAt = this.projectPageMap[projectId][page].updated_at;
return !isThisWeek(new Date(pageUpdatedAt)) && !isYesterday(new Date(pageUpdatedAt));
});
return { today, yesterday, this_week, older };
}
/**
* Fetching all the pages for a specific project
* @param workspaceSlug
* @param projectId
*/
fetchProjectPages = async (workspaceSlug: string, projectId: string) => {
try {
await this.pageService.getProjectPages(workspaceSlug, projectId).then((response) => {
runInAction(() => {
for (const page of response) {
set(this.projectPageMap, [projectId, page.id], new PageStore(page, this.rootStore));
}
});
return response;
});
} catch (e) {
throw e;
}
};
/**
* fetches all archived pages for a project.
* @param workspaceSlug
* @param projectId
* @returns Promise<IPage[]>
*/
fetchArchivedProjectPages = async (workspaceSlug: string, projectId: string) => {
try {
await this.pageService.getArchivedPages(workspaceSlug, projectId).then((response) => {
runInAction(() => {
for (const page of response) {
set(this.projectArchivedPageMap, [projectId, page.id], new PageStore(page, this.rootStore));
}
});
return response;
});
} catch (e) {
throw e;
}
};
/**
* Creates a new page using the api and updated the local state in store
* @param workspaceSlug
* @param projectId
* @param data
*/
createPage = async (workspaceSlug: string, projectId: string, data: Partial<IPage>) => {
const response = await this.pageService.createPage(workspaceSlug, projectId, data);
runInAction(() => {
set(this.projectPageMap, [projectId, response.id], new PageStore(response, this.rootStore));
});
return response;
};
/**
* delete a page using the api and updates the local state in store
* @param workspaceSlug
* @param projectId
* @param pageId
* @returns
*/
deletePage = async (workspaceSlug: string, projectId: string, pageId: string) => {
const response = await this.pageService.deletePage(workspaceSlug, projectId, pageId);
runInAction(() => {
delete this.projectArchivedPageMap[projectId][pageId];
});
return response;
};
/**
* Mark a page archived
* @param workspaceSlug
* @param projectId
* @param pageId
*/
archivePage = async (workspaceSlug: string, projectId: string, pageId: string) => {
runInAction(() => {
set(this.projectArchivedPageMap, [projectId, pageId], this.projectPageMap[projectId][pageId]);
set(this.projectArchivedPageMap[projectId][pageId], "archived_at", new Date().toISOString());
delete this.projectPageMap[projectId][pageId];
});
const response = await this.pageService.archivePage(workspaceSlug, projectId, pageId).catch(() => {
runInAction(() => {
set(this.projectPageMap, [projectId, pageId], this.projectArchivedPageMap[projectId][pageId]);
set(this.projectPageMap[projectId][pageId], "archived_at", null);
delete this.projectArchivedPageMap[projectId][pageId];
});
});
return response;
};
/**
* Restore a page from archived pages to pages
* @param workspaceSlug
* @param projectId
* @param pageId
*/
restorePage = async (workspaceSlug: string, projectId: string, pageId: string) => {
const pageArchivedAt = this.projectArchivedPageMap[projectId][pageId].archived_at;
runInAction(() => {
set(this.projectPageMap, [projectId, pageId], this.projectArchivedPageMap[projectId][pageId]);
set(this.projectPageMap[projectId][pageId], "archived_at", null);
delete this.projectArchivedPageMap[projectId][pageId];
});
await this.pageService.restorePage(workspaceSlug, projectId, pageId).catch(() => {
runInAction(() => {
set(this.projectArchivedPageMap, [projectId, pageId], this.projectPageMap[projectId][pageId]);
set(this.projectArchivedPageMap[projectId][pageId], "archived_at", pageArchivedAt);
delete this.projectPageMap[projectId][pageId];
});
});
};
}