import set from "lodash/set"; import unset from "lodash/unset"; import { makeObservable, observable, runInAction, action, reaction } from "mobx"; import { computedFn } from "mobx-utils"; // types import { TPage, TPageFilters, TPageNavigationTabs } from "@plane/types"; // helpers import { filterPagesByPageType, orderPages, shouldFilterPage } from "@/helpers/page.helper"; // services import { PageService } from "@/services/page.service"; // store import { IPageStore, PageStore } from "@/store/pages/page.store"; import { RootStore } from "../root.store"; type TLoader = "init-loader" | "mutation-loader" | undefined; type TError = { title: string; description: string }; export interface IProjectPageStore { // observables loader: TLoader; data: Record; // pageId => PageStore error: TError | undefined; filters: TPageFilters; // helper actions getCurrentProjectPageIds: (pageType: TPageNavigationTabs) => string[] | undefined; getCurrentProjectFilteredPageIds: (pageType: TPageNavigationTabs) => string[] | undefined; pageById: (pageId: string) => IPageStore | undefined; updateFilters: (filterKey: T, filterValue: TPageFilters[T]) => void; clearAllFilters: () => void; // actions getAllPages: (pageType: TPageNavigationTabs) => Promise; getPageById: (pageId: string) => Promise; createPage: (pageData: Partial) => Promise; removePage: (pageId: string) => Promise; } export class ProjectPageStore implements IProjectPageStore { // observables loader: TLoader = "init-loader"; data: Record = {}; // pageId => PageStore error: TError | undefined = undefined; filters: TPageFilters = { searchQuery: "", sortKey: "name", sortBy: "asc", }; // service service: PageService; constructor(private store: RootStore) { makeObservable(this, { // observables loader: observable.ref, data: observable, error: observable, filters: observable, // helper actions updateFilters: action, clearAllFilters: action, // actions getAllPages: action, getPageById: action, createPage: action, removePage: action, }); // service this.service = new PageService(); // initialize display filters of the current project reaction( () => this.store.router.projectId, (projectId) => { if (!projectId) return; this.filters.searchQuery = ""; } ); } /** * @description get the current project page ids based on the pageType * @param {TPageNavigationTabs} pageType */ getCurrentProjectPageIds = computedFn((pageType: TPageNavigationTabs) => { const { projectId } = this.store.router; if (!projectId) return undefined; // helps to filter pages based on the pageType let pagesByType = filterPagesByPageType(pageType, Object.values(this?.data || {})); pagesByType = pagesByType.filter((p) => p.project === projectId); const pages = (pagesByType.map((page) => page.id) as string[]) || undefined; return pages ?? undefined; }); /** * @description get the current project filtered page ids based on the pageType * @param {TPageNavigationTabs} pageType */ getCurrentProjectFilteredPageIds = computedFn((pageType: TPageNavigationTabs) => { const { projectId } = this.store.router; if (!projectId) return undefined; // helps to filter pages based on the pageType const pagesByType = filterPagesByPageType(pageType, Object.values(this?.data || {})); let filteredPages = pagesByType.filter( (p) => p.project === projectId && p.name?.toLowerCase().includes(this.filters.searchQuery.toLowerCase()) && shouldFilterPage(p, this.filters.filters) ); filteredPages = orderPages(filteredPages, this.filters.sortKey, this.filters.sortBy); const pages = (filteredPages.map((page) => page.id) as string[]) || undefined; return pages ?? undefined; }); /** * @description get the page store by id * @param {string} pageId */ pageById = computedFn((pageId: string) => this.data?.[pageId] || undefined); updateFilters = (filterKey: T, filterValue: TPageFilters[T]) => { runInAction(() => { set(this.filters, [filterKey], filterValue); }); }; /** * @description clear all the filters */ clearAllFilters = () => runInAction(() => { set(this.filters, ["filters"], {}); }); /** * @description fetch all the pages */ getAllPages = async (pageType: TPageNavigationTabs) => { try { const { workspaceSlug, projectId } = this.store.router; if (!workspaceSlug || !projectId) return undefined; const currentPageIds = this.getCurrentProjectPageIds(pageType); runInAction(() => { this.loader = currentPageIds && currentPageIds.length > 0 ? `mutation-loader` : `init-loader`; this.error = undefined; }); const pages = await this.service.fetchAll(workspaceSlug, projectId); runInAction(() => { for (const page of pages) if (page?.id) set(this.data, [page.id], new PageStore(this.store, page)); this.loader = undefined; }); return pages; } catch (error) { runInAction(() => { this.loader = undefined; this.error = { title: "Failed", description: "Failed to fetch the pages, Please try again later.", }; }); throw error; } }; /** * @description fetch the details of a page * @param {string} pageId */ getPageById = async (pageId: string) => { try { const { workspaceSlug, projectId } = this.store.router; if (!workspaceSlug || !projectId || !pageId) return undefined; const currentPageId = this.pageById(pageId); runInAction(() => { this.loader = currentPageId ? `mutation-loader` : `init-loader`; this.error = undefined; }); const page = await this.service.fetchById(workspaceSlug, projectId, pageId); runInAction(() => { if (page?.id) set(this.data, [page.id], new PageStore(this.store, page)); this.loader = undefined; }); return page; } catch (error) { runInAction(() => { this.loader = undefined; this.error = { title: "Failed", description: "Failed to fetch the page, Please try again later.", }; }); throw error; } }; /** * @description create a page * @param {Partial} pageData */ createPage = async (pageData: Partial) => { try { const { workspaceSlug, projectId } = this.store.router; if (!workspaceSlug || !projectId) return undefined; runInAction(() => { this.loader = "mutation-loader"; this.error = undefined; }); const page = await this.service.create(workspaceSlug, projectId, pageData); runInAction(() => { if (page?.id) set(this.data, [page.id], new PageStore(this.store, page)); this.loader = undefined; }); return page; } catch (error) { runInAction(() => { this.loader = undefined; this.error = { title: "Failed", description: "Failed to create a page, Please try again later.", }; }); throw error; } }; /** * @description delete a page * @param {string} pageId */ removePage = async (pageId: string) => { try { const { workspaceSlug, projectId } = this.store.router; if (!workspaceSlug || !projectId || !pageId) return undefined; await this.service.remove(workspaceSlug, projectId, pageId); runInAction(() => unset(this.data, [pageId])); } catch (error) { runInAction(() => { this.loader = undefined; this.error = { title: "Failed", description: "Failed to delete a page, Please try again later.", }; }); throw error; } }; }