fix: pages store structure changes

This commit is contained in:
sriram veeraghanta 2024-01-07 12:05:52 +05:30
parent 0a05aef046
commit b62a1b11b1
5 changed files with 237 additions and 286 deletions

View File

@ -89,7 +89,10 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
const verticalPosition = verticalAlignPosition(_list); const verticalPosition = verticalAlignPosition(_list);
return ( return (
<div className={`relative flex flex-shrink-0 flex-col ${!verticalPosition ? `w-[340px]` : ``} group`}> <div
className={`relative flex flex-shrink-0 flex-col ${!verticalPosition ? `w-[340px]` : ``} group`}
key={_list.id}
>
{sub_group_by === null && ( {sub_group_by === null && (
<div className="sticky top-0 z-[2] w-full flex-shrink-0 bg-custom-background-90 py-1"> <div className="sticky top-0 z-[2] w-full flex-shrink-0 bg-custom-background-90 py-1">
<HeaderGroupByCard <HeaderGroupByCard

View File

@ -0,0 +1,9 @@
import { useContext } from "react";
// mobx store
import { StoreContext } from "contexts/store-context";
export const useProjectPages = () => {
const context = useContext(StoreContext);
if (context === undefined) throw new Error("useProjectPublish must be used within StoreProvider");
return context.projectPages;
};

View File

@ -1,214 +1,95 @@
import { action, computed, makeObservable, observable, runInAction } from "mobx"; import { observable, runInAction } from "mobx";
import set from "lodash/set"; import set from "lodash/set";
import omit from "lodash/omit"; import omit from "lodash/omit";
import isToday from "date-fns/isToday"; import isToday from "date-fns/isToday";
import isThisWeek from "date-fns/isThisWeek"; import isThisWeek from "date-fns/isThisWeek";
import isYesterday from "date-fns/isYesterday"; import isYesterday from "date-fns/isYesterday";
// services
import { IPage } from "@plane/types";
import { PageService } from "services/page.service"; import { PageService } from "services/page.service";
// helpers import { is } from "date-fns/locale";
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
// types
import { IPage, IRecentPages } from "@plane/types";
// store
import { RootStore } from "./root.store";
export interface IPageStore { export interface IPageStore {
pages: Record<string, IPage>; access: number;
archivedPages: Record<string, IPage>; archived_at: string | null;
// project computed color: string;
projectPageIds: string[] | null; created_at: Date;
favoriteProjectPageIds: string[] | null; created_by: string;
privateProjectPageIds: string[] | null; description: string;
publicProjectPageIds: string[] | null; description_html: string;
archivedProjectPageIds: string[] | null; description_stripped: string | null;
recentProjectPages: IRecentPages | null; id: string;
// fetch page information actions is_favorite: boolean;
getUnArchivedPageById: (pageId: string) => IPage | null; is_locked: boolean;
getArchivedPageById: (pageId: string) => IPage | null; labels: string[];
// fetch actions name: string;
fetchProjectPages: (workspaceSlug: string, projectId: string) => Promise<IPage[]>; owned_by: string;
fetchArchivedProjectPages: (workspaceSlug: string, projectId: string) => Promise<IPage[]>; project: string;
// favorites actions updated_at: Date;
addToFavorites: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>; updated_by: string;
removeFromFavorites: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>; workspace: string;
// crud
createPage: (workspaceSlug: string, projectId: string, data: Partial<IPage>) => Promise<IPage>;
updatePage: (workspaceSlug: string, projectId: string, pageId: string, data: Partial<IPage>) => Promise<IPage>;
deletePage: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>;
// access control actions
makePublic: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>;
makePrivate: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>;
// archive actions
archivePage: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>;
restorePage: (workspaceSlug: string, projectId: string, pageId: string) => Promise<void>;
} }
export class PageStore implements IPageStore { export class PageStore {
pages: Record<string, IPage> = {}; access: number;
archivedPages: Record<string, IPage> = {}; archived_at: string | null;
// services color: string;
pageService; created_at: Date;
// stores created_by: string;
rootStore; description: string;
description_html: string;
description_stripped: string | null;
id: string;
is_favorite: boolean;
is_locked: boolean;
labels: string[];
name: string;
owned_by: string;
project: string;
updated_at: Date;
updated_by: string;
workspace: string;
constructor(rootStore: RootStore) { pageService;
makeObservable(this, {
pages: observable, constructor(page: IPage) {
archivedPages: observable, observable(this, {
// computed name: observable.ref,
projectPageIds: computed, description_html: observable.ref,
favoriteProjectPageIds: computed, is_favorite: observable.ref,
publicProjectPageIds: computed, is_locked: observable.ref,
privateProjectPageIds: computed,
archivedProjectPageIds: computed,
recentProjectPages: computed,
// computed actions
getUnArchivedPageById: action,
getArchivedPageById: action,
// fetch actions
fetchProjectPages: action,
fetchArchivedProjectPages: action,
// favorites actions
addToFavorites: action,
removeFromFavorites: action,
// crud
createPage: action,
updatePage: action,
deletePage: action,
// access control actions
makePublic: action,
makePrivate: action,
// archive actions
archivePage: action,
restorePage: action,
}); });
// stores this.created_by = page?.created_by || "";
this.rootStore = rootStore; this.created_at = page?.created_at || new Date();
// services this.color = page?.color || "";
this.archived_at = page?.archived_at || null;
this.name = page?.name || "";
this.description = page?.description || "";
this.description_stripped = page?.description_stripped || "";
this.description_html = page?.description_html || "";
this.access = page?.access || 0;
this.workspace = page?.workspace || "";
this.updated_by = page?.updated_by || "";
this.updated_at = page?.updated_at || new Date();
this.project = page?.project || "";
this.owned_by = page?.owned_by || "";
this.labels = page?.labels || [];
this.is_locked = page?.is_locked || false;
this.id = page?.id || "";
this.is_favorite = page?.is_favorite || false;
this.pageService = new PageService(); this.pageService = new PageService();
} }
/** updateName = async (name: string) => {
* retrieves all pages for a projectId that is available in the url. this.name = name;
*/ await this.pageService.patchPage(this.workspace, this.project, this.id, { name });
get projectPageIds() { };
const projectId = this.rootStore.app.router.projectId;
if (!projectId) return null;
const projectPageIds = Object.keys(this.pages).filter((pageId) => this.pages?.[pageId]?.project === projectId);
return projectPageIds ?? null;
}
/** updateDescription = async (description: string) => {
* retrieves all favorite pages for a projectId that is available in the url. this.description = description;
*/ await this.pageService.patchPage(this.workspace, this.project, this.id, { description });
get favoriteProjectPageIds() { };
if (!this.projectPageIds) return null;
const favoritePagesIds = Object.keys(this.projectPageIds).filter((pageId) => this.pages?.[pageId]?.is_favorite);
return favoritePagesIds ?? null;
}
/**
* retrieves all private pages for a projectId that is available in the url.
*/
get privateProjectPageIds() {
if (!this.projectPageIds) return null;
const privatePagesIds = Object.keys(this.projectPageIds).filter((pageId) => this.pages?.[pageId]?.access === 1);
return privatePagesIds ?? null;
}
/**
* retrieves all shared pages which are public to everyone in the project for a projectId that is available in the url.
*/
get publicProjectPageIds() {
if (!this.projectPageIds) return null;
const publicPagesIds = Object.keys(this.projectPageIds).filter((pageId) => this.pages?.[pageId]?.access === 0);
return publicPagesIds ?? null;
}
/**
* retrieves all recent pages for a projectId that is available in the url.
* In format where today, yesterday, this_week, older are keys.
*/
get recentProjectPages() {
if (!this.projectPageIds) return null;
const data: IRecentPages = { today: [], yesterday: [], this_week: [], older: [] };
data.today = this.projectPageIds.filter((p) => isToday(new Date(this.pages?.[p]?.created_at))) || [];
data.yesterday = this.projectPageIds.filter((p) => isYesterday(new Date(this.pages?.[p]?.created_at))) || [];
data.this_week =
this.projectPageIds.filter((p) => {
const pageCreatedAt = this.pages?.[p]?.created_at;
return (
isThisWeek(new Date(pageCreatedAt)) &&
!isToday(new Date(pageCreatedAt)) &&
!isYesterday(new Date(pageCreatedAt))
);
}) || [];
data.older =
this.projectPageIds.filter((p) => {
const pageCreatedAt = this.pages?.[p]?.created_at;
return !isThisWeek(new Date(pageCreatedAt)) && !isYesterday(new Date(pageCreatedAt));
}) || [];
return data;
}
/**
* retrieves all archived pages for a projectId that is available in the url.
*/
get archivedProjectPageIds() {
const projectId = this.rootStore.app.router.projectId;
if (!projectId) return null;
const archivedProjectPageIds = Object.keys(this.archivedPages).filter(
(pageId) => this.archivedPages?.[pageId]?.project === projectId
);
return archivedProjectPageIds ?? null;
}
/**
* retrieves a page from pages by id.
* @param pageId
* @returns IPage | null
*/
getUnArchivedPageById = (pageId: string) => this.pages?.[pageId] ?? null;
/**
* retrieves a page from archived pages by id.
* @param pageId
* @returns IPage | null
*/
getArchivedPageById = (pageId: string) => this.archivedPages?.[pageId] ?? null;
/**
* fetches all pages for a project.
* @param workspaceSlug
* @param projectId
* @returns Promise<IPage[]>
*/
fetchProjectPages = async (workspaceSlug: string, projectId: string) =>
await this.pageService.getProjectPages(workspaceSlug, projectId).then((response) => {
runInAction(() => {
response.forEach((page) => {
set(this.pages, [page.id], page);
});
});
return response;
});
/**
* fetches all archived pages for a project.
* @param workspaceSlug
* @param projectId
* @returns Promise<IPage[]>
*/
fetchArchivedProjectPages = async (workspaceSlug: string, projectId: string) =>
await this.pageService.getArchivedPages(workspaceSlug, projectId).then((response) => {
runInAction(() => {
response.forEach((page) => {
set(this.archivedPages, [page.id], page);
});
});
return response;
});
/** /**
* Add Page to users favorites list * Add Page to users favorites list
@ -219,13 +100,11 @@ export class PageStore implements IPageStore {
addToFavorites = async (workspaceSlug: string, projectId: string, pageId: string) => { addToFavorites = async (workspaceSlug: string, projectId: string, pageId: string) => {
try { try {
runInAction(() => { runInAction(() => {
set(this.pages, [pageId, "is_favorite"], true); this.is_favorite = true;
}); });
await this.pageService.addPageToFavorites(workspaceSlug, projectId, pageId); await this.pageService.addPageToFavorites(workspaceSlug, projectId, pageId);
} catch (error) { } catch (error) {
runInAction(() => { this.is_favorite = false;
set(this.pages, [pageId, "is_favorite"], false);
});
throw error; throw error;
} }
}; };
@ -238,62 +117,13 @@ export class PageStore implements IPageStore {
*/ */
removeFromFavorites = async (workspaceSlug: string, projectId: string, pageId: string) => { removeFromFavorites = async (workspaceSlug: string, projectId: string, pageId: string) => {
try { try {
runInAction(() => { this.is_favorite = false;
set(this.pages, [pageId, "is_favorite"], false);
});
await this.pageService.removePageFromFavorites(workspaceSlug, projectId, pageId); await this.pageService.removePageFromFavorites(workspaceSlug, projectId, pageId);
} catch (error) { } catch (error) {
runInAction(() => { this.is_favorite = true;
set(this.pages, [pageId, "is_favorite"], true);
});
throw error; throw error;
} }
}; };
/**
* 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>) =>
await this.pageService.createPage(workspaceSlug, projectId, data).then((response) => {
runInAction(() => {
set(this.pages, [response.id], response);
});
return response;
});
/**
* updates the page using the api and updates the local state in store
* @param workspaceSlug
* @param projectId
* @param pageId
* @param data
* @returns
*/
updatePage = async (workspaceSlug: string, projectId: string, pageId: string, data: Partial<IPage>) =>
await this.pageService.patchPage(workspaceSlug, projectId, pageId, data).then((response) => {
const originalPage = this.getUnArchivedPageById(pageId);
runInAction(() => {
set(this.pages, [pageId], { ...originalPage, ...data });
});
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) =>
await this.pageService.deletePage(workspaceSlug, projectId, pageId).then((response) => {
runInAction(() => {
omit(this.archivedPages, [pageId]);
});
return response;
});
/** /**
* make a page public * make a page public
@ -305,12 +135,12 @@ export class PageStore implements IPageStore {
makePublic = async (workspaceSlug: string, projectId: string, pageId: string) => { makePublic = async (workspaceSlug: string, projectId: string, pageId: string) => {
try { try {
runInAction(() => { runInAction(() => {
set(this.pages, [pageId, "access"], 0); this.access = 0;
}); });
await this.pageService.patchPage(workspaceSlug, projectId, pageId, { access: 0 }); await this.pageService.patchPage(workspaceSlug, projectId, pageId, { access: 0 });
} catch (error) { } catch (error) {
runInAction(() => { runInAction(() => {
set(this.pages, [pageId, "access"], 1); this.access = 1;
}); });
throw error; throw error;
} }
@ -326,43 +156,14 @@ export class PageStore implements IPageStore {
makePrivate = async (workspaceSlug: string, projectId: string, pageId: string) => { makePrivate = async (workspaceSlug: string, projectId: string, pageId: string) => {
try { try {
runInAction(() => { runInAction(() => {
set(this.pages, [pageId, "access"], 1); this.access = 1;
}); });
await this.pageService.patchPage(workspaceSlug, projectId, pageId, { access: 1 }); await this.pageService.patchPage(workspaceSlug, projectId, pageId, { access: 1 });
} catch (error) { } catch (error) {
runInAction(() => { runInAction(() => {
set(this.pages, [pageId, "access"], 0); this.access = 0;
}); });
throw error; throw error;
} }
}; };
/**
* Mark a page archived
* @param workspaceSlug
* @param projectId
* @param pageId
*/
archivePage = async (workspaceSlug: string, projectId: string, pageId: string) =>
await this.pageService.archivePage(workspaceSlug, projectId, pageId).then(() => {
runInAction(() => {
set(this.archivedPages, [pageId], this.pages[pageId]);
set(this.archivedPages, [pageId, "archived_at"], renderFormattedPayloadDate(new Date()));
omit(this.pages, [pageId]);
});
});
/**
* Restore a page from archived pages to pages
* @param workspaceSlug
* @param projectId
* @param pageId
*/
restorePage = async (workspaceSlug: string, projectId: string, pageId: string) =>
await this.pageService.restorePage(workspaceSlug, projectId, pageId).then(() => {
runInAction(() => {
set(this.pages, [pageId], this.archivedPages[pageId]);
omit(this.archivedPages, [pageId]);
});
});
} }

View File

@ -0,0 +1,136 @@
import { makeObservable, observable, runInAction, action } from "mobx";
import { set } from "lodash";
// services
import { PageService } from "services/page.service";
// store
import { PageStore, IPageStore } from "store/page.store";
// types
import { IPage } from "@plane/types";
export interface IProjectPageStore {
projectPages: Record<string, IPageStore[]>;
projectArchivedPages: Record<string, IPageStore[]>;
// fetch actions
fetchProjectPages: (workspaceSlug: string, projectId: string) => void;
fetchArchivedProjectPages: (workspaceSlug: string, projectId: string) => void;
// crud actions
createPage: (workspaceSlug: string, projectId: string, data: Partial<IPage>) => void;
deletePage: (workspaceSlug: string, projectId: string, pageId: string) => void;
}
export class ProjectPageStore implements IProjectPageStore {
projectPages: Record<string, IPageStore[]> = {}; // { projectId: [page1, page2] }
projectArchivedPages: Record<string, IPageStore[]> = {}; // { projectId: [page1, page2] }
pageService;
constructor() {
makeObservable(this, {
projectPages: observable,
projectArchivedPages: observable,
// fetch actions
fetchProjectPages: action,
fetchArchivedProjectPages: action,
// crud actions
createPage: action,
deletePage: action,
});
this.pageService = new PageService();
}
/**
* Fetching all the pages for a specific project
* @param workspaceSlug
* @param projectId
*/
fetchProjectPages = async (workspaceSlug: string, projectId: string) => {
const response = await this.pageService.getProjectPages(workspaceSlug, projectId);
runInAction(() => {
this.projectPages[projectId] = response?.map((page) => new PageStore(page));
});
};
/**
* fetches all archived pages for a project.
* @param workspaceSlug
* @param projectId
* @returns Promise<IPage[]>
*/
fetchArchivedProjectPages = async (workspaceSlug: string, projectId: string) =>
await this.pageService.getArchivedPages(workspaceSlug, projectId).then((response) => {
runInAction(() => {
this.projectArchivedPages[projectId] = response?.map((page) => new PageStore(page));
});
return response;
});
/**
* 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(() => {
this.projectPages[projectId] = [...this.projectPages[projectId], new PageStore(response)];
});
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(() => {
this.projectPages = set(
this.projectPages,
[projectId],
this.projectPages[projectId].filter((page) => page.id !== pageId)
);
});
return response;
};
/**
* Mark a page archived
* @param workspaceSlug
* @param projectId
* @param pageId
*/
archivePage = async (workspaceSlug: string, projectId: string, pageId: string) => {
const response = await this.pageService.archivePage(workspaceSlug, projectId, pageId);
runInAction(() => {
set(
this.projectPages,
[projectId],
this.projectPages[projectId].filter((page) => page.id !== pageId)
);
this.projectArchivedPages = set(this.projectArchivedPages, [projectId], this.projectArchivedPages[projectId]);
});
return response;
};
/**
* Restore a page from archived pages to pages
* @param workspaceSlug
* @param projectId
* @param pageId
*/
restorePage = async (workspaceSlug: string, projectId: string, pageId: string) =>
await this.pageService.restorePage(workspaceSlug, projectId, pageId).then(() => {
runInAction(() => {
set(
this.projectArchivedPages,
[projectId],
this.projectArchivedPages[projectId].filter((page) => page.id !== pageId)
);
set(this.projectPages, [projectId], [...this.projectPages[projectId]]);
});
});
}

View File

@ -16,6 +16,7 @@ import { IInboxRootStore, InboxRootStore } from "./inbox";
import { IEstimateStore, EstimateStore } from "./estimate.store"; import { IEstimateStore, EstimateStore } from "./estimate.store";
import { GlobalViewStore, IGlobalViewStore } from "./global-view.store"; import { GlobalViewStore, IGlobalViewStore } from "./global-view.store";
import { IMentionStore, MentionStore } from "./mention.store"; import { IMentionStore, MentionStore } from "./mention.store";
import { IProjectPageStore, ProjectPageStore } from "./project-page.store";
enableStaticRendering(typeof window === "undefined"); enableStaticRendering(typeof window === "undefined");
@ -31,11 +32,12 @@ export class RootStore {
module: IModuleStore; module: IModuleStore;
projectView: IProjectViewStore; projectView: IProjectViewStore;
globalView: IGlobalViewStore; globalView: IGlobalViewStore;
page: IPageStore; // page: IPageStore;
issue: IIssueRootStore; issue: IIssueRootStore;
state: IStateStore; state: IStateStore;
estimate: IEstimateStore; estimate: IEstimateStore;
mention: IMentionStore; mention: IMentionStore;
projectPages: IProjectPageStore;
constructor() { constructor() {
this.app = new AppRootStore(this); this.app = new AppRootStore(this);
@ -50,10 +52,10 @@ export class RootStore {
this.module = new ModulesStore(this); this.module = new ModulesStore(this);
this.projectView = new ProjectViewStore(this); this.projectView = new ProjectViewStore(this);
this.globalView = new GlobalViewStore(this); this.globalView = new GlobalViewStore(this);
this.page = new PageStore(this);
this.issue = new IssueRootStore(this); this.issue = new IssueRootStore(this);
this.state = new StateStore(this); this.state = new StateStore(this);
this.estimate = new EstimateStore(this); this.estimate = new EstimateStore(this);
this.mention = new MentionStore(this); this.mention = new MentionStore(this);
this.projectPages = new ProjectPageStore();
} }
} }