Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
012405f7
Commit
012405f7
authored
Feb 26, 2025
by
Johnny
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: user stats state
parent
81502d90
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
83 additions
and
93 deletions
+83
-93
HomeSidebar.tsx
web/src/components/HomeSidebar/HomeSidebar.tsx
+7
-7
TagsSection.tsx
web/src/components/HomeSidebar/TagsSection.tsx
+6
-6
MemoActionMenu.tsx
web/src/components/MemoActionMenu.tsx
+3
-3
TagSelector.tsx
web/src/components/MemoEditor/ActionButton/TagSelector.tsx
+5
-4
TagSuggestions.tsx
web/src/components/MemoEditor/Editor/TagSuggestions.tsx
+5
-4
MemoView.tsx
web/src/components/MemoView.tsx
+2
-3
RenameTagDialog.tsx
web/src/components/RenameTagDialog.tsx
+0
-3
StatisticsView.tsx
web/src/components/StatisticsView.tsx
+7
-6
UserProfile.tsx
web/src/pages/UserProfile.tsx
+1
-1
index.ts
web/src/store/v1/index.ts
+0
-1
userStats.ts
web/src/store/v1/userStats.ts
+0
-51
user.ts
web/src/store/v2/user.ts
+47
-4
No files found.
web/src/components/HomeSidebar/HomeSidebar.tsx
View file @
012405f7
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 {
...
@@ -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) => {
...
@@ -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
.
f
etchUserByUsername
(
username
||
""
);
const
user
=
await
userStore
.
getOrF
etchUserByUsername
(
username
||
""
);
parent
=
user
.
name
;
parent
=
user
.
name
;
}
}
await
userSt
atsStore
.
list
UserStats
(
parent
);
await
userSt
ore
.
fetch
UserStats
(
parent
);
},
},
300
,
300
,
[
memoList
.
size
(),
userSt
atsStore
.
s
tateId
,
location
.
pathname
],
[
memoList
.
size
(),
userSt
ore
.
state
.
statsS
tateId
,
location
.
pathname
],
);
);
return
(
return
(
...
@@ -93,6 +93,6 @@ const HomeSidebar = (props: Props) => {
...
@@ -93,6 +93,6 @@ const HomeSidebar = (props: Props) => {
</
div
>
</
div
>
</
aside
>
</
aside
>
);
);
};
}
)
;
export
default
HomeSidebar
;
export
default
HomeSidebar
;
web/src/components/HomeSidebar/TagsSection.tsx
View file @
012405f7
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 {
...
@@ -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
(
use
UserStatsTags
()
)
const
tags
=
Object
.
entries
(
use
rStore
.
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) => {
...
@@ -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) => {
...
@@ -114,6 +114,6 @@ const TagsSection = (props: Props) => {
)
}
)
}
</
div
>
</
div
>
);
);
};
}
)
;
export
default
TagsSection
;
export
default
TagsSection
;
web/src/components/MemoActionMenu.tsx
View file @
012405f7
...
@@ -15,7 +15,8 @@ import toast from "react-hot-toast";
...
@@ -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) => {
...
@@ -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.
userSt
atsStore
.
set
StateId
();
userSt
ore
.
setStats
StateId
();
};
};
const
handleTogglePinMemoBtnClick
=
async
()
=>
{
const
handleTogglePinMemoBtnClick
=
async
()
=>
{
...
...
web/src/components/MemoEditor/ActionButton/TagSelector.tsx
View file @
012405f7
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
{
use
UserStatsTags
}
from
"@/store/v1
"
;
import
{
use
rStore
}
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 {
...
@@ -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
(
use
UserStatsTags
()
)
const
tags
=
Object
.
entries
(
use
rStore
.
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) => {
...
@@ -71,6 +72,6 @@ const TagSelector = (props: Props) => {
</
Menu
>
</
Menu
>
</
Dropdown
>
</
Dropdown
>
);
);
};
}
)
;
export
default
TagSelector
;
export
default
TagSelector
;
web/src/components/MemoEditor/Editor/TagSuggestions.tsx
View file @
012405f7
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
{
use
UserStatsTags
}
from
"@/store/v1
"
;
import
{
use
rStore
}
from
"@/store/v2
"
;
import
{
cn
}
from
"@/utils"
;
import
{
cn
}
from
"@/utils"
;
import
{
EditorRefActions
}
from
"."
;
import
{
EditorRefActions
}
from
"."
;
...
@@ -13,12 +14,12 @@ type Props = {
...
@@ -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
(
use
UserStatsTags
()
)
const
tags
=
Object
.
entries
(
use
rStore
.
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) => {
...
@@ -120,6 +121,6 @@ const TagSuggestions = ({ editorRef, editorActions }: Props) => {
))
}
))
}
</
div
>
</
div
>
);
);
};
}
)
;
export
default
TagSuggestions
;
export
default
TagSuggestions
;
web/src/components/MemoView.tsx
View file @
012405f7
...
@@ -5,7 +5,7 @@ import { Link, useLocation } from "react-router-dom";
...
@@ -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) => {
...
@@ -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) => {
...
@@ -102,7 +101,7 @@ const MemoView: React.FC<Props> = (props: Props) => {
const
onEditorConfirm
=
()
=>
{
const
onEditorConfirm
=
()
=>
{
setShowEditor
(
false
);
setShowEditor
(
false
);
userSt
atsStore
.
set
StateId
();
userSt
ore
.
setStats
StateId
();
};
};
const
onPinIconClick
=
async
()
=>
{
const
onPinIconClick
=
async
()
=>
{
...
...
web/src/components/RenameTagDialog.tsx
View file @
012405f7
...
@@ -5,7 +5,6 @@ import React, { useState } from "react";
...
@@ -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 {
...
@@ -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) => {
...
@@ -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
);
...
...
web/src/components/StatisticsView.tsx
View file @
012405f7
...
@@ -2,21 +2,22 @@ import { Tooltip } from "@mui/joy";
...
@@ -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 = () => {
...
@@ -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
(
userSt
atsStor
e
.
userStatsByName
))
{
for
(
const
stats
of
Object
.
values
(
userSt
ore
.
stat
e
.
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 = () => {
...
@@ -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"
))));
},
[
userSt
atsStore
.
userStatsByName
,
userStatsStore
.
stateId
]);
},
[
userSt
ore
.
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 = () => {
...
@@ -135,6 +136,6 @@ const StatisticsView = () => {
</
div
>
</
div
>
</
div
>
</
div
>
);
);
};
}
)
;
export
default
StatisticsView
;
export
default
StatisticsView
;
web/src/pages/UserProfile.tsx
View file @
012405f7
...
@@ -31,7 +31,7 @@ const UserProfile = () => {
...
@@ -31,7 +31,7 @@ const UserProfile = () => {
}
}
userStore
userStore
.
f
etchUserByUsername
(
username
)
.
getOrF
etchUserByUsername
(
username
)
.
then
((
user
)
=>
{
.
then
((
user
)
=>
{
setUser
(
user
);
setUser
(
user
);
loadingState
.
setFinish
();
loadingState
.
setFinish
();
...
...
web/src/store/v1/index.ts
View file @
012405f7
...
@@ -2,4 +2,3 @@ export * from "./memo";
...
@@ -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"
;
web/src/store/v1/userStats.ts
deleted
100644 → 0
View file @
81502d90
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
;
};
web/src/store/v2/user.ts
View file @
012405f7
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 {
...
@@ -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 = (() => {
...
@@ -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 = (() => {
...
@@ -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
,
f
etchUserByUsername
,
getOrF
etchUserByUsername
,
getUserByName
,
getUserByName
,
fetchUsers
,
fetchUsers
,
updateUser
,
updateUser
,
...
@@ -150,6 +191,8 @@ const userStore = (() => {
...
@@ -150,6 +191,8 @@ const userStore = (() => {
fetchShortcuts
,
fetchShortcuts
,
fetchInboxes
,
fetchInboxes
,
updateInbox
,
updateInbox
,
fetchUserStats
,
setStatsStateId
,
};
};
})();
})();
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment