From f05b8de91dd6659a0eb872a8d7f1565da1819b3c Mon Sep 17 00:00:00 2001 From: gurusainath Date: Wed, 7 Feb 2024 11:30:06 +0530 Subject: [PATCH] chore: implemented view create and edit operations and updated the store --- packages/types/src/view/base.d.ts | 1 + web/components/profile/sidebar.tsx | 4 +- web/components/view/all-issues-root.tsx | 107 ++++--- .../view/applied-filters/filter.tsx | 25 +- .../view/applied-filters/helper.tsx | 8 +- web/components/view/applied-filters/root.tsx | 18 +- web/components/view/index.ts | 5 +- web/components/view/types.d.ts | 10 +- .../view/views/create-edit-form.tsx | 85 +++-- web/components/view/views/create-edit.tsx | 88 ------ .../view/views/dropdown/dropdown-item.tsx | 74 +++++ web/components/view/views/dropdown/root.tsx | 125 ++++++++ web/components/view/views/root.tsx | 94 ++++-- web/components/view/views/view-item.tsx | 44 +-- web/constants/view/root.ts | 6 +- web/lib/app-provider.tsx | 5 +- web/store/view/root.store.ts | 24 +- web/store/view/user/view-root.store.ts | 16 +- web/store/view/user/view.store.ts | 296 +++++++++++++----- web/store/view/view-root.store.ts | 115 ++++--- web/store/view/view.store.ts | 160 ++++++---- 21 files changed, 874 insertions(+), 436 deletions(-) delete mode 100644 web/components/view/views/create-edit.tsx create mode 100644 web/components/view/views/dropdown/dropdown-item.tsx create mode 100644 web/components/view/views/dropdown/root.tsx diff --git a/packages/types/src/view/base.d.ts b/packages/types/src/view/base.d.ts index c237bdcd7..d41cba270 100644 --- a/packages/types/src/view/base.d.ts +++ b/packages/types/src/view/base.d.ts @@ -44,4 +44,5 @@ export type TView = { // local view variables is_local_view: boolean; is_create: boolean; + is_editable: boolean; }; diff --git a/web/components/profile/sidebar.tsx b/web/components/profile/sidebar.tsx index 3ce7747c9..8371900d3 100644 --- a/web/components/profile/sidebar.tsx +++ b/web/components/profile/sidebar.tsx @@ -48,7 +48,9 @@ export const ProfileSidebar = observer(() => { ]; return ( -
+
{userProjectsData ? ( <>
diff --git a/web/components/view/all-issues-root.tsx b/web/components/view/all-issues-root.tsx index 337b991aa..4d94759dc 100644 --- a/web/components/view/all-issues-root.tsx +++ b/web/components/view/all-issues-root.tsx @@ -5,11 +5,11 @@ import { CheckCircle } from "lucide-react"; import { useView, useViewDetail } from "hooks/store"; import useToast from "hooks/use-toast"; // components -import { ViewRoot, ViewCreateEdit, ViewFiltersRoot, ViewAppliedFiltersRoot, ViewLayoutRoot } from "."; +import { ViewRoot, ViewCreateEditForm, ViewAppliedFiltersRoot, ViewLayoutRoot } from "."; // ui import { Spinner } from "@plane/ui"; // constants -import { VIEW_TYPES } from "constants/view"; +import { VIEW_TYPES, viewLocalPayload } from "constants/view"; // types import { TViewOperations } from "./types"; import { TView, TViewFilters, TViewDisplayFilters, TViewDisplayProperties } from "@plane/types"; @@ -23,6 +23,7 @@ type TAllIssuesViewRoot = { export const AllIssuesViewRoot: FC = observer((props) => { const { workspaceSlug, projectId, viewId } = props; // states + const [currentCreateEditViewId, setCurrentCreateEditViewId] = useState(undefined); const [viewType, setViewType] = useState(VIEW_TYPES.WORKSPACE_VIEWS); const workspaceViewTabOptions = [ { @@ -43,18 +44,45 @@ export const AllIssuesViewRoot: FC = observer((props) => { const viewOperations: TViewOperations = useMemo( () => ({ - localViewCreate: (data) => viewStore?.localViewCreate(data), - clearLocalView: (viewId: string) => viewStore?.clearLocalView(viewId), + setName: (name: string) => viewDetailStore?.setName(name), + setDescription: (name: string) => viewDetailStore?.setDescription(name), setFilters: (filters: Partial) => viewDetailStore?.setFilters(filters), setDisplayFilters: (display_filters: Partial) => viewDetailStore?.setDisplayFilters(display_filters), setDisplayProperties: (display_properties: Partial) => viewDetailStore?.setDisplayProperties(display_properties), + localViewCreateEdit: (viewId: string | undefined) => { + if (viewId === undefined) { + const viewPayload = viewLocalPayload; + setCurrentCreateEditViewId(viewPayload.id); + viewStore?.localViewCreate(viewPayload as TView); + } else setCurrentCreateEditViewId(viewId); + }, + localViewCreateEditClear: async (viewId: string | undefined) => { + console.log("viewId", viewId); + if (viewId) viewStore?.remove(viewId); + setCurrentCreateEditViewId(undefined); + }, fetch: async () => await viewStore?.fetch(), create: async (data: Partial) => { try { await viewStore?.create(data); - if (data.id) viewOperations.clearLocalView(data.id); + setCurrentCreateEditViewId(undefined); + } catch { + setToastAlert({ title: "Error", message: "Error creating view", type: "error" }); + } + }, + update: async () => { + try { + await viewDetailStore?.saveChanges(); + setCurrentCreateEditViewId(undefined); + } catch { + setToastAlert({ title: "Error", message: "Error creating view", type: "error" }); + } + }, + remove: async (viewId: string) => { + try { + await viewStore?.remove(viewId); } catch { setToastAlert({ title: "Error", message: "Error creating view", type: "error" }); } @@ -77,15 +105,15 @@ export const AllIssuesViewRoot: FC = observer((props) => {
All Issues
-
+
{workspaceViewTabOptions.map((tab) => (
@@ -101,23 +129,18 @@ export const AllIssuesViewRoot: FC = observer((props) => {
) : ( <> - - - {/* */} +
+ +
-
+ {/*
= observer((props) => { viewType={viewType} viewOperations={viewOperations} /> -
+
*/} -
+ {/*
= observer((props) => { viewType={viewType} viewOperations={viewOperations} /> -
+
*/} -
+ {/*
Filters -
+
*/} -
+ {/*
Display Filters -
+
*/} - {!viewDetailStore?.is_local_view && ( + {/* {!viewDetailStore?.is_local_view && (
- -
Edit
-
+
Edit
- )} + )} */}
)} + + {currentCreateEditViewId != undefined && ( + + )}
); }); diff --git a/web/components/view/applied-filters/filter.tsx b/web/components/view/applied-filters/filter.tsx index a25f1b33d..88ddaa09a 100644 --- a/web/components/view/applied-filters/filter.tsx +++ b/web/components/view/applied-filters/filter.tsx @@ -1,4 +1,4 @@ -import { FC } from "react"; +import { FC, Fragment } from "react"; import { observer } from "mobx-react-lite"; import isEmpty from "lodash/isEmpty"; import { X } from "lucide-react"; @@ -33,21 +33,20 @@ export const ViewAppliedFilters: FC = observer((props) => { if (!filterKeyValue || filterKeyValue.length <= -1) return <>; return ( -
+
{generateTitle(filterKey)}
{["1", "2", "3", "4"].map((filterId) => ( - + + + ))}
{ +export const filterOptions = (key: keyof TViewFilters, selectedFilters: string[]) => { switch (key) { case "project": return []; @@ -45,12 +45,12 @@ export const generateTitle = (title: string) => .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(" "); -export const constructAppliedFilters = (filters: TFilters): TComputedAppliedFilters => { +export const constructAppliedFilters = (filters: TViewFilters): TComputedAppliedFilters => { const appliedFilters: TComputedAppliedFilters = []; if (filters && !isEmpty(filters)) { Object.keys(filters).forEach((_filterKey) => { - const _key = _filterKey as keyof TFilters; + const _key = _filterKey as keyof TViewFilters; const _value = filters[_key]; if (_value && !isEmpty(_value)) { diff --git a/web/components/view/applied-filters/root.tsx b/web/components/view/applied-filters/root.tsx index a2c4c3e54..4e977b7e6 100644 --- a/web/components/view/applied-filters/root.tsx +++ b/web/components/view/applied-filters/root.tsx @@ -1,4 +1,4 @@ -import { FC } from "react"; +import { FC, Fragment } from "react"; import { observer } from "mobx-react-lite"; import { X } from "lucide-react"; import isEmpty from "lodash/isEmpty"; @@ -43,13 +43,15 @@ export const ViewAppliedFiltersRoot: FC = observer((pro {filterKeys.map((key) => { const filterKey = key as keyof TViewFilters; return ( - + + + ); })}
void; - clearLocalView: (viewId: string) => void; + setName: (name: string) => void; + setDescription: (description: string) => void; setFilters: (filters: Partial) => void; setDisplayFilters: (display_filters: Partial) => void; setDisplayProperties: (display_properties: Partial) => void; + + localViewCreateEdit: (viewId: string | undefined) => void; + localViewCreateEditClear: (viewId: string | undefined) => Promise; + fetch: () => Promise; create: (data: Partial) => Promise; + update: () => Promise; + remove: (viewId: string) => Promise; }; diff --git a/web/components/view/views/create-edit-form.tsx b/web/components/view/views/create-edit-form.tsx index 33700cdcf..78ce4fd93 100644 --- a/web/components/view/views/create-edit-form.tsx +++ b/web/components/view/views/create-edit-form.tsx @@ -1,15 +1,15 @@ -import { FC, Fragment } from "react"; +import { FC, Fragment, useCallback, useEffect, useState } from "react"; import { observer } from "mobx-react-lite"; import { Dialog, Transition } from "@headlessui/react"; -import { Trash2, Plus, X } from "lucide-react"; +import { Briefcase, Globe2, Plus, X } from "lucide-react"; // hooks -import { useViewDetail } from "hooks/store"; +import { useViewDetail, useProject } from "hooks/store"; // components import { ViewAppliedFiltersRoot } from "../"; // ui import { Input, Button } from "@plane/ui"; // types -import { TView, TViewTypes } from "@plane/types"; +import { TViewTypes } from "@plane/types"; import { TViewOperations } from "../types"; type TViewCreateEditForm = { @@ -18,28 +18,50 @@ type TViewCreateEditForm = { viewId: string; viewType: TViewTypes; viewOperations: TViewOperations; - modalToggle: boolean; - handleModalClose: () => void; - onSubmit: (viewData: Partial) => void; }; export const ViewCreateEditForm: FC = observer((props) => { - const { workspaceSlug, projectId, viewId, viewType, viewOperations, modalToggle, handleModalClose, onSubmit } = props; + const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props; // hooks const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + const { getProjectById } = useProject(); + // states + const [modalToggle, setModalToggle] = useState(false); + const [loader, setLoader] = useState(false); + + const modalOpen = useCallback(() => setModalToggle(true), [setModalToggle]); + const modalClose = useCallback(() => { + setModalToggle(false); + setTimeout(() => { + viewOperations.localViewCreateEditClear(viewId); + }, 200); + }, [viewId, setModalToggle, viewOperations]); + + useEffect(() => { + if (viewId) modalOpen(); + }, [viewId, modalOpen, modalClose]); const onContinue = async () => { - const payload: Partial = { - id: viewDetailStore?.id, - name: viewDetailStore?.name, - filters: viewDetailStore?.filters, - }; - onSubmit(payload); + setLoader(true); + if (viewDetailStore?.is_create) { + const payload = viewDetailStore?.filtersToUpdate; + await viewOperations.create(payload); + modalClose(); + } else { + const payload = viewDetailStore?.filtersToUpdate; + if (!payload) return; + await viewOperations.update(); + modalClose(); + } + setLoader(false); }; + const projectDetails = projectId ? getProjectById(projectId) : undefined; + + if (!viewDetailStore?.id) return <>; return ( - + = observer((props) => { >
- {/*
-
- + {projectId && projectDetails ? ( +
+
+ +
+
{projectDetails?.identifier || "Project"}
-
Project Identifier
-
*/} -
Create View
+ ) : ( +
+
+ +
+
Workspace
+
+ )} +
Save View
@@ -79,7 +110,7 @@ export const ViewCreateEditForm: FC = observer((props) => { id="name" name="name" type="text" - value={viewDetailStore?.name || ""} + value={viewDetailStore?.filtersToUpdate?.name || ""} onChange={(e) => { viewDetailStore?.setName(e.target.value); }} @@ -90,13 +121,13 @@ export const ViewCreateEditForm: FC = observer((props) => {
-
+
Filters
-
+
Clear all filters
@@ -115,11 +146,11 @@ export const ViewCreateEditForm: FC = observer((props) => {
- -
diff --git a/web/components/view/views/create-edit.tsx b/web/components/view/views/create-edit.tsx deleted file mode 100644 index 159790f00..000000000 --- a/web/components/view/views/create-edit.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { FC, ReactNode, useState } from "react"; -import { observer } from "mobx-react-lite"; -import { Plus } from "lucide-react"; -// ui -import { Button } from "@plane/ui"; -// components -import { ViewCreateEditForm } from "./create-edit-form"; -// constants -import { viewLocalPayload } from "constants/view"; -// types -import { TViewOperations } from "../types"; -import { TView, TViewFilters, TViewTypes } from "@plane/types"; - -type TViewCreateEdit = { - workspaceSlug: string; - projectId: string | undefined; - viewId: string | undefined; - viewType: TViewTypes; - viewOperations: TViewOperations; - children?: ReactNode; -}; - -export const ViewCreateEdit: FC = observer((props) => { - const { workspaceSlug, projectId, viewId, viewType, viewOperations, children } = props; - // states - const [currentViewId, setCurrentViewId] = useState(); - const [currentFilters, setCurrentFilters] = useState>({}); - const [modalToggle, setModalToggle] = useState(false); - - const handleModalOpen = () => { - if (viewId === undefined) { - const viewPayload = viewLocalPayload; - setCurrentViewId(viewPayload.id); - viewOperations?.localViewCreate(viewPayload as TView); - } else { - setCurrentViewId(viewId); - } - setModalToggle(true); - }; - - const handleModalClose = () => { - if (viewId === undefined) { - if (currentViewId) viewOperations?.clearLocalView(currentViewId); - } else { - } - setModalToggle(false); - setCurrentViewId(undefined); - setCurrentFilters({}); - }; - - const onSubmit = async (viewData: Partial) => { - if (!viewData?.name) return; - try { - await viewOperations.create(viewData); - handleModalClose(); - } catch (error) { - console.log(error); - } - }; - - return ( - <> - {currentViewId && ( - - )} - -
- {children ? ( - children - ) : ( - - )} -
- - ); -}); diff --git a/web/components/view/views/dropdown/dropdown-item.tsx b/web/components/view/views/dropdown/dropdown-item.tsx new file mode 100644 index 000000000..8dab34bf2 --- /dev/null +++ b/web/components/view/views/dropdown/dropdown-item.tsx @@ -0,0 +1,74 @@ +import { FC } from "react"; +import Link from "next/link"; +import { Combobox } from "@headlessui/react"; +import { GripVertical, MoreVertical } from "lucide-react"; +// hooks +import { useViewDetail } from "hooks/store"; +// ui +import { PhotoFilterIcon, Tooltip } from "@plane/ui"; +// types +import { TViewTypes } from "@plane/types"; + +type TViewDropdownItem = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string; + viewType: TViewTypes; + currentViewId: string | undefined; + searchQuery: string; +}; + +export const ViewDropdownItem: FC = (props) => { + const { workspaceSlug, projectId, viewId, viewType, currentViewId, searchQuery } = props; + // hooks + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewId, viewType); + + const isDragEnabled = false; + const isEditable = !viewDetailStore?.is_local_view || false; + + if (!viewDetailStore) return <>; + if (!searchQuery || (searchQuery && viewDetailStore?.name?.toLowerCase().includes(searchQuery.toLowerCase()))) + return ( + + +
+ {isDragEnabled && ( +
+ +
+ )} + +
+ +
+ +
+ {viewDetailStore?.name} +
+ +
+
+ + {isEditable && ( +
+ +
+ )} +
+ ); + return <>; +}; diff --git a/web/components/view/views/dropdown/root.tsx b/web/components/view/views/dropdown/root.tsx new file mode 100644 index 000000000..cbe538d89 --- /dev/null +++ b/web/components/view/views/dropdown/root.tsx @@ -0,0 +1,125 @@ +import { FC, Fragment, ReactNode, useRef, useState } from "react"; +import { Combobox } from "@headlessui/react"; +import { usePopper } from "react-popper"; +import { Plus, Search } from "lucide-react"; +// hooks +import useOutsideClickDetector from "hooks/use-outside-click-detector"; +import { useView } from "hooks/store"; +// components +import { ViewDropdownItem } from "../../"; +// types +import { TViewTypes } from "@plane/types"; +import { TViewOperations } from "../../types"; + +type TViewDropdown = { + workspaceSlug: string; + projectId: string | undefined; + viewId: string | undefined; + viewType: TViewTypes; + viewOperations: TViewOperations; + children?: ReactNode; +}; + +export const ViewDropdown: FC = (props) => { + const { workspaceSlug, projectId, viewId: currentViewId, viewType, viewOperations, children } = props; + // hooks + const viewStore = useView(workspaceSlug, projectId, viewType); + // states + const [dropdownToggle, setDropdownToggle] = useState(false); + const [query, setQuery] = useState(""); + // refs + const dropdownRef = useRef(null); + // popper-js refs + const [referenceElement, setReferenceElement] = useState(null); + const [popperElement, setPopperElement] = useState(null); + // popper-js init + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: "bottom-start", + modifiers: [ + { + name: "preventOverflow", + options: { + padding: 12, + }, + }, + ], + }); + + const handleDropdownOpen = () => setDropdownToggle(true); + const handleDropdownClose = () => setDropdownToggle(false); + const handleDropdownToggle = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + if (!dropdownToggle) handleDropdownOpen(); + else handleDropdownClose(); + }; + + useOutsideClickDetector(dropdownRef, handleDropdownClose); + + return ( + + + + + + {dropdownToggle && ( + +
+
+ + setQuery(e.target.value)} + placeholder="Search for a view..." + displayValue={(assigned: any) => assigned?.name} + autoFocus + /> +
+ +
+ {viewStore?.viewIds && + viewStore?.viewIds.length > 0 && + viewStore?.viewIds.map((viewId) => ( + + + + ))} +
+ +
viewOperations?.localViewCreateEdit(undefined)} + > + +
New view
+
+
+
+ )} +
+ ); +}; diff --git a/web/components/view/views/root.tsx b/web/components/view/views/root.tsx index a3f656f6c..40edcca58 100644 --- a/web/components/view/views/root.tsx +++ b/web/components/view/views/root.tsx @@ -1,9 +1,12 @@ -import { FC } from "react"; +import { FC, Fragment, useEffect, useState } from "react"; import { observer } from "mobx-react-lite"; +import { Plus } from "lucide-react"; // hooks import { useView } from "hooks/store"; // components -import { ViewItem, ViewCreateEdit } from "../"; +import { ViewItem, ViewDropdown } from "../"; +// ui +import { Button } from "@plane/ui"; // types import { TViewOperations } from "../types"; import { TViewTypes } from "@plane/types"; @@ -20,31 +23,84 @@ export const ViewRoot: FC = observer((props) => { const { workspaceSlug, projectId, viewId, viewType, viewOperations } = props; // hooks const viewStore = useView(workspaceSlug, projectId, viewType); + // state + const [itemsToRenderViewsCount, setItemsToRenderViewCount] = useState(0); + + useEffect(() => { + const handleViewTabsVisibility = () => { + const tabContainer = document.getElementById("tab-container"); + const tabItemViewMore = document.getElementById("tab-item-view-more"); + const itemWidth = 124; + if (!tabContainer || !tabItemViewMore) return; + + const containerWidth = tabContainer.clientWidth; + const itemViewMoreLeftOffset = tabItemViewMore.offsetLeft; + const itemViewMoreRightOffset = containerWidth - itemViewMoreLeftOffset; + + if (itemViewMoreLeftOffset + (tabItemViewMore.clientWidth + 10) > containerWidth) { + const itemsToRender = Math.floor(containerWidth / itemWidth); + setItemsToRenderViewCount(itemsToRender); + } + if (itemViewMoreRightOffset > itemWidth + 10) { + const itemsToRenderLeft = Math.floor(itemViewMoreLeftOffset / itemWidth) || 0; + const itemsToRenderRight = Math.floor(itemViewMoreRightOffset / itemWidth) || 0; + setItemsToRenderViewCount(itemsToRenderLeft + itemsToRenderRight); + } + }; + + window.addEventListener("resize", () => handleViewTabsVisibility()); + handleViewTabsVisibility(); + + return () => window.removeEventListener("resize", () => handleViewTabsVisibility()); + }, [viewStore?.viewIds]); return ( -
+
{viewStore?.viewIds && viewStore?.viewIds.length > 0 && ( -
- {viewStore?.viewIds.map((_viewId) => ( - +
+ {viewStore?.viewIds?.slice(0, itemsToRenderViewsCount || viewStore?.viewIds.length).map((_viewId) => ( + + + ))} + +
+ {viewStore?.viewIds.length <= (itemsToRenderViewsCount || viewStore?.viewIds.length) ? null : ( + +
+ + + + + {viewStore?.viewIds.length - (itemsToRenderViewsCount || viewStore?.viewIds.length)} More... + +
+
+ )} +
)}
- +
); diff --git a/web/components/view/views/view-item.tsx b/web/components/view/views/view-item.tsx index 474bc29a2..b26054336 100644 --- a/web/components/view/views/view-item.tsx +++ b/web/components/view/views/view-item.tsx @@ -1,10 +1,10 @@ -import { FC } from "react"; +import { FC, Fragment } from "react"; import Link from "next/link"; import { observer } from "mobx-react-lite"; // hooks -import { useView } from "hooks/store"; +import { useView, useViewDetail } from "hooks/store"; // ui -import { PhotoFilterIcon } from "@plane/ui"; +import { PhotoFilterIcon, Tooltip } from "@plane/ui"; // types import { TViewTypes } from "@plane/types"; @@ -19,27 +19,31 @@ type TViewItem = { export const ViewItem: FC = observer((props) => { const { workspaceSlug, projectId, viewId, viewType, viewItemId } = props; // hooks - const viewStore = useView(workspaceSlug, projectId, viewType); + const viewDetailStore = useViewDetail(workspaceSlug, projectId, viewItemId, viewType); - const view = viewStore?.viewById(viewItemId); - - if (!view) return <>; + if (!viewDetailStore) return <>; return ( -
- + + viewItemId === viewId && e.preventDefault()} - > -
- -
-
- {view?.name} -
- + onClick={(e) => viewItemId === viewId && e.preventDefault()} + > +
+ +
+
+ {viewDetailStore?.name} +
+ +
); diff --git a/web/constants/view/root.ts b/web/constants/view/root.ts index 265d92f92..1024ca928 100644 --- a/web/constants/view/root.ts +++ b/web/constants/view/root.ts @@ -13,9 +13,9 @@ export const viewLocalPayload: Partial = { id: uuidV4(), name: "", description: "", - filters: {}, - display_filters: {}, - display_properties: {}, + filters: undefined, + display_filters: undefined, + display_properties: undefined, is_local_view: false, is_create: true, }; diff --git a/web/lib/app-provider.tsx b/web/lib/app-provider.tsx index 864c87f27..070f04c3d 100644 --- a/web/lib/app-provider.tsx +++ b/web/lib/app-provider.tsx @@ -47,7 +47,7 @@ export const AppProvider: FC = observer((props) => { - = observer((props) => { posthogHost={envConfig?.posthog_host || null} > {children} - + */} + {children} diff --git a/web/store/view/root.store.ts b/web/store/view/root.store.ts index 535010af6..69a8f5dae 100644 --- a/web/store/view/root.store.ts +++ b/web/store/view/root.store.ts @@ -30,13 +30,7 @@ export class GlobalViewRootStore { cycleUserViewStore?: userViewRootStore; constructor(private store: RootStore) { - const defaultViews: any[] = [ - { - id: "all-issues", - name: "All Issues", - filters: {}, - is_local_view: true, - }, + const workspaceViewMeStoreDefaultViews: any[] = [ { id: "assigned", name: "Assigned", @@ -62,9 +56,21 @@ export class GlobalViewRootStore { is_local_view: true, }, ]; + const workspaceViewStoreDefaultViews: any[] = [ + { + id: "all-issues", + name: "All Issues", + filters: {}, + is_local_view: true, + }, + ]; - this.workspaceViewMeStore = new ViewRootStore(this.store, new WorkspaceMeViewService()); - this.workspaceViewStore = new ViewRootStore(this.store, new WorkspaceViewService(), defaultViews); + this.workspaceViewMeStore = new ViewRootStore( + this.store, + new WorkspaceMeViewService(), + workspaceViewMeStoreDefaultViews + ); + this.workspaceViewStore = new ViewRootStore(this.store, new WorkspaceViewService(), workspaceViewStoreDefaultViews); this.projectViewStore = new ViewRootStore(this.store, new ProjectViewService()); this.projectViewMeStore = new ViewRootStore(this.store, new ProjectViewMeService()); diff --git a/web/store/view/user/view-root.store.ts b/web/store/view/user/view-root.store.ts index 7797f3b04..d5322b016 100644 --- a/web/store/view/user/view-root.store.ts +++ b/web/store/view/user/view-root.store.ts @@ -51,13 +51,13 @@ export class userViewRootStore implements TUserViewRootStore { const view = await this.service.fetch(this.workspaceSlug, this.projectId, this.featureId); if (!view) return; - runInAction(() => { - if (this.workspaceSlug && view.id) - set( - this.viewMap, - [view.id], - new UserViewStore(view, this.service, this.workspaceSlug, this.projectId, this.featureId) - ); - }); + // runInAction(() => { + // if (this.workspaceSlug && view.id) + // set( + // this.viewMap, + // [view.id], + // new UserViewStore(view, this.service, this.workspaceSlug, this.projectId, this.featureId) + // ); + // }); }; } diff --git a/web/store/view/user/view.store.ts b/web/store/view/user/view.store.ts index a44ee0fb0..c3c6d2972 100644 --- a/web/store/view/user/view.store.ts +++ b/web/store/view/user/view.store.ts @@ -1,109 +1,155 @@ import { action, computed, makeObservable, observable, runInAction } from "mobx"; import set from "lodash/set"; +// store +import { RootStore } from "store/root.store"; // types -import { TUserViewService } from "services/view/types"; +import { TViewService } from "services/view/types"; import { - TUserView, + TView, TViewFilters, TViewDisplayFilters, TViewDisplayProperties, TViewFilterProps, - TViewFilterPartialProps, + TViewAccess, } from "@plane/types"; // helpers import { FiltersHelper } from "../helpers/filters_helpers"; type TLoader = "submitting" | "submit" | undefined; -export type TUserViewStore = TUserView & { +export type TUserViewStore = TView & { // observables loader: TLoader; - filtersToUpdate: TViewFilterPartialProps; + filtersToUpdate: Partial; // computed appliedFilters: TViewFilterProps | undefined; appliedFiltersQueryParams: string | undefined; // helper actions - updateFilters: (filters: Partial) => void; - updateDisplayFilters: (display_filters: Partial) => void; - updateDisplayProperties: (display_properties: Partial) => void; - resetFilterChanges: () => void; - saveFilterChanges: () => void; + setName: (name: string) => void; + setDescription: (description: string) => void; + setFilters: (filters: Partial) => void; + setDisplayFilters: (display_filters: Partial) => void; + setDisplayProperties: (display_properties: Partial) => void; + resetChanges: () => void; + saveChanges: () => Promise; // actions - update: (viewData: Partial) => Promise; + lockView: () => Promise; + unlockView: () => Promise; + makeFavorite: () => Promise; + removeFavorite: () => Promise; + update: (viewData: Partial) => Promise; }; export class UserViewStore extends FiltersHelper implements TUserViewStore { id: string | undefined; workspace: string | undefined; project: string | undefined; - module: string | undefined; - cycle: string | undefined; - filters: TViewFilters | undefined; - display_filters: TViewDisplayFilters | undefined; - display_properties: TViewDisplayProperties | undefined; - user: string | undefined; + name: string | undefined; + description: string | undefined; + query: string | undefined; + filters: TViewFilters; + display_filters: TViewDisplayFilters; + display_properties: TViewDisplayProperties; + access: TViewAccess | undefined; + owned_by: string | undefined; + sort_order: number | undefined; + is_locked: boolean = false; + is_pinned: boolean = false; + is_favorite: boolean = false; created_by: string | undefined; updated_by: string | undefined; created_at: Date | undefined; updated_at: Date | undefined; - + is_local_view: boolean = false; + is_create: boolean = false; + is_editable: boolean = false; loader: TLoader = undefined; - filtersToUpdate: TViewFilterPartialProps = { - filters: {}, - display_filters: {}, - display_properties: {}, + filtersToUpdate: Partial = { + name: "", + description: "", + filters: undefined, + display_filters: undefined, + display_properties: undefined, }; - constructor( - _view: TUserView, - private service: TUserViewService, - private workspaceSlug: string, - private projectId: string | undefined, - private featureId: string | undefined // moduleId/cycleId - ) { + constructor(private store: RootStore, _view: TView, private service: TViewService) { super(); this.id = _view.id; this.workspace = _view.workspace; this.project = _view.project; - this.filters = _view.filters ? this.computedFilters(_view.filters) : undefined; - this.display_filters = _view.display_filters ? this.computedDisplayFilters(_view.display_filters) : undefined; - this.display_properties = _view.display_properties - ? this.computedDisplayProperties(_view.display_properties) - : undefined; - this.user = _view.user; + this.name = _view.name; + this.description = _view.description; + this.query = _view.query; + this.filters = this.computedFilters(_view.filters); + this.display_filters = this.computedDisplayFilters(_view.display_filters); + this.display_properties = this.computedDisplayProperties(_view.display_properties); + this.access = _view.access; + this.owned_by = _view.owned_by; + this.sort_order = _view.sort_order; + this.is_locked = _view.is_locked; + this.is_pinned = _view.is_pinned; + this.is_favorite = _view.is_favorite; this.created_by = _view.created_by; this.updated_by = _view.updated_by; this.created_at = _view.created_at; this.updated_at = _view.updated_at; + this.is_local_view = _view.is_local_view; + this.is_create = _view.is_create; + this.is_editable = _view.is_editable; + makeObservable(this, { // observables - loader: observable, - filtersToUpdate: observable.ref, + id: observable.ref, + workspace: observable.ref, + project: observable.ref, + name: observable.ref, + description: observable.ref, + query: observable.ref, + filters: observable, + display_filters: observable, + display_properties: observable, + access: observable.ref, + owned_by: observable.ref, + sort_order: observable.ref, + is_locked: observable.ref, + is_pinned: observable.ref, + is_favorite: observable.ref, + created_by: observable.ref, + updated_by: observable.ref, + created_at: observable.ref, + updated_at: observable.ref, + is_local_view: observable.ref, + is_create: observable.ref, + is_editable: observable.ref, + loader: observable.ref, + filtersToUpdate: observable, // computed appliedFilters: computed, appliedFiltersQueryParams: computed, // helper actions - updateFilters: action, - updateDisplayFilters: action, - updateDisplayProperties: action, - resetFilterChanges: action, - saveFilterChanges: action, + setName: action, + setFilters: action, + setDisplayFilters: action, + setDisplayProperties: action, + resetChanges: action, + saveChanges: action, // actions update: action, + lockView: action, + unlockView: action, }); } // computed get appliedFilters() { return { - filters: this.filters ? this.computedFilters(this.filters, this.filtersToUpdate.filters) : undefined, - display_filters: this.display_filters - ? this.computedDisplayFilters(this.display_filters, this.filtersToUpdate.display_filters) - : undefined, - display_properties: this.display_properties - ? this.computedDisplayProperties(this.display_properties, this.filtersToUpdate.display_properties) - : undefined, + filters: this.computedFilters(this.filters, this.filtersToUpdate.filters), + display_filters: this.computedDisplayFilters(this.display_filters, this.filtersToUpdate.display_filters), + display_properties: this.computedDisplayProperties( + this.display_properties, + this.filtersToUpdate.display_properties + ), }; } @@ -114,14 +160,29 @@ export class UserViewStore extends FiltersHelper implements TUserViewStore { } // helper actions - updateFilters = (filters: Partial) => { + setName = (name: string) => { runInAction(() => { - this.loader = "submit"; - this.filtersToUpdate.filters = filters; + this.filtersToUpdate.name = name; }); }; - updateDisplayFilters = async (display_filters: Partial) => { + setDescription = (description: string) => { + runInAction(() => { + this.filtersToUpdate.description = description; + }); + }; + + setFilters = (filters: Partial) => { + runInAction(() => { + this.loader = "submit"; + Object.keys(filters).forEach((key) => { + const _key = key as keyof TViewFilters; + set(this.filtersToUpdate, ["filters", _key], filters[_key]); + }); + }); + }; + + setDisplayFilters = async (display_filters: Partial) => { const appliedFilters = this.appliedFilters; const layout = appliedFilters?.display_filters?.layout; @@ -129,7 +190,7 @@ export class UserViewStore extends FiltersHelper implements TUserViewStore { const group_by = appliedFilters?.display_filters?.group_by; const sub_issue = appliedFilters?.display_filters?.sub_issue; - if (group_by === undefined) display_filters.sub_group_by = undefined; + if (group_by === undefined && display_filters.sub_group_by) display_filters.sub_group_by = undefined; if (layout === "kanban") { if (sub_group_by === group_by) display_filters.group_by = undefined; if (group_by === null) display_filters.group_by = "state"; @@ -137,47 +198,130 @@ export class UserViewStore extends FiltersHelper implements TUserViewStore { if (layout === "spreadsheet" && sub_issue === true) display_filters.sub_issue = false; runInAction(() => { - this.loader = "submit"; - this.filtersToUpdate.display_filters = display_filters; + Object.keys(display_filters).forEach((key) => { + const _key = key as keyof TViewDisplayFilters; + set(this.filtersToUpdate, ["display_filters", _key], display_filters[_key]); + }); }); }; - updateDisplayProperties = async (display_properties: Partial) => { + setDisplayProperties = async (display_properties: Partial) => { runInAction(() => { - this.loader = "submit"; - this.filtersToUpdate.display_properties = display_properties; + Object.keys(display_properties).forEach((key) => { + const _key = key as keyof TViewDisplayProperties; + set(this.filtersToUpdate, ["display_properties", _key], display_properties[_key]); + }); }); }; - resetFilterChanges = () => { + resetChanges = () => { runInAction(() => { this.loader = undefined; this.filtersToUpdate = { - filters: {}, - display_filters: {}, - display_properties: {}, + name: this.name, + description: this.description, + filters: this.filters, + display_filters: this.display_filters, + display_properties: this.display_properties, }; }); }; - saveFilterChanges = async () => { - this.loader = "submitting"; - if (this.appliedFilters) await this.update(this.appliedFilters); - this.loader = undefined; + saveChanges = async () => { + try { + this.loader = "submitting"; + if (this.filtersToUpdate) await this.update(this.filtersToUpdate); + this.loader = undefined; + } catch { + this.loader = undefined; + Object.keys(this.filtersToUpdate).forEach((key) => { + const _key = key as keyof TView; + set(this, _key, this.filtersToUpdate[_key]); + }); + } }; // actions - update = async (viewData: Partial) => { - if (!this.workspaceSlug || !this.id) return; + lockView = async () => { + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug || !this.id || !this.service.lock) return; - const view = await this.service.update(this.workspaceSlug, viewData, this.projectId, this.featureId); - if (!view) return; + const view = await this.service.lock(workspaceSlug, this.id, projectId); + if (!view) return; - runInAction(() => { - Object.keys(viewData).forEach((key) => { - const _key = key as keyof TViewFilterProps; - set(this, _key, viewData[_key]); + runInAction(() => { + this.is_locked = view.is_locked; }); - }); + } catch { + this.is_locked = this.is_locked; + } + }; + + unlockView = async () => { + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug || !this.id || !this.service.unlock) return; + + const view = await this.service.unlock(workspaceSlug, this.id, projectId); + if (!view) return; + + runInAction(() => { + this.is_locked = view.is_locked; + }); + } catch { + this.is_locked = this.is_locked; + } + }; + + makeFavorite = async () => { + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug || !this.id || !this.service.makeFavorite) return; + + const view = await this.service.makeFavorite(workspaceSlug, this.id, projectId); + if (!view) return; + + runInAction(() => { + this.is_favorite = view.is_locked; + }); + } catch { + this.is_favorite = this.is_favorite; + } + }; + + removeFavorite = async () => { + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug || !this.id || !this.service.removeFavorite) return; + + const view = await this.service.removeFavorite(workspaceSlug, this.id, projectId); + if (!view) return; + + runInAction(() => { + this.is_favorite = view.is_locked; + }); + } catch { + this.is_favorite = this.is_favorite; + } + }; + + update = async (viewData: Partial) => { + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug || !this.id) return; + + const view = await this.service.update(workspaceSlug, this.id, viewData, projectId); + if (!view) return; + + runInAction(() => { + Object.keys(viewData).forEach((key) => { + const _key = key as keyof TView; + set(this, _key, viewData[_key]); + }); + }); + } catch { + this.resetChanges(); + } }; } diff --git a/web/store/view/view-root.store.ts b/web/store/view/view-root.store.ts index 7a172e691..4a607d61f 100644 --- a/web/store/view/view-root.store.ts +++ b/web/store/view/view-root.store.ts @@ -1,5 +1,7 @@ import { action, computed, makeObservable, observable, runInAction } from "mobx"; import set from "lodash/set"; +import sortBy from "lodash/sortBy"; +import reverse from "lodash/reverse"; // stores import { RootStore } from "store/root.store"; import { ViewStore } from "./view.store"; @@ -18,9 +20,8 @@ type TViewRootStore = { // helper actions viewById: (viewId: string) => ViewStore | undefined; // actions - fetch: (_loader?: TLoader) => Promise; localViewCreate: (view: TView) => Promise; - clearLocalView: (viewId: string) => Promise; + fetch: (_loader?: TLoader) => Promise; create: (view: Partial) => Promise; remove: (viewId: string) => Promise; duplicate: (viewId: string) => Promise; @@ -39,9 +40,8 @@ export class ViewRootStore implements TViewRootStore { // computed viewIds: computed, // actions - fetch: action, localViewCreate: action, - clearLocalView: action, + fetch: action, create: action, remove: action, duplicate: action, @@ -51,80 +51,91 @@ export class ViewRootStore implements TViewRootStore { // computed get viewIds() { const views = Object.values(this.viewMap); - return views.filter((view) => !view?.is_create).map((view) => view.id) as string[]; + const localViews = views.filter((view) => view.is_local_view); + let apiViews = views.filter((view) => !view.is_local_view && !view.is_create); + apiViews = reverse(sortBy(apiViews, "sort_order")); + + const _viewIds = [...localViews.map((view) => view.id), ...apiViews.map((view) => view.id)]; + + return _viewIds as string[]; } // helper actions viewById = (viewId: string) => this.viewMap?.[viewId] || undefined; // actions - fetch = async (_loader: TLoader = "init-loader") => { - const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug) return; - - runInAction(() => { - if (this.defaultViews && this.defaultViews.length > 0) - this.defaultViews?.forEach((view) => { - if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); - }); - }); - - this.loader = _loader; - const views = await this.service.fetch(workspaceSlug, projectId); - if (!views) return; - - runInAction(() => { - views.forEach((view) => { - if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); - }); - this.loader = undefined; - }); - }; - localViewCreate = async (view: TView) => { runInAction(() => { if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); }); }; - clearLocalView = async (viewId: string) => { - runInAction(() => { - if (viewId) delete this.viewMap[viewId]; - }); + fetch = async (_loader: TLoader = "init-loader") => { + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug) return; + + if (this.defaultViews && this.defaultViews.length > 0) + runInAction(() => { + this.defaultViews?.forEach((view) => { + if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); + }); + }); + + this.loader = _loader; + const views = await this.service.fetch(workspaceSlug, projectId); + if (!views) return; + + runInAction(() => { + views.forEach((view) => { + if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); + }); + this.loader = undefined; + }); + } catch {} }; create = async (data: Partial) => { - const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug) return; + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug) return; - const view = await this.service.create(workspaceSlug, data, projectId); - if (!view) return; + const view = await this.service.create(workspaceSlug, data, projectId); + if (!view) return; - runInAction(() => { - if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); - }); + runInAction(() => { + if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); + }); + + if (data.id) this.remove(data.id); + } catch {} }; remove = async (viewId: string) => { - const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug || !viewId) return; + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug || !viewId) return; - await this.service.remove?.(workspaceSlug, viewId, projectId); + if (this.viewMap?.[viewId] != undefined && !this.viewMap?.[viewId]?.is_create) + await this.service.remove?.(workspaceSlug, viewId, projectId); - runInAction(() => { - delete this.viewMap[viewId]; - }); + runInAction(() => { + delete this.viewMap[viewId]; + }); + } catch {} }; duplicate = async (viewId: string) => { - const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug || !this.service.duplicate) return; + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug || !this.service.duplicate) return; - const view = await this.service.duplicate(workspaceSlug, viewId, projectId); - if (!view) return; + const view = await this.service.duplicate(workspaceSlug, viewId, projectId); + if (!view) return; - runInAction(() => { - if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); - }); + runInAction(() => { + if (view.id) set(this.viewMap, [view.id], new ViewStore(this.store, view, this.service)); + }); + } catch {} }; } diff --git a/web/store/view/view.store.ts b/web/store/view/view.store.ts index d50d6a41e..7da271a53 100644 --- a/web/store/view/view.store.ts +++ b/web/store/view/view.store.ts @@ -10,7 +10,6 @@ import { TViewDisplayFilters, TViewDisplayProperties, TViewFilterProps, - TViewFilterPartialProps, TViewAccess, } from "@plane/types"; // helpers @@ -21,17 +20,18 @@ type TLoader = "submitting" | "submit" | undefined; export type TViewStore = TView & { // observables loader: TLoader; - filtersToUpdate: TViewFilterPartialProps; + filtersToUpdate: Partial; // computed appliedFilters: TViewFilterProps | undefined; appliedFiltersQueryParams: string | undefined; // helper actions setName: (name: string) => void; + setDescription: (description: string) => void; setFilters: (filters: Partial) => void; setDisplayFilters: (display_filters: Partial) => void; setDisplayProperties: (display_properties: Partial) => void; - resetFilterChanges: () => void; - saveFilterChanges: () => void; + resetChanges: () => void; + saveChanges: () => Promise; // actions lockView: () => Promise; unlockView: () => Promise; @@ -60,15 +60,16 @@ export class ViewStore extends FiltersHelper implements TViewStore { updated_by: string | undefined; created_at: Date | undefined; updated_at: Date | undefined; - // local variables is_local_view: boolean = false; is_create: boolean = false; - + is_editable: boolean = false; loader: TLoader = undefined; - filtersToUpdate: TViewFilterPartialProps = { - filters: {}, - display_filters: {}, - display_properties: {}, + filtersToUpdate: Partial = { + name: "", + description: "", + filters: undefined, + display_filters: undefined, + display_properties: undefined, }; constructor(private store: RootStore, _view: TView, private service: TViewService) { @@ -92,8 +93,10 @@ export class ViewStore extends FiltersHelper implements TViewStore { this.updated_by = _view.updated_by; this.created_at = _view.created_at; this.updated_at = _view.updated_at; + this.is_local_view = _view.is_local_view; this.is_create = _view.is_create; + this.is_editable = _view.is_editable; makeObservable(this, { // observables @@ -118,6 +121,7 @@ export class ViewStore extends FiltersHelper implements TViewStore { updated_at: observable.ref, is_local_view: observable.ref, is_create: observable.ref, + is_editable: observable.ref, loader: observable.ref, filtersToUpdate: observable, // computed @@ -128,8 +132,8 @@ export class ViewStore extends FiltersHelper implements TViewStore { setFilters: action, setDisplayFilters: action, setDisplayProperties: action, - resetFilterChanges: action, - saveFilterChanges: action, + resetChanges: action, + saveChanges: action, // actions update: action, lockView: action, @@ -158,7 +162,13 @@ export class ViewStore extends FiltersHelper implements TViewStore { // helper actions setName = (name: string) => { runInAction(() => { - this.name = name; + this.filtersToUpdate.name = name; + }); + }; + + setDescription = (description: string) => { + runInAction(() => { + this.filtersToUpdate.description = description; }); }; @@ -188,19 +198,15 @@ export class ViewStore extends FiltersHelper implements TViewStore { if (layout === "spreadsheet" && sub_issue === true) display_filters.sub_issue = false; runInAction(() => { - this.loader = "submit"; Object.keys(display_filters).forEach((key) => { const _key = key as keyof TViewDisplayFilters; set(this.filtersToUpdate, ["display_filters", _key], display_filters[_key]); }); }); - - console.log("this.filtersToUpdate", this.filtersToUpdate?.display_filters?.layout); }; setDisplayProperties = async (display_properties: Partial) => { runInAction(() => { - this.loader = "submit"; Object.keys(display_properties).forEach((key) => { const _key = key as keyof TViewDisplayProperties; set(this.filtersToUpdate, ["display_properties", _key], display_properties[_key]); @@ -208,84 +214,114 @@ export class ViewStore extends FiltersHelper implements TViewStore { }); }; - resetFilterChanges = () => { + resetChanges = () => { runInAction(() => { this.loader = undefined; this.filtersToUpdate = { - filters: {}, - display_filters: {}, - display_properties: {}, + name: this.name, + description: this.description, + filters: this.filters, + display_filters: this.display_filters, + display_properties: this.display_properties, }; }); }; - saveFilterChanges = async () => { - this.loader = "submitting"; - if (this.appliedFilters) await this.update(this.appliedFilters); - this.loader = undefined; + saveChanges = async () => { + try { + this.loader = "submitting"; + if (this.filtersToUpdate) await this.update(this.filtersToUpdate); + this.loader = undefined; + } catch { + this.loader = undefined; + Object.keys(this.filtersToUpdate).forEach((key) => { + const _key = key as keyof TView; + set(this, _key, this.filtersToUpdate[_key]); + }); + } }; // actions lockView = async () => { - const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug || !this.id || !this.service.lock) return; + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug || !this.id || !this.service.lock) return; - const view = await this.service.lock(workspaceSlug, this.id, projectId); - if (!view) return; + const view = await this.service.lock(workspaceSlug, this.id, projectId); + if (!view) return; - runInAction(() => { - this.is_locked = view.is_locked; - }); + runInAction(() => { + this.is_locked = view.is_locked; + }); + } catch { + this.is_locked = this.is_locked; + } }; unlockView = async () => { - const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug || !this.id || !this.service.unlock) return; + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug || !this.id || !this.service.unlock) return; - const view = await this.service.unlock(workspaceSlug, this.id, projectId); - if (!view) return; + const view = await this.service.unlock(workspaceSlug, this.id, projectId); + if (!view) return; - runInAction(() => { - this.is_locked = view.is_locked; - }); + runInAction(() => { + this.is_locked = view.is_locked; + }); + } catch { + this.is_locked = this.is_locked; + } }; makeFavorite = async () => { - const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug || !this.id || !this.service.makeFavorite) return; + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug || !this.id || !this.service.makeFavorite) return; - const view = await this.service.makeFavorite(workspaceSlug, this.id, projectId); - if (!view) return; + const view = await this.service.makeFavorite(workspaceSlug, this.id, projectId); + if (!view) return; - runInAction(() => { - this.is_favorite = view.is_locked; - }); + runInAction(() => { + this.is_favorite = view.is_locked; + }); + } catch { + this.is_favorite = this.is_favorite; + } }; removeFavorite = async () => { - const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug || !this.id || !this.service.removeFavorite) return; + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug || !this.id || !this.service.removeFavorite) return; - const view = await this.service.removeFavorite(workspaceSlug, this.id, projectId); - if (!view) return; + const view = await this.service.removeFavorite(workspaceSlug, this.id, projectId); + if (!view) return; - runInAction(() => { - this.is_favorite = view.is_locked; - }); + runInAction(() => { + this.is_favorite = view.is_locked; + }); + } catch { + this.is_favorite = this.is_favorite; + } }; update = async (viewData: Partial) => { - const { workspaceSlug, projectId } = this.store.app.router; - if (!workspaceSlug || !this.id) return; + try { + const { workspaceSlug, projectId } = this.store.app.router; + if (!workspaceSlug || !this.id) return; - const view = await this.service.update(workspaceSlug, this.id, viewData, projectId); - if (!view) return; + const view = await this.service.update(workspaceSlug, this.id, viewData, projectId); + if (!view) return; - runInAction(() => { - Object.keys(viewData).forEach((key) => { - const _key = key as keyof TView; - set(this, _key, viewData[_key]); + runInAction(() => { + Object.keys(viewData).forEach((key) => { + const _key = key as keyof TView; + set(this, _key, viewData[_key]); + }); }); - }); + } catch { + this.resetChanges(); + } }; }