From 785c250f3cdbac7c1b1731fa2d5257a2918fa056 Mon Sep 17 00:00:00 2001
From: Steven
Date: Tue, 27 May 2025 08:26:13 +0800
Subject: [PATCH] refactor: migrate memo store
---
.../components/HomeSidebar/HomeSidebar.tsx | 6 +-
.../components/Inbox/MemoCommentMessage.tsx | 9 +-
web/src/components/MemoActionMenu.tsx | 8 +-
.../EmbeddedContent/EmbeddedMemo.tsx | 8 +-
.../ReferencedContent/ReferencedMemo.tsx | 8 +-
.../components/MemoContent/TaskListItem.tsx | 8 +-
web/src/components/MemoContent/index.tsx | 8 +-
.../MemoEditor/RelationListView.tsx | 8 +-
web/src/components/MemoEditor/index.tsx | 4 +-
web/src/components/MemoView.tsx | 8 +-
.../PagedMemoList/PagedMemoList.tsx | 9 +-
web/src/components/ReactionSelector.tsx | 8 +-
web/src/components/ReactionView.tsx | 8 +-
web/src/pages/MemoDetail.tsx | 8 +-
web/src/pages/Resources.tsx | 8 +-
web/src/store/v1/index.ts | 1 -
web/src/store/v1/memo.ts | 128 ---------------
web/src/store/v2/index.ts | 3 +-
web/src/store/v2/memo.ts | 150 ++++++++++++++++++
19 files changed, 206 insertions(+), 192 deletions(-)
delete mode 100644 web/src/store/v1/memo.ts
create mode 100644 web/src/store/v2/memo.ts
diff --git a/web/src/components/HomeSidebar/HomeSidebar.tsx b/web/src/components/HomeSidebar/HomeSidebar.tsx
index fdf62e246..4be07aff2 100644
--- a/web/src/components/HomeSidebar/HomeSidebar.tsx
+++ b/web/src/components/HomeSidebar/HomeSidebar.tsx
@@ -6,8 +6,7 @@ import useDebounce from "react-use/lib/useDebounce";
import SearchBar from "@/components/SearchBar";
import useCurrentUser from "@/hooks/useCurrentUser";
import { Routes } from "@/router";
-import { useMemoList } from "@/store/v1";
-import { userStore } from "@/store/v2";
+import { memoStore, userStore } from "@/store/v2";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
import MemoFilters from "../MemoFilters";
@@ -30,7 +29,6 @@ const HomeSidebar = observer((props: Props) => {
const t = useTranslate();
const location = useLocation();
const currentUser = useCurrentUser();
- const memoList = useMemoList();
const homeNavLink: NavLinkItem = {
id: "header-home",
@@ -61,7 +59,7 @@ const HomeSidebar = observer((props: Props) => {
await userStore.fetchUserStats(parent);
},
300,
- [memoList.size(), userStore.state.statsStateId, location.pathname],
+ [memoStore.state.memos.length, userStore.state.statsStateId, location.pathname],
);
return (
diff --git a/web/src/components/Inbox/MemoCommentMessage.tsx b/web/src/components/Inbox/MemoCommentMessage.tsx
index 6642ac092..cb98a4d76 100644
--- a/web/src/components/Inbox/MemoCommentMessage.tsx
+++ b/web/src/components/Inbox/MemoCommentMessage.tsx
@@ -1,13 +1,13 @@
import { Tooltip } from "@mui/joy";
import { InboxIcon, LoaderIcon, MessageCircleIcon } from "lucide-react";
+import { observer } from "mobx-react-lite";
import { useState } from "react";
import toast from "react-hot-toast";
import { activityServiceClient } from "@/grpcweb";
import useAsyncEffect from "@/hooks/useAsyncEffect";
import useNavigateTo from "@/hooks/useNavigateTo";
import { activityNamePrefix } from "@/store/common";
-import { useMemoStore } from "@/store/v1";
-import { userStore } from "@/store/v2";
+import { memoStore, userStore } from "@/store/v2";
import { Inbox, Inbox_Status } from "@/types/proto/api/v1/inbox_service";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { User } from "@/types/proto/api/v1/user_service";
@@ -18,10 +18,9 @@ interface Props {
inbox: Inbox;
}
-const MemoCommentMessage = ({ inbox }: Props) => {
+const MemoCommentMessage = observer(({ inbox }: Props) => {
const t = useTranslate();
const navigateTo = useNavigateTo();
- const memoStore = useMemoStore();
const [relatedMemo, setRelatedMemo] = useState(undefined);
const [sender, setSender] = useState(undefined);
const [initialized, setInitialized] = useState(false);
@@ -124,6 +123,6 @@ const MemoCommentMessage = ({ inbox }: Props) => {
);
-};
+});
export default MemoCommentMessage;
diff --git a/web/src/components/MemoActionMenu.tsx b/web/src/components/MemoActionMenu.tsx
index 7c955e6a5..0548e53b7 100644
--- a/web/src/components/MemoActionMenu.tsx
+++ b/web/src/components/MemoActionMenu.tsx
@@ -11,11 +11,12 @@ import {
TrashIcon,
SquareCheckIcon,
} from "lucide-react";
+import { observer } from "mobx-react-lite";
import toast from "react-hot-toast";
import { useLocation } from "react-router-dom";
import { markdownServiceClient } from "@/grpcweb";
import useNavigateTo from "@/hooks/useNavigateTo";
-import { useMemoStore } from "@/store/v1";
+import { memoStore } from "@/store/v2";
import { userStore } from "@/store/v2";
import { State } from "@/types/proto/api/v1/common";
import { NodeType } from "@/types/proto/api/v1/markdown_service";
@@ -43,12 +44,11 @@ const checkHasCompletedTaskList = (memo: Memo) => {
return false;
};
-const MemoActionMenu = (props: Props) => {
+const MemoActionMenu = observer((props: Props) => {
const { memo, readonly } = props;
const t = useTranslate();
const location = useLocation();
const navigateTo = useNavigateTo();
- const memoStore = useMemoStore();
const hasCompletedTaskList = checkHasCompletedTaskList(memo);
const isInMemoDetailPage = location.pathname.startsWith(`/${memo.name}`);
const isComment = Boolean(memo.parent);
@@ -212,6 +212,6 @@ const MemoActionMenu = (props: Props) => {
);
-};
+});
export default MemoActionMenu;
diff --git a/web/src/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx b/web/src/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx
index d030bc204..90b23d32a 100644
--- a/web/src/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx
+++ b/web/src/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx
@@ -1,12 +1,13 @@
import copy from "copy-to-clipboard";
import { ArrowUpRightIcon } from "lucide-react";
+import { observer } from "mobx-react-lite";
import { useContext, useEffect } from "react";
import toast from "react-hot-toast";
import { Link } from "react-router-dom";
import MemoResourceListView from "@/components/MemoResourceListView";
import useLoading from "@/hooks/useLoading";
import { extractMemoIdFromName } from "@/store/common";
-import { useMemoStore } from "@/store/v1";
+import { memoStore } from "@/store/v2";
import { cn } from "@/utils";
import MemoContent from "..";
import { RendererContext } from "../types";
@@ -17,10 +18,9 @@ interface Props {
params: string;
}
-const EmbeddedMemo = ({ resourceId: uid, params: paramsStr }: Props) => {
+const EmbeddedMemo = observer(({ resourceId: uid, params: paramsStr }: Props) => {
const context = useContext(RendererContext);
const loadingState = useLoading();
- const memoStore = useMemoStore();
const memoName = `memos/${uid}`;
const memo = memoStore.getMemoByName(memoName);
@@ -87,6 +87,6 @@ const EmbeddedMemo = ({ resourceId: uid, params: paramsStr }: Props) => {
{contentNode}
);
-};
+});
export default EmbeddedMemo;
diff --git a/web/src/components/MemoContent/ReferencedContent/ReferencedMemo.tsx b/web/src/components/MemoContent/ReferencedContent/ReferencedMemo.tsx
index 66ea2307c..e7847fc6a 100644
--- a/web/src/components/MemoContent/ReferencedContent/ReferencedMemo.tsx
+++ b/web/src/components/MemoContent/ReferencedContent/ReferencedMemo.tsx
@@ -1,8 +1,9 @@
+import { observer } from "mobx-react-lite";
import { useContext, useEffect } from "react";
import useLoading from "@/hooks/useLoading";
import useNavigateTo from "@/hooks/useNavigateTo";
import { memoNamePrefix } from "@/store/common";
-import { useMemoStore } from "@/store/v1";
+import { memoStore } from "@/store/v2";
import { RendererContext } from "../types";
import Error from "./Error";
@@ -11,10 +12,9 @@ interface Props {
params: string;
}
-const ReferencedMemo = ({ resourceId: uid, params: paramsStr }: Props) => {
+const ReferencedMemo = observer(({ resourceId: uid, params: paramsStr }: Props) => {
const navigateTo = useNavigateTo();
const loadingState = useLoading();
- const memoStore = useMemoStore();
const memoName = `${memoNamePrefix}${uid}`;
const memo = memoStore.getMemoByName(memoName);
const params = new URLSearchParams(paramsStr);
@@ -50,6 +50,6 @@ const ReferencedMemo = ({ resourceId: uid, params: paramsStr }: Props) => {
{displayContent}
);
-};
+});
export default ReferencedMemo;
diff --git a/web/src/components/MemoContent/TaskListItem.tsx b/web/src/components/MemoContent/TaskListItem.tsx
index a62487545..560612579 100644
--- a/web/src/components/MemoContent/TaskListItem.tsx
+++ b/web/src/components/MemoContent/TaskListItem.tsx
@@ -1,7 +1,8 @@
import { Checkbox } from "@usememos/mui";
+import { observer } from "mobx-react-lite";
import { useContext } from "react";
import { markdownServiceClient } from "@/grpcweb";
-import { useMemoStore } from "@/store/v1";
+import { memoStore } from "@/store/v2";
import { Node, TaskListItemNode } from "@/types/proto/api/v1/markdown_service";
import { cn } from "@/utils";
import Renderer from "./Renderer";
@@ -16,9 +17,8 @@ interface Props {
children: Node[];
}
-const TaskListItem: React.FC = ({ node, complete, children }: Props) => {
+const TaskListItem = observer(({ node, complete, children }: Props) => {
const context = useContext(RendererContext);
- const memoStore = useMemoStore();
const handleCheckboxChange = async (on: boolean) => {
if (context.readonly || !context.memoName) {
@@ -48,6 +48,6 @@ const TaskListItem: React.FC = ({ node, complete, children }: Props) => {
);
-};
+});
export default TaskListItem;
diff --git a/web/src/components/MemoContent/index.tsx b/web/src/components/MemoContent/index.tsx
index 9a6c849b3..afba1d529 100644
--- a/web/src/components/MemoContent/index.tsx
+++ b/web/src/components/MemoContent/index.tsx
@@ -1,6 +1,7 @@
+import { observer } from "mobx-react-lite";
import { memo, useEffect, useRef, useState } from "react";
import useCurrentUser from "@/hooks/useCurrentUser";
-import { useMemoStore } from "@/store/v1";
+import { memoStore } from "@/store/v2";
import { Node, NodeType } from "@/types/proto/api/v1/markdown_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
@@ -29,11 +30,10 @@ interface Props {
type ContentCompactView = "ALL" | "SNIPPET";
-const MemoContent: React.FC = (props: Props) => {
+const MemoContent = observer((props: Props) => {
const { className, contentClassName, nodes, memoName, embeddedMemos, onClick, onDoubleClick } = props;
const t = useTranslate();
const currentUser = useCurrentUser();
- const memoStore = useMemoStore();
const memoContentContainerRef = useRef(null);
const [showCompactMode, setShowCompactMode] = useState(undefined);
const memo = memoName ? memoStore.getMemoByName(memoName) : null;
@@ -122,6 +122,6 @@ const MemoContent: React.FC = (props: Props) => {
);
-};
+});
export default memo(MemoContent);
diff --git a/web/src/components/MemoEditor/RelationListView.tsx b/web/src/components/MemoEditor/RelationListView.tsx
index da9ba9e24..ad709acfa 100644
--- a/web/src/components/MemoEditor/RelationListView.tsx
+++ b/web/src/components/MemoEditor/RelationListView.tsx
@@ -1,6 +1,7 @@
import { LinkIcon, XIcon } from "lucide-react";
+import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react";
-import { useMemoStore } from "@/store/v1";
+import { memoStore } from "@/store/v2";
import { Memo, MemoRelation, MemoRelation_Type } from "@/types/proto/api/v1/memo_service";
interface Props {
@@ -8,9 +9,8 @@ interface Props {
setRelationList: (relationList: MemoRelation[]) => void;
}
-const RelationListView = (props: Props) => {
+const RelationListView = observer((props: Props) => {
const { relationList, setRelationList } = props;
- const memoStore = useMemoStore();
const [referencingMemoList, setReferencingMemoList] = useState([]);
useEffect(() => {
@@ -50,6 +50,6 @@ const RelationListView = (props: Props) => {
)}
>
);
-};
+});
export default RelationListView;
diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx
index 724ffeaa8..42c6fe0c4 100644
--- a/web/src/components/MemoEditor/index.tsx
+++ b/web/src/components/MemoEditor/index.tsx
@@ -13,8 +13,7 @@ import { TAB_SPACE_WIDTH } from "@/helpers/consts";
import { isValidUrl } from "@/helpers/utils";
import useAsyncEffect from "@/hooks/useAsyncEffect";
import useCurrentUser from "@/hooks/useCurrentUser";
-import { useMemoStore } from "@/store/v1";
-import { resourceStore, userStore, workspaceStore } from "@/store/v2";
+import { memoStore, resourceStore, userStore, workspaceStore } from "@/store/v2";
import { Location, Memo, MemoRelation, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_service";
import { Resource } from "@/types/proto/api/v1/resource_service";
import { UserSetting } from "@/types/proto/api/v1/user_service";
@@ -61,7 +60,6 @@ const MemoEditor = observer((props: Props) => {
const { className, cacheKey, memoName, parentMemoName, autoFocus, onConfirm, onCancel } = props;
const t = useTranslate();
const { i18n } = useTranslation();
- const memoStore = useMemoStore();
const currentUser = useCurrentUser();
const [state, setState] = useState({
memoVisibility: Visibility.PRIVATE,
diff --git a/web/src/components/MemoView.tsx b/web/src/components/MemoView.tsx
index 6118c7105..0e166bd25 100644
--- a/web/src/components/MemoView.tsx
+++ b/web/src/components/MemoView.tsx
@@ -1,11 +1,12 @@
import { Tooltip } from "@mui/joy";
import { BookmarkIcon, EyeOffIcon, MessageCircleMoreIcon } from "lucide-react";
+import { observer } from "mobx-react-lite";
import { memo, useCallback, useRef, useState } from "react";
import { Link, useLocation } from "react-router-dom";
import useAsyncEffect from "@/hooks/useAsyncEffect";
import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo";
-import { useMemoStore } from "@/store/v1";
+import { memoStore } from "@/store/v2";
import { userStore, workspaceStore } from "@/store/v2";
import { State } from "@/types/proto/api/v1/common";
import { Memo, MemoRelation_Type, Visibility } from "@/types/proto/api/v1/memo_service";
@@ -36,14 +37,13 @@ interface Props {
parentPage?: string;
}
-const MemoView: React.FC = (props: Props) => {
+const MemoView: React.FC = observer((props: Props) => {
const { memo, className } = props;
const t = useTranslate();
const location = useLocation();
const navigateTo = useNavigateTo();
const currentUser = useCurrentUser();
const user = useCurrentUser();
- const memoStore = useMemoStore();
const [showEditor, setShowEditor] = useState(false);
const [creator, setCreator] = useState(userStore.getUserByName(memo.creator));
const [showNSFWContent, setShowNSFWContent] = useState(props.showNsfwContent);
@@ -250,6 +250,6 @@ const MemoView: React.FC = (props: Props) => {
)}
);
-};
+});
export default memo(MemoView);
diff --git a/web/src/components/PagedMemoList/PagedMemoList.tsx b/web/src/components/PagedMemoList/PagedMemoList.tsx
index 211ffcb1c..cac610b27 100644
--- a/web/src/components/PagedMemoList/PagedMemoList.tsx
+++ b/web/src/components/PagedMemoList/PagedMemoList.tsx
@@ -7,8 +7,7 @@ import PullToRefresh from "react-simple-pull-to-refresh";
import { DEFAULT_LIST_MEMOS_PAGE_SIZE } from "@/helpers/consts";
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { Routes } from "@/router";
-import { useMemoList, useMemoStore } from "@/store/v1";
-import { viewStore } from "@/store/v2";
+import { memoStore, viewStore } from "@/store/v2";
import { Direction, State } from "@/types/proto/api/v1/common";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { useTranslate } from "@/utils/i18n";
@@ -35,13 +34,11 @@ interface LocalState {
const PagedMemoList = observer((props: Props) => {
const t = useTranslate();
const { md } = useResponsiveWidth();
- const memoStore = useMemoStore();
- const memoList = useMemoList();
const [state, setState] = useState({
isRequesting: true, // Initial request
nextPageToken: "",
});
- const sortedMemoList = props.listSort ? props.listSort(memoList.value) : memoList.value;
+ const sortedMemoList = props.listSort ? props.listSort(memoStore.state.memos) : memoStore.state.memos;
const showMemoEditor = Boolean(matchPath(Routes.ROOT, window.location.pathname));
const fetchMoreMemos = async (nextPageToken: string) => {
@@ -62,7 +59,7 @@ const PagedMemoList = observer((props: Props) => {
};
const refreshList = async () => {
- memoList.reset();
+ memoStore.state.updateStateId();
setState((state) => ({ ...state, nextPageToken: "" }));
await fetchMoreMemos("");
};
diff --git a/web/src/components/ReactionSelector.tsx b/web/src/components/ReactionSelector.tsx
index 457834340..5a07fb293 100644
--- a/web/src/components/ReactionSelector.tsx
+++ b/web/src/components/ReactionSelector.tsx
@@ -1,10 +1,11 @@
import { Dropdown, Menu, MenuButton } from "@mui/joy";
import { SmilePlusIcon } from "lucide-react";
+import { observer } from "mobx-react-lite";
import { useRef, useState } from "react";
import useClickAway from "react-use/lib/useClickAway";
import { memoServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
-import { useMemoStore } from "@/store/v1";
+import { memoStore } from "@/store/v2";
import { workspaceStore } from "@/store/v2";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
@@ -14,10 +15,9 @@ interface Props {
className?: string;
}
-const ReactionSelector = (props: Props) => {
+const ReactionSelector = observer((props: Props) => {
const { memo, className } = props;
const currentUser = useCurrentUser();
- const memoStore = useMemoStore();
const [open, setOpen] = useState(false);
const containerRef = useRef(null);
const workspaceMemoRelatedSetting = workspaceStore.state.memoRelatedSetting;
@@ -86,6 +86,6 @@ const ReactionSelector = (props: Props) => {
);
-};
+});
export default ReactionSelector;
diff --git a/web/src/components/ReactionView.tsx b/web/src/components/ReactionView.tsx
index 6e52cb1dd..48a82344a 100644
--- a/web/src/components/ReactionView.tsx
+++ b/web/src/components/ReactionView.tsx
@@ -1,7 +1,8 @@
import { Tooltip } from "@mui/joy";
+import { observer } from "mobx-react-lite";
import { memoServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
-import { useMemoStore } from "@/store/v1";
+import { memoStore } from "@/store/v2";
import { State } from "@/types/proto/api/v1/common";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { User } from "@/types/proto/api/v1/user_service";
@@ -28,10 +29,9 @@ const stringifyUsers = (users: User[], reactionType: string): string => {
);
};
-const ReactionView = (props: Props) => {
+const ReactionView = observer((props: Props) => {
const { memo, reactionType, users } = props;
const currentUser = useCurrentUser();
- const memoStore = useMemoStore();
const hasReaction = users.some((user) => currentUser && user.username === currentUser.username);
const readonly = memo.state === State.ARCHIVED;
@@ -80,6 +80,6 @@ const ReactionView = (props: Props) => {
);
-};
+});
export default ReactionView;
diff --git a/web/src/pages/MemoDetail.tsx b/web/src/pages/MemoDetail.tsx
index 383890598..e3c665bfe 100644
--- a/web/src/pages/MemoDetail.tsx
+++ b/web/src/pages/MemoDetail.tsx
@@ -1,5 +1,6 @@
import { Button } from "@usememos/mui";
import { ArrowUpLeftFromCircleIcon, MessageCircleIcon } from "lucide-react";
+import { observer } from "mobx-react-lite";
import { ClientError } from "nice-grpc-web";
import { useEffect, useState } from "react";
import { toast } from "react-hot-toast";
@@ -12,20 +13,19 @@ import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo";
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { memoNamePrefix } from "@/store/common";
-import { useMemoStore } from "@/store/v1";
+import { memoStore } from "@/store/v2";
import { workspaceStore } from "@/store/v2";
import { Memo, MemoRelation_Type } from "@/types/proto/api/v1/memo_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
-const MemoDetail = () => {
+const MemoDetail = observer(() => {
const t = useTranslate();
const { md } = useResponsiveWidth();
const params = useParams();
const navigateTo = useNavigateTo();
const { state: locationState } = useLocation();
const currentUser = useCurrentUser();
- const memoStore = useMemoStore();
const uid = params.uid;
const memoName = `${memoNamePrefix}${uid}`;
const memo = memoStore.getMemoByName(memoName);
@@ -176,6 +176,6 @@ const MemoDetail = () => {
);
-};
+});
export default MemoDetail;
diff --git a/web/src/pages/Resources.tsx b/web/src/pages/Resources.tsx
index 5587d2960..125d37db3 100644
--- a/web/src/pages/Resources.tsx
+++ b/web/src/pages/Resources.tsx
@@ -3,6 +3,7 @@ import { Button, Input } from "@usememos/mui";
import dayjs from "dayjs";
import { includes } from "lodash-es";
import { PaperclipIcon, SearchIcon, TrashIcon } from "lucide-react";
+import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react";
import Empty from "@/components/Empty";
import MobileHeader from "@/components/MobileHeader";
@@ -11,7 +12,7 @@ import { resourceServiceClient } from "@/grpcweb";
import useLoading from "@/hooks/useLoading";
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import i18n from "@/i18n";
-import { useMemoStore } from "@/store/v1";
+import { memoStore } from "@/store/v2";
import { Resource } from "@/types/proto/api/v1/resource_service";
import { useTranslate } from "@/utils/i18n";
@@ -33,14 +34,13 @@ interface State {
searchQuery: string;
}
-const Resources = () => {
+const Resources = observer(() => {
const t = useTranslate();
const { md } = useResponsiveWidth();
const loadingState = useLoading();
const [state, setState] = useState({
searchQuery: "",
});
- const memoStore = useMemoStore();
const [resources, setResources] = useState([]);
const filteredResources = resources.filter((resource) => includes(resource.filename, state.searchQuery));
const groupedResources = groupResourcesByDate(filteredResources.filter((resource) => resource.memo));
@@ -165,6 +165,6 @@ const Resources = () => {
);
-};
+});
export default Resources;
diff --git a/web/src/store/v1/index.ts b/web/src/store/v1/index.ts
index b090b900a..1d5a627d9 100644
--- a/web/src/store/v1/index.ts
+++ b/web/src/store/v1/index.ts
@@ -1,2 +1 @@
-export * from "./memo";
export * from "./memoFilter";
diff --git a/web/src/store/v1/memo.ts b/web/src/store/v1/memo.ts
deleted file mode 100644
index 14c1abb91..000000000
--- a/web/src/store/v1/memo.ts
+++ /dev/null
@@ -1,128 +0,0 @@
-import { uniqueId } from "lodash-es";
-import { create } from "zustand";
-import { combine } from "zustand/middleware";
-import { memoServiceClient } from "@/grpcweb";
-import { CreateMemoRequest, ListMemosRequest, Memo } from "@/types/proto/api/v1/memo_service";
-
-interface State {
- // stateId is used to identify the store instance state.
- // It should be update when any state change.
- stateId: string;
- memoMapByName: Record;
- currentRequest: AbortController | null;
-}
-
-const getDefaultState = (): State => ({
- stateId: uniqueId(),
- memoMapByName: {},
- currentRequest: null,
-});
-
-export const useMemoStore = create(
- combine(getDefaultState(), (set, get) => ({
- setState: (state: State) => set(state),
- getState: () => get(),
- updateStateId: () => set({ stateId: uniqueId() }),
- fetchMemos: async (request: Partial) => {
- const currentRequest = get().currentRequest;
- if (currentRequest) {
- currentRequest.abort();
- }
-
- const controller = new AbortController();
- set({ currentRequest: controller });
-
- try {
- const { memos, nextPageToken } = await memoServiceClient.listMemos(
- {
- ...request,
- },
- { signal: controller.signal },
- );
-
- if (!controller.signal.aborted) {
- const memoMap = request.pageToken ? { ...get().memoMapByName } : {};
- for (const memo of memos) {
- memoMap[memo.name] = memo;
- }
- set({ stateId: uniqueId(), memoMapByName: memoMap });
- return { memos, nextPageToken };
- }
- } catch (error: any) {
- if (error.name === "AbortError") {
- return;
- }
- throw error;
- } finally {
- if (get().currentRequest === controller) {
- set({ currentRequest: null });
- }
- }
- },
- getOrFetchMemoByName: async (name: string, options?: { skipCache?: boolean; skipStore?: boolean }) => {
- const memoMap = get().memoMapByName;
- const memoCache = memoMap[name];
- if (memoCache && !options?.skipCache) {
- return memoCache;
- }
-
- const memo = await memoServiceClient.getMemo({
- name,
- });
- if (!options?.skipStore) {
- memoMap[name] = memo;
- set({ stateId: uniqueId(), memoMapByName: memoMap });
- }
- return memo;
- },
- getMemoByName: (name: string) => {
- return get().memoMapByName[name];
- },
- createMemo: async (request: CreateMemoRequest) => {
- const memo = await memoServiceClient.createMemo(request);
- const memoMap = get().memoMapByName;
- memoMap[memo.name] = memo;
- set({ stateId: uniqueId(), memoMapByName: memoMap });
- return memo;
- },
- updateMemo: async (update: Partial, updateMask: string[]) => {
- const memo = await memoServiceClient.updateMemo({
- memo: update,
- updateMask,
- });
-
- const memoMap = get().memoMapByName;
- memoMap[memo.name] = memo;
- set({ stateId: uniqueId(), memoMapByName: memoMap });
- return memo;
- },
- deleteMemo: async (name: string) => {
- await memoServiceClient.deleteMemo({
- name,
- });
-
- const memoMap = get().memoMapByName;
- delete memoMap[name];
- set({ stateId: uniqueId(), memoMapByName: memoMap });
- },
- })),
-);
-
-export const useMemoList = () => {
- const memoStore = useMemoStore();
- const memos = Object.values(memoStore.getState().memoMapByName);
-
- const reset = () => {
- memoStore.updateStateId();
- };
-
- const size = () => {
- return Object.keys(memoStore.getState().memoMapByName).length;
- };
-
- return {
- value: memos,
- reset,
- size,
- };
-};
diff --git a/web/src/store/v2/index.ts b/web/src/store/v2/index.ts
index 4747e2bbf..78f6377bb 100644
--- a/web/src/store/v2/index.ts
+++ b/web/src/store/v2/index.ts
@@ -1,6 +1,7 @@
+import memoStore from "./memo";
import resourceStore from "./resource";
import userStore from "./user";
import viewStore from "./view";
import workspaceStore from "./workspace";
-export { resourceStore, workspaceStore, userStore, viewStore };
+export { memoStore, resourceStore, workspaceStore, userStore, viewStore };
diff --git a/web/src/store/v2/memo.ts b/web/src/store/v2/memo.ts
new file mode 100644
index 000000000..0ff5d79d2
--- /dev/null
+++ b/web/src/store/v2/memo.ts
@@ -0,0 +1,150 @@
+import { uniqueId } from "lodash-es";
+import { makeAutoObservable } from "mobx";
+import { memoServiceClient } from "@/grpcweb";
+import { CreateMemoRequest, ListMemosRequest, Memo } from "@/types/proto/api/v1/memo_service";
+
+class LocalState {
+ stateId: string = uniqueId();
+ memoMapByName: Record = {};
+ currentRequest: AbortController | null = null;
+
+ constructor() {
+ makeAutoObservable(this);
+ }
+
+ setPartial(partial: Partial) {
+ Object.assign(this, partial);
+ }
+
+ updateStateId() {
+ this.stateId = uniqueId();
+ }
+
+ get memos() {
+ return Object.values(this.memoMapByName);
+ }
+
+ get size() {
+ return Object.keys(this.memoMapByName).length;
+ }
+}
+
+const memoStore = (() => {
+ const state = new LocalState();
+
+ const fetchMemos = async (request: Partial) => {
+ if (state.currentRequest) {
+ state.currentRequest.abort();
+ }
+
+ const controller = new AbortController();
+ state.setPartial({ currentRequest: controller });
+
+ try {
+ const { memos, nextPageToken } = await memoServiceClient.listMemos(
+ {
+ ...request,
+ },
+ { signal: controller.signal },
+ );
+
+ if (!controller.signal.aborted) {
+ const memoMap = request.pageToken ? { ...state.memoMapByName } : {};
+ for (const memo of memos) {
+ memoMap[memo.name] = memo;
+ }
+ state.setPartial({
+ stateId: uniqueId(),
+ memoMapByName: memoMap,
+ });
+ return { memos, nextPageToken };
+ }
+ } catch (error: any) {
+ if (error.name === "AbortError") {
+ return;
+ }
+ throw error;
+ } finally {
+ if (state.currentRequest === controller) {
+ state.setPartial({ currentRequest: null });
+ }
+ }
+ };
+
+ const getOrFetchMemoByName = async (name: string, options?: { skipCache?: boolean; skipStore?: boolean }) => {
+ const memoCache = state.memoMapByName[name];
+ if (memoCache && !options?.skipCache) {
+ return memoCache;
+ }
+
+ const memo = await memoServiceClient.getMemo({
+ name,
+ });
+
+ if (!options?.skipStore) {
+ const memoMap = { ...state.memoMapByName };
+ memoMap[name] = memo;
+ state.setPartial({
+ stateId: uniqueId(),
+ memoMapByName: memoMap,
+ });
+ }
+
+ return memo;
+ };
+
+ const getMemoByName = (name: string) => {
+ return state.memoMapByName[name];
+ };
+
+ const createMemo = async (request: CreateMemoRequest) => {
+ const memo = await memoServiceClient.createMemo(request);
+ const memoMap = { ...state.memoMapByName };
+ memoMap[memo.name] = memo;
+ state.setPartial({
+ stateId: uniqueId(),
+ memoMapByName: memoMap,
+ });
+ return memo;
+ };
+
+ const updateMemo = async (update: Partial, updateMask: string[]) => {
+ const memo = await memoServiceClient.updateMemo({
+ memo: update,
+ updateMask,
+ });
+
+ const memoMap = { ...state.memoMapByName };
+ memoMap[memo.name] = memo;
+ state.setPartial({
+ stateId: uniqueId(),
+ memoMapByName: memoMap,
+ });
+ return memo;
+ };
+
+ const deleteMemo = async (name: string) => {
+ await memoServiceClient.deleteMemo({
+ name,
+ });
+
+ const memoMap = { ...state.memoMapByName };
+ delete memoMap[name];
+ state.setPartial({
+ stateId: uniqueId(),
+ memoMapByName: memoMap,
+ });
+ };
+
+ return {
+ state,
+ fetchMemos,
+ getOrFetchMemoByName,
+ getMemoByName,
+ createMemo,
+ updateMemo,
+ deleteMemo,
+ };
+})();
+
+export default memoStore;