mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
refactor: user stats state
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
import { last } from "lodash-es";
|
import { last } from "lodash-es";
|
||||||
import { Globe2Icon, HomeIcon } from "lucide-react";
|
import { Globe2Icon, HomeIcon } from "lucide-react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { matchPath, NavLink, useLocation } from "react-router-dom";
|
import { matchPath, NavLink, useLocation } from "react-router-dom";
|
||||||
import useDebounce from "react-use/lib/useDebounce";
|
import useDebounce from "react-use/lib/useDebounce";
|
||||||
import SearchBar from "@/components/SearchBar";
|
import SearchBar from "@/components/SearchBar";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import { Routes } from "@/router";
|
import { Routes } from "@/router";
|
||||||
import { useMemoList, useUserStatsStore } from "@/store/v1";
|
import { useMemoList } from "@/store/v1";
|
||||||
import { userStore } from "@/store/v2";
|
import { userStore } from "@/store/v2";
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/utils";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
@@ -25,12 +26,11 @@ interface Props {
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const HomeSidebar = (props: Props) => {
|
const HomeSidebar = observer((props: Props) => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const currentUser = useCurrentUser();
|
const currentUser = useCurrentUser();
|
||||||
const memoList = useMemoList();
|
const memoList = useMemoList();
|
||||||
const userStatsStore = useUserStatsStore();
|
|
||||||
|
|
||||||
const homeNavLink: NavLinkItem = {
|
const homeNavLink: NavLinkItem = {
|
||||||
id: "header-home",
|
id: "header-home",
|
||||||
@@ -55,13 +55,13 @@ const HomeSidebar = (props: Props) => {
|
|||||||
}
|
}
|
||||||
if (matchPath("/u/:username", location.pathname) !== null) {
|
if (matchPath("/u/:username", location.pathname) !== null) {
|
||||||
const username = last(location.pathname.split("/"));
|
const username = last(location.pathname.split("/"));
|
||||||
const user = await userStore.fetchUserByUsername(username || "");
|
const user = await userStore.getOrFetchUserByUsername(username || "");
|
||||||
parent = user.name;
|
parent = user.name;
|
||||||
}
|
}
|
||||||
await userStatsStore.listUserStats(parent);
|
await userStore.fetchUserStats(parent);
|
||||||
},
|
},
|
||||||
300,
|
300,
|
||||||
[memoList.size(), userStatsStore.stateId, location.pathname],
|
[memoList.size(), userStore.state.statsStateId, location.pathname],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -93,6 +93,6 @@ const HomeSidebar = (props: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default HomeSidebar;
|
export default HomeSidebar;
|
||||||
|
@@ -1,9 +1,11 @@
|
|||||||
import { Dropdown, Menu, MenuButton, MenuItem, Switch } from "@mui/joy";
|
import { Dropdown, Menu, MenuButton, MenuItem, Switch } from "@mui/joy";
|
||||||
import { Edit3Icon, HashIcon, MoreVerticalIcon, TagsIcon, TrashIcon } from "lucide-react";
|
import { Edit3Icon, HashIcon, MoreVerticalIcon, TagsIcon, TrashIcon } from "lucide-react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import useLocalStorage from "react-use/lib/useLocalStorage";
|
import useLocalStorage from "react-use/lib/useLocalStorage";
|
||||||
import { memoServiceClient } from "@/grpcweb";
|
import { memoServiceClient } from "@/grpcweb";
|
||||||
import { useMemoFilterStore, useUserStatsStore, useUserStatsTags } from "@/store/v1";
|
import { useMemoFilterStore } from "@/store/v1";
|
||||||
|
import { userStore } from "@/store/v2";
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/utils";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import showRenameTagDialog from "../RenameTagDialog";
|
import showRenameTagDialog from "../RenameTagDialog";
|
||||||
@@ -14,12 +16,11 @@ interface Props {
|
|||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagsSection = (props: Props) => {
|
const TagsSection = observer((props: Props) => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const memoFilterStore = useMemoFilterStore();
|
const memoFilterStore = useMemoFilterStore();
|
||||||
const userStatsStore = useUserStatsStore();
|
|
||||||
const [treeMode, setTreeMode] = useLocalStorage<boolean>("tag-view-as-tree", false);
|
const [treeMode, setTreeMode] = useLocalStorage<boolean>("tag-view-as-tree", false);
|
||||||
const tags = Object.entries(useUserStatsTags())
|
const tags = Object.entries(userStore.state.tagCount)
|
||||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||||
.sort((a, b) => b[1] - a[1]);
|
.sort((a, b) => b[1] - a[1]);
|
||||||
|
|
||||||
@@ -42,7 +43,6 @@ const TagsSection = (props: Props) => {
|
|||||||
parent: "memos/-",
|
parent: "memos/-",
|
||||||
tag: tag,
|
tag: tag,
|
||||||
});
|
});
|
||||||
userStatsStore.setStateId();
|
|
||||||
toast.success(t("message.deleted-successfully"));
|
toast.success(t("message.deleted-successfully"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -114,6 +114,6 @@ const TagsSection = (props: Props) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default TagsSection;
|
export default TagsSection;
|
||||||
|
@@ -15,7 +15,8 @@ import toast from "react-hot-toast";
|
|||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { markdownServiceClient } from "@/grpcweb";
|
import { markdownServiceClient } from "@/grpcweb";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
import { useMemoStore, useUserStatsStore } from "@/store/v1";
|
import { useMemoStore } from "@/store/v1";
|
||||||
|
import { userStore } from "@/store/v2";
|
||||||
import { State } from "@/types/proto/api/v1/common";
|
import { State } from "@/types/proto/api/v1/common";
|
||||||
import { NodeType } from "@/types/proto/api/v1/markdown_service";
|
import { NodeType } from "@/types/proto/api/v1/markdown_service";
|
||||||
import { Memo } from "@/types/proto/api/v1/memo_service";
|
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||||
@@ -48,14 +49,13 @@ const MemoActionMenu = (props: Props) => {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigateTo = useNavigateTo();
|
const navigateTo = useNavigateTo();
|
||||||
const memoStore = useMemoStore();
|
const memoStore = useMemoStore();
|
||||||
const userStatsStore = useUserStatsStore();
|
|
||||||
const isArchived = memo.state === State.ARCHIVED;
|
const isArchived = memo.state === State.ARCHIVED;
|
||||||
const hasCompletedTaskList = checkHasCompletedTaskList(memo);
|
const hasCompletedTaskList = checkHasCompletedTaskList(memo);
|
||||||
const isInMemoDetailPage = location.pathname.startsWith(`/${memo.name}`);
|
const isInMemoDetailPage = location.pathname.startsWith(`/${memo.name}`);
|
||||||
|
|
||||||
const memoUpdatedCallback = () => {
|
const memoUpdatedCallback = () => {
|
||||||
// Refresh user stats.
|
// Refresh user stats.
|
||||||
userStatsStore.setStateId();
|
userStore.setStatsStateId();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTogglePinMemoBtnClick = async () => {
|
const handleTogglePinMemoBtnClick = async () => {
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import { Dropdown, Menu, MenuButton } from "@mui/joy";
|
import { Dropdown, Menu, MenuButton } from "@mui/joy";
|
||||||
import { Button } from "@usememos/mui";
|
import { Button } from "@usememos/mui";
|
||||||
import { HashIcon } from "lucide-react";
|
import { HashIcon } from "lucide-react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import useClickAway from "react-use/lib/useClickAway";
|
import useClickAway from "react-use/lib/useClickAway";
|
||||||
import OverflowTip from "@/components/kit/OverflowTip";
|
import OverflowTip from "@/components/kit/OverflowTip";
|
||||||
import { useUserStatsTags } from "@/store/v1";
|
import { userStore } from "@/store/v2";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import { EditorRefActions } from "../Editor";
|
import { EditorRefActions } from "../Editor";
|
||||||
|
|
||||||
@@ -12,12 +13,12 @@ interface Props {
|
|||||||
editorRef: React.RefObject<EditorRefActions>;
|
editorRef: React.RefObject<EditorRefActions>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagSelector = (props: Props) => {
|
const TagSelector = observer((props: Props) => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const { editorRef } = props;
|
const { editorRef } = props;
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const tags = Object.entries(useUserStatsTags())
|
const tags = Object.entries(userStore.state.tagCount)
|
||||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||||
.sort((a, b) => b[1] - a[1])
|
.sort((a, b) => b[1] - a[1])
|
||||||
.map(([tag]) => tag);
|
.map(([tag]) => tag);
|
||||||
@@ -71,6 +72,6 @@ const TagSelector = (props: Props) => {
|
|||||||
</Menu>
|
</Menu>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default TagSelector;
|
export default TagSelector;
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import getCaretCoordinates from "textarea-caret";
|
import getCaretCoordinates from "textarea-caret";
|
||||||
import OverflowTip from "@/components/kit/OverflowTip";
|
import OverflowTip from "@/components/kit/OverflowTip";
|
||||||
import { useUserStatsTags } from "@/store/v1";
|
import { userStore } from "@/store/v2";
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/utils";
|
||||||
import { EditorRefActions } from ".";
|
import { EditorRefActions } from ".";
|
||||||
|
|
||||||
@@ -13,12 +14,12 @@ type Props = {
|
|||||||
|
|
||||||
type Position = { left: number; top: number; height: number };
|
type Position = { left: number; top: number; height: number };
|
||||||
|
|
||||||
const TagSuggestions = ({ editorRef, editorActions }: Props) => {
|
const TagSuggestions = observer(({ editorRef, editorActions }: Props) => {
|
||||||
const [position, setPosition] = useState<Position | null>(null);
|
const [position, setPosition] = useState<Position | null>(null);
|
||||||
const [selected, select] = useState(0);
|
const [selected, select] = useState(0);
|
||||||
const selectedRef = useRef(selected);
|
const selectedRef = useRef(selected);
|
||||||
selectedRef.current = selected;
|
selectedRef.current = selected;
|
||||||
const tags = Object.entries(useUserStatsTags())
|
const tags = Object.entries(userStore.state.tagCount)
|
||||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||||
.sort((a, b) => b[1] - a[1])
|
.sort((a, b) => b[1] - a[1])
|
||||||
.map(([tag]) => tag);
|
.map(([tag]) => tag);
|
||||||
@@ -120,6 +121,6 @@ const TagSuggestions = ({ editorRef, editorActions }: Props) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default TagSuggestions;
|
export default TagSuggestions;
|
||||||
|
@@ -5,7 +5,7 @@ import { Link, useLocation } from "react-router-dom";
|
|||||||
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
import { useMemoStore, useUserStatsStore } from "@/store/v1";
|
import { useMemoStore } from "@/store/v1";
|
||||||
import { userStore, workspaceStore } from "@/store/v2";
|
import { userStore, workspaceStore } from "@/store/v2";
|
||||||
import { State } from "@/types/proto/api/v1/common";
|
import { State } from "@/types/proto/api/v1/common";
|
||||||
import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
|
import { MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
|
||||||
@@ -45,7 +45,6 @@ const MemoView: React.FC<Props> = (props: Props) => {
|
|||||||
const currentUser = useCurrentUser();
|
const currentUser = useCurrentUser();
|
||||||
const user = useCurrentUser();
|
const user = useCurrentUser();
|
||||||
const memoStore = useMemoStore();
|
const memoStore = useMemoStore();
|
||||||
const userStatsStore = useUserStatsStore();
|
|
||||||
const [showEditor, setShowEditor] = useState<boolean>(false);
|
const [showEditor, setShowEditor] = useState<boolean>(false);
|
||||||
const [creator, setCreator] = useState(userStore.getUserByName(memo.creator));
|
const [creator, setCreator] = useState(userStore.getUserByName(memo.creator));
|
||||||
const [showNSFWContent, setShowNSFWContent] = useState(props.showNsfwContent);
|
const [showNSFWContent, setShowNSFWContent] = useState(props.showNsfwContent);
|
||||||
@@ -102,7 +101,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
const onEditorConfirm = () => {
|
const onEditorConfirm = () => {
|
||||||
setShowEditor(false);
|
setShowEditor(false);
|
||||||
userStatsStore.setStateId();
|
userStore.setStatsStateId();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onPinIconClick = async () => {
|
const onPinIconClick = async () => {
|
||||||
|
@@ -5,7 +5,6 @@ import React, { useState } from "react";
|
|||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { memoServiceClient } from "@/grpcweb";
|
import { memoServiceClient } from "@/grpcweb";
|
||||||
import useLoading from "@/hooks/useLoading";
|
import useLoading from "@/hooks/useLoading";
|
||||||
import { useUserStatsStore } from "@/store/v1";
|
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import { generateDialog } from "./Dialog";
|
import { generateDialog } from "./Dialog";
|
||||||
|
|
||||||
@@ -16,7 +15,6 @@ interface Props extends DialogProps {
|
|||||||
const RenameTagDialog: React.FC<Props> = (props: Props) => {
|
const RenameTagDialog: React.FC<Props> = (props: Props) => {
|
||||||
const { tag, destroy } = props;
|
const { tag, destroy } = props;
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const userStatsStore = useUserStatsStore();
|
|
||||||
const [newName, setNewName] = useState(tag);
|
const [newName, setNewName] = useState(tag);
|
||||||
const requestState = useLoading(false);
|
const requestState = useLoading(false);
|
||||||
|
|
||||||
@@ -41,7 +39,6 @@ const RenameTagDialog: React.FC<Props> = (props: Props) => {
|
|||||||
newTag: newName,
|
newTag: newName,
|
||||||
});
|
});
|
||||||
toast.success("Rename tag successfully");
|
toast.success("Rename tag successfully");
|
||||||
userStatsStore.setStateId();
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
toast.error(error.details);
|
toast.error(error.details);
|
||||||
|
@@ -2,21 +2,22 @@ import { Tooltip } from "@mui/joy";
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { countBy } from "lodash-es";
|
import { countBy } from "lodash-es";
|
||||||
import { CheckCircleIcon, ChevronRightIcon, ChevronLeftIcon, Code2Icon, LinkIcon, ListTodoIcon } from "lucide-react";
|
import { CheckCircleIcon, ChevronRightIcon, ChevronLeftIcon, Code2Icon, LinkIcon, ListTodoIcon } from "lucide-react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import DatePicker from "react-datepicker";
|
import DatePicker from "react-datepicker";
|
||||||
import "react-datepicker/dist/react-datepicker.css";
|
import "react-datepicker/dist/react-datepicker.css";
|
||||||
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
import useAsyncEffect from "@/hooks/useAsyncEffect";
|
||||||
import i18n from "@/i18n";
|
import i18n from "@/i18n";
|
||||||
import { useMemoFilterStore, useUserStatsStore } from "@/store/v1";
|
import { useMemoFilterStore } from "@/store/v1";
|
||||||
|
import { userStore } from "@/store/v2";
|
||||||
import { UserStats_MemoTypeStats } from "@/types/proto/api/v1/user_service";
|
import { UserStats_MemoTypeStats } from "@/types/proto/api/v1/user_service";
|
||||||
import { cn } from "@/utils";
|
import { cn } from "@/utils";
|
||||||
import { useTranslate } from "@/utils/i18n";
|
import { useTranslate } from "@/utils/i18n";
|
||||||
import ActivityCalendar from "./ActivityCalendar";
|
import ActivityCalendar from "./ActivityCalendar";
|
||||||
|
|
||||||
const StatisticsView = () => {
|
const StatisticsView = observer(() => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const memoFilterStore = useMemoFilterStore();
|
const memoFilterStore = useMemoFilterStore();
|
||||||
const userStatsStore = useUserStatsStore();
|
|
||||||
const [memoTypeStats, setMemoTypeStats] = useState<UserStats_MemoTypeStats>(UserStats_MemoTypeStats.fromPartial({}));
|
const [memoTypeStats, setMemoTypeStats] = useState<UserStats_MemoTypeStats>(UserStats_MemoTypeStats.fromPartial({}));
|
||||||
const [activityStats, setActivityStats] = useState<Record<string, number>>({});
|
const [activityStats, setActivityStats] = useState<Record<string, number>>({});
|
||||||
const [selectedDate] = useState(new Date());
|
const [selectedDate] = useState(new Date());
|
||||||
@@ -25,7 +26,7 @@ const StatisticsView = () => {
|
|||||||
useAsyncEffect(async () => {
|
useAsyncEffect(async () => {
|
||||||
const memoTypeStats = UserStats_MemoTypeStats.fromPartial({});
|
const memoTypeStats = UserStats_MemoTypeStats.fromPartial({});
|
||||||
const displayTimeList: Date[] = [];
|
const displayTimeList: Date[] = [];
|
||||||
for (const stats of Object.values(userStatsStore.userStatsByName)) {
|
for (const stats of Object.values(userStore.state.userStatsByName)) {
|
||||||
displayTimeList.push(...stats.memoDisplayTimestamps);
|
displayTimeList.push(...stats.memoDisplayTimestamps);
|
||||||
if (stats.memoTypeStats) {
|
if (stats.memoTypeStats) {
|
||||||
memoTypeStats.codeCount += stats.memoTypeStats.codeCount;
|
memoTypeStats.codeCount += stats.memoTypeStats.codeCount;
|
||||||
@@ -36,7 +37,7 @@ const StatisticsView = () => {
|
|||||||
}
|
}
|
||||||
setMemoTypeStats(memoTypeStats);
|
setMemoTypeStats(memoTypeStats);
|
||||||
setActivityStats(countBy(displayTimeList.map((date) => dayjs(date).format("YYYY-MM-DD"))));
|
setActivityStats(countBy(displayTimeList.map((date) => dayjs(date).format("YYYY-MM-DD"))));
|
||||||
}, [userStatsStore.userStatsByName, userStatsStore.stateId]);
|
}, [userStore.state.userStatsByName]);
|
||||||
|
|
||||||
const onCalendarClick = (date: string) => {
|
const onCalendarClick = (date: string) => {
|
||||||
memoFilterStore.removeFilter((f) => f.factor === "displayTime");
|
memoFilterStore.removeFilter((f) => f.factor === "displayTime");
|
||||||
@@ -135,6 +136,6 @@ const StatisticsView = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
export default StatisticsView;
|
export default StatisticsView;
|
||||||
|
@@ -31,7 +31,7 @@ const UserProfile = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
userStore
|
userStore
|
||||||
.fetchUserByUsername(username)
|
.getOrFetchUserByUsername(username)
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
setUser(user);
|
setUser(user);
|
||||||
loadingState.setFinish();
|
loadingState.setFinish();
|
||||||
|
@@ -2,4 +2,3 @@ export * from "./memo";
|
|||||||
export * from "./resourceName";
|
export * from "./resourceName";
|
||||||
export * from "./resource";
|
export * from "./resource";
|
||||||
export * from "./memoFilter";
|
export * from "./memoFilter";
|
||||||
export * from "./userStats";
|
|
||||||
|
@@ -1,51 +0,0 @@
|
|||||||
import { uniqueId } from "lodash-es";
|
|
||||||
import { create } from "zustand";
|
|
||||||
import { combine } from "zustand/middleware";
|
|
||||||
import { userServiceClient } from "@/grpcweb";
|
|
||||||
import { UserStats } from "@/types/proto/api/v1/user_service";
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
// stateId is used to identify the store instance state.
|
|
||||||
// It should be update when any state change.
|
|
||||||
stateId: string;
|
|
||||||
userStatsByName: Record<string, UserStats>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDefaultState = (): State => ({
|
|
||||||
stateId: uniqueId(),
|
|
||||||
userStatsByName: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const useUserStatsStore = create(
|
|
||||||
combine(getDefaultState(), (set, get) => ({
|
|
||||||
setState: (state: State) => set(state),
|
|
||||||
getState: () => get(),
|
|
||||||
listUserStats: async (user?: string) => {
|
|
||||||
const userStatsByName: Record<string, UserStats> = {};
|
|
||||||
if (!user) {
|
|
||||||
const { userStats } = await userServiceClient.listAllUserStats({});
|
|
||||||
for (const stats of userStats) {
|
|
||||||
userStatsByName[stats.name] = stats;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const userStats = await userServiceClient.getUserStats({ name: user });
|
|
||||||
userStatsByName[user] = userStats;
|
|
||||||
}
|
|
||||||
set({ ...get(), userStatsByName });
|
|
||||||
},
|
|
||||||
setStateId: (id = uniqueId()) => {
|
|
||||||
set({ ...get(), stateId: id });
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const useUserStatsTags = () => {
|
|
||||||
const userStatsStore = useUserStatsStore();
|
|
||||||
const tagAmounts: Record<string, number> = {};
|
|
||||||
for (const userStats of Object.values(userStatsStore.getState().userStatsByName)) {
|
|
||||||
for (const tag of Object.keys(userStats.tagCount)) {
|
|
||||||
tagAmounts[tag] = (tagAmounts[tag] || 0) + userStats.tagCount[tag];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tagAmounts;
|
|
||||||
};
|
|
@@ -1,7 +1,8 @@
|
|||||||
|
import { uniqueId } from "lodash-es";
|
||||||
import { makeAutoObservable } from "mobx";
|
import { makeAutoObservable } from "mobx";
|
||||||
import { authServiceClient, inboxServiceClient, userServiceClient } from "@/grpcweb";
|
import { authServiceClient, inboxServiceClient, userServiceClient } from "@/grpcweb";
|
||||||
import { Inbox } from "@/types/proto/api/v1/inbox_service";
|
import { Inbox } from "@/types/proto/api/v1/inbox_service";
|
||||||
import { Shortcut, User, UserSetting } from "@/types/proto/api/v1/user_service";
|
import { Shortcut, User, UserSetting, UserStats } from "@/types/proto/api/v1/user_service";
|
||||||
import workspaceStore from "./workspace";
|
import workspaceStore from "./workspace";
|
||||||
|
|
||||||
class LocalState {
|
class LocalState {
|
||||||
@@ -10,6 +11,20 @@ class LocalState {
|
|||||||
shortcuts: Shortcut[] = [];
|
shortcuts: Shortcut[] = [];
|
||||||
inboxes: Inbox[] = [];
|
inboxes: Inbox[] = [];
|
||||||
userMapByName: Record<string, User> = {};
|
userMapByName: Record<string, User> = {};
|
||||||
|
userStatsByName: Record<string, UserStats> = {};
|
||||||
|
|
||||||
|
// The state id of user stats map.
|
||||||
|
statsStateId = uniqueId();
|
||||||
|
|
||||||
|
get tagCount() {
|
||||||
|
const tagCount: Record<string, number> = {};
|
||||||
|
for (const stats of Object.values(this.userStatsByName)) {
|
||||||
|
for (const tag of Object.keys(stats.tagCount)) {
|
||||||
|
tagCount[tag] = (tagCount[tag] || 0) + stats.tagCount[tag];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tagCount;
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
@@ -40,13 +55,19 @@ const userStore = (() => {
|
|||||||
return user;
|
return user;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchUserByUsername = async (username: string) => {
|
const getOrFetchUserByUsername = async (username: string) => {
|
||||||
|
const userMap = state.userMapByName;
|
||||||
|
for (const name in userMap) {
|
||||||
|
if (userMap[name].username === username) {
|
||||||
|
return userMap[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
const user = await userServiceClient.getUserByUsername({
|
const user = await userServiceClient.getUserByUsername({
|
||||||
username,
|
username,
|
||||||
});
|
});
|
||||||
state.setPartial({
|
state.setPartial({
|
||||||
userMapByName: {
|
userMapByName: {
|
||||||
...state.userMapByName,
|
...userMap,
|
||||||
[user.name]: user,
|
[user.name]: user,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -138,10 +159,30 @@ const userStore = (() => {
|
|||||||
return updatedInbox;
|
return updatedInbox;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchUserStats = async (user?: string) => {
|
||||||
|
const userStatsByName: Record<string, UserStats> = {};
|
||||||
|
if (!user) {
|
||||||
|
const { userStats } = await userServiceClient.listAllUserStats({});
|
||||||
|
for (const stats of userStats) {
|
||||||
|
userStatsByName[stats.name] = stats;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const userStats = await userServiceClient.getUserStats({ name: user });
|
||||||
|
userStatsByName[user] = userStats;
|
||||||
|
}
|
||||||
|
state.setPartial({
|
||||||
|
userStatsByName,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setStatsStateId = (id = uniqueId()) => {
|
||||||
|
state.statsStateId = id;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
getOrFetchUserByName,
|
getOrFetchUserByName,
|
||||||
fetchUserByUsername,
|
getOrFetchUserByUsername,
|
||||||
getUserByName,
|
getUserByName,
|
||||||
fetchUsers,
|
fetchUsers,
|
||||||
updateUser,
|
updateUser,
|
||||||
@@ -150,6 +191,8 @@ const userStore = (() => {
|
|||||||
fetchShortcuts,
|
fetchShortcuts,
|
||||||
fetchInboxes,
|
fetchInboxes,
|
||||||
updateInbox,
|
updateInbox,
|
||||||
|
fetchUserStats,
|
||||||
|
setStatsStateId,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user