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
10c9bb08
Commit
10c9bb08
authored
Jul 19, 2024
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: retire timeline page
parent
17ecfb5c
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
36 additions
and
331 deletions
+36
-331
ActivityCalendar.tsx
web/src/components/ActivityCalendar.tsx
+6
-6
Navigation.tsx
web/src/components/Navigation.tsx
+2
-8
TimelineSidebar.tsx
web/src/components/TimelineSidebar/TimelineSidebar.tsx
+0
-25
TimelineSidebarDrawer.tsx
web/src/components/TimelineSidebar/TimelineSidebarDrawer.tsx
+0
-37
index.ts
web/src/components/TimelineSidebar/index.ts
+0
-4
UserStatisticsView.tsx
web/src/components/UserStatisticsView.tsx
+26
-1
RootLayout.tsx
web/src/layouts/RootLayout.tsx
+1
-16
en.json
web/src/locales/en.json
+0
-3
Home.tsx
web/src/pages/Home.tsx
+0
-7
Timeline.tsx
web/src/pages/Timeline.tsx
+0
-216
index.tsx
web/src/router/index.tsx
+1
-8
No files found.
web/src/components/ActivityCalendar.tsx
View file @
10c9bb08
...
@@ -18,11 +18,11 @@ const getCellAdditionalStyles = (count: number, maxCount: number) => {
...
@@ -18,11 +18,11 @@ const getCellAdditionalStyles = (count: number, maxCount: number) => {
const
ratio
=
count
/
maxCount
;
const
ratio
=
count
/
maxCount
;
if
(
ratio
>
0.7
)
{
if
(
ratio
>
0.7
)
{
return
"bg-
blue
-600 text-gray-100 dark:opacity-80"
;
return
"bg-
teal
-600 text-gray-100 dark:opacity-80"
;
}
else
if
(
ratio
>
0.4
)
{
}
else
if
(
ratio
>
0.4
)
{
return
"bg-
blue-400 text-gray-2
00 dark:opacity-80"
;
return
"bg-
teal-400 text-gray-1
00 dark:opacity-80"
;
}
else
{
}
else
{
return
"bg-
blue-300 text-gray-6
00 dark:opacity-80"
;
return
"bg-
teal-300 text-gray-1
00 dark:opacity-80"
;
}
}
};
};
...
@@ -48,7 +48,7 @@ const ActivityCalendar = (props: Props) => {
...
@@ -48,7 +48,7 @@ const ActivityCalendar = (props: Props) => {
}
}
return
(
return
(
<
div
className=
{
clsx
(
"w-
36 h-auto p-0.5
shrink-0 grid grid-cols-7 grid-flow-row gap-1"
)
}
>
<
div
className=
{
clsx
(
"w-
full h-auto
shrink-0 grid grid-cols-7 grid-flow-row gap-1"
)
}
>
{
days
.
map
((
day
,
index
)
=>
{
{
days
.
map
((
day
,
index
)
=>
{
const
date
=
getNormalizedDateString
(
const
date
=
getNormalizedDateString
(
getDateWithOffset
(
`${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`
),
getDateWithOffset
(
`${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`
),
...
@@ -61,7 +61,7 @@ const ActivityCalendar = (props: Props) => {
...
@@ -61,7 +61,7 @@ const ActivityCalendar = (props: Props) => {
<
Tooltip
className=
"shrink-0"
key=
{
`${date}-${index}`
}
title=
{
tooltipText
}
placement=
"top"
arrow
>
<
Tooltip
className=
"shrink-0"
key=
{
`${date}-${index}`
}
title=
{
tooltipText
}
placement=
"top"
arrow
>
<
div
<
div
className=
{
clsx
(
className=
{
clsx
(
"w-
4 h-4 text-[9px] rounded-md
flex justify-center items-center border"
,
"w-
6 h-6 text-xs rounded-xl
flex justify-center items-center border"
,
getCellAdditionalStyles
(
count
,
maxCount
),
getCellAdditionalStyles
(
count
,
maxCount
),
isToday
&&
"border-gray-600 dark:border-zinc-300"
,
isToday
&&
"border-gray-600 dark:border-zinc-300"
,
isSelected
&&
"font-bold border-gray-600 dark:border-zinc-300"
,
isSelected
&&
"font-bold border-gray-600 dark:border-zinc-300"
,
...
@@ -77,7 +77,7 @@ const ActivityCalendar = (props: Props) => {
...
@@ -77,7 +77,7 @@ const ActivityCalendar = (props: Props) => {
<
div
<
div
key=
{
`${date}-${index}`
}
key=
{
`${date}-${index}`
}
className=
{
clsx
(
className=
{
clsx
(
"shrink-0 opacity-30 w-
4 h-4 rounded-md
flex justify-center items-center border border-transparent"
,
"shrink-0 opacity-30 w-
6 h-6 rounded-xl
flex justify-center items-center border border-transparent"
,
getCellAdditionalStyles
(
count
,
maxCount
),
getCellAdditionalStyles
(
count
,
maxCount
),
)
}
)
}
></
div
>
></
div
>
...
...
web/src/components/Navigation.tsx
View file @
10c9bb08
...
@@ -50,16 +50,10 @@ const Navigation = (props: Props) => {
...
@@ -50,16 +50,10 @@ const Navigation = (props: Props) => {
const
homeNavLink
:
NavLinkItem
=
{
const
homeNavLink
:
NavLinkItem
=
{
id
:
"header-home"
,
id
:
"header-home"
,
path
:
Routes
.
HOME
,
path
:
Routes
.
ROOT
,
title
:
t
(
"common.home"
),
title
:
t
(
"common.home"
),
icon
:
<
Icon
.
Home
className=
"w-6 h-auto opacity-70 shrink-0"
/>,
icon
:
<
Icon
.
Home
className=
"w-6 h-auto opacity-70 shrink-0"
/>,
};
};
const
timelineNavLink
:
NavLinkItem
=
{
id
:
"header-timeline"
,
path
:
Routes
.
TIMELINE
,
title
:
t
(
"timeline.title"
),
icon
:
<
Icon
.
GanttChartSquare
className=
"w-6 h-auto opacity-70 shrink-0"
/>,
};
const
resourcesNavLink
:
NavLinkItem
=
{
const
resourcesNavLink
:
NavLinkItem
=
{
id
:
"header-resources"
,
id
:
"header-resources"
,
path
:
Routes
.
RESOURCES
,
path
:
Routes
.
RESOURCES
,
...
@@ -117,7 +111,7 @@ const Navigation = (props: Props) => {
...
@@ -117,7 +111,7 @@ const Navigation = (props: Props) => {
};
};
const
navLinks
:
NavLinkItem
[]
=
user
const
navLinks
:
NavLinkItem
[]
=
user
?
[
homeNavLink
,
timelineNavLink
,
resourcesNavLink
,
exploreNavLink
,
profileNavLink
,
inboxNavLink
,
archivedNavLink
,
settingNavLink
]
?
[
homeNavLink
,
resourcesNavLink
,
exploreNavLink
,
profileNavLink
,
inboxNavLink
,
archivedNavLink
,
settingNavLink
]
:
[
exploreNavLink
,
signInNavLink
,
aboutNavLink
];
:
[
exploreNavLink
,
signInNavLink
,
aboutNavLink
];
return
(
return
(
...
...
web/src/components/TimelineSidebar/TimelineSidebar.tsx
deleted
100644 → 0
View file @
17ecfb5c
import
clsx
from
"clsx"
;
import
TagsSection
from
"../HomeSidebar/TagsSection"
;
import
SearchBar
from
"../SearchBar"
;
import
UserStatisticsView
from
"../UserStatisticsView"
;
interface
Props
{
className
?:
string
;
}
const
TimelineSidebar
=
(
props
:
Props
)
=>
{
return
(
<
aside
className=
{
clsx
(
"relative w-full h-auto max-h-screen overflow-auto hide-scrollbar flex flex-col justify-start items-start"
,
props
.
className
,
)
}
>
<
SearchBar
/>
<
UserStatisticsView
/>
<
TagsSection
/>
</
aside
>
);
};
export
default
TimelineSidebar
;
web/src/components/TimelineSidebar/TimelineSidebarDrawer.tsx
deleted
100644 → 0
View file @
17ecfb5c
import
{
Drawer
,
IconButton
}
from
"@mui/joy"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useLocation
}
from
"react-router-dom"
;
import
Icon
from
"../Icon"
;
import
TimelineSidebar
from
"./TimelineSidebar"
;
const
TimelineSidebarDrawer
=
()
=>
{
const
location
=
useLocation
();
const
[
open
,
setOpen
]
=
useState
(
false
);
useEffect
(()
=>
{
setOpen
(
false
);
},
[
location
.
pathname
]);
const
toggleDrawer
=
(
inOpen
:
boolean
)
=>
(
event
:
React
.
KeyboardEvent
|
React
.
MouseEvent
)
=>
{
if
(
event
.
type
===
"keydown"
&&
((
event
as
React
.
KeyboardEvent
).
key
===
"Tab"
||
(
event
as
React
.
KeyboardEvent
).
key
===
"Shift"
))
{
return
;
}
setOpen
(
inOpen
);
};
return
(
<>
<
IconButton
onClick=
{
toggleDrawer
(
true
)
}
>
<
Icon
.
Search
className=
"w-5 h-auto dark:text-gray-400"
/>
</
IconButton
>
<
Drawer
anchor=
"right"
size=
"sm"
open=
{
open
}
onClose=
{
toggleDrawer
(
false
)
}
>
<
div
className=
"w-full h-full px-4 bg-zinc-100 dark:bg-zinc-900"
>
<
TimelineSidebar
className=
"py-4"
/>
</
div
>
</
Drawer
>
</>
);
};
export
default
TimelineSidebarDrawer
;
web/src/components/TimelineSidebar/index.ts
deleted
100644 → 0
View file @
17ecfb5c
import
TimelineSidebar
from
"./TimelineSidebar"
;
import
TimelineSidebarDrawer
from
"./TimelineSidebarDrawer"
;
export
{
TimelineSidebar
,
TimelineSidebarDrawer
};
web/src/components/UserStatisticsView.tsx
View file @
10c9bb08
import
{
Divider
,
Tooltip
}
from
"@mui/joy"
;
import
{
Divider
,
Tooltip
}
from
"@mui/joy"
;
import
clsx
from
"clsx"
;
import
clsx
from
"clsx"
;
import
dayjs
from
"dayjs"
;
import
{
useState
}
from
"react"
;
import
{
useState
}
from
"react"
;
import
toast
from
"react-hot-toast"
;
import
toast
from
"react-hot-toast"
;
import
{
memoServiceClient
}
from
"@/grpcweb"
;
import
{
memoServiceClient
}
from
"@/grpcweb"
;
import
useAsyncEffect
from
"@/hooks/useAsyncEffect"
;
import
useAsyncEffect
from
"@/hooks/useAsyncEffect"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
i18n
from
"@/i18n"
;
import
{
useFilterStore
}
from
"@/store/module"
;
import
{
useFilterStore
}
from
"@/store/module"
;
import
{
useMemoStore
}
from
"@/store/v1"
;
import
{
useMemoStore
}
from
"@/store/v1"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
ActivityCalendar
from
"./ActivityCalendar"
;
import
Icon
from
"./Icon"
;
import
Icon
from
"./Icon"
;
interface
UserMemoStats
{
interface
UserMemoStats
{
...
@@ -25,6 +28,8 @@ const UserStatisticsView = () => {
...
@@ -25,6 +28,8 @@ const UserStatisticsView = () => {
const
[
memoAmount
,
setMemoAmount
]
=
useState
(
0
);
const
[
memoAmount
,
setMemoAmount
]
=
useState
(
0
);
const
[
isRequesting
,
setIsRequesting
]
=
useState
(
false
);
const
[
isRequesting
,
setIsRequesting
]
=
useState
(
false
);
const
[
memoStats
,
setMemoStats
]
=
useState
<
UserMemoStats
>
({
link
:
0
,
taskList
:
0
,
code
:
0
,
incompleteTasks
:
0
});
const
[
memoStats
,
setMemoStats
]
=
useState
<
UserMemoStats
>
({
link
:
0
,
taskList
:
0
,
code
:
0
,
incompleteTasks
:
0
});
const
[
activityStats
,
setActivityStats
]
=
useState
<
Record
<
string
,
number
>>
({});
const
monthString
=
dayjs
(
new
Date
().
toDateString
()).
format
(
"YYYY-MM"
);
const
days
=
Math
.
ceil
((
Date
.
now
()
-
currentUser
.
createTime
!
.
getTime
())
/
86400000
);
const
days
=
Math
.
ceil
((
Date
.
now
()
-
currentUser
.
createTime
!
.
getTime
())
/
86400000
);
const
filter
=
filterStore
.
state
;
const
filter
=
filterStore
.
state
;
...
@@ -50,6 +55,21 @@ const UserStatisticsView = () => {
...
@@ -50,6 +55,21 @@ const UserStatisticsView = () => {
});
});
setMemoStats
(
memoStats
);
setMemoStats
(
memoStats
);
setMemoAmount
(
properties
.
length
);
setMemoAmount
(
properties
.
length
);
const
filters
=
[
`row_status == "NORMAL"`
];
const
{
stats
}
=
await
memoServiceClient
.
getUserMemosStats
({
name
:
currentUser
.
name
,
timezone
:
Intl
.
DateTimeFormat
().
resolvedOptions
().
timeZone
,
filter
:
filters
.
join
(
" && "
),
});
setActivityStats
(
Object
.
fromEntries
(
Object
.
entries
(
stats
).
filter
(([
date
])
=>
{
return
dayjs
(
date
).
format
(
"YYYY-MM"
)
===
monthString
;
}),
),
);
setIsRequesting
(
false
);
setIsRequesting
(
false
);
},
[
memoStore
.
stateId
]);
},
[
memoStore
.
stateId
]);
...
@@ -64,7 +84,9 @@ const UserStatisticsView = () => {
...
@@ -64,7 +84,9 @@ const UserStatisticsView = () => {
return
(
return
(
<
div
className=
"group w-full border mt-2 py-2 px-3 rounded-lg space-y-0.5 text-gray-500 dark:text-gray-400 bg-zinc-50 dark:bg-zinc-900 dark:border-zinc-800"
>
<
div
className=
"group w-full border mt-2 py-2 px-3 rounded-lg space-y-0.5 text-gray-500 dark:text-gray-400 bg-zinc-50 dark:bg-zinc-900 dark:border-zinc-800"
>
<
div
className=
"w-full mb-1 flex flex-row justify-between items-center"
>
<
div
className=
"w-full mb-1 flex flex-row justify-between items-center"
>
<
p
className=
"text-sm font-medium leading-6 dark:text-gray-500"
>
{
t
(
"common.statistics"
)
}
</
p
>
<
p
className=
"text-sm font-medium leading-6 dark:text-gray-400"
>
{
new
Date
().
toLocaleDateString
(
i18n
.
language
,
{
month
:
"long"
,
day
:
"numeric"
})
}
</
p
>
<
div
className=
"group-hover:block hidden"
>
<
div
className=
"group-hover:block hidden"
>
<
Tooltip
title=
{
"Refresh"
}
placement=
"top"
>
<
Tooltip
title=
{
"Refresh"
}
placement=
"top"
>
<
Icon
.
RefreshCcw
<
Icon
.
RefreshCcw
...
@@ -74,6 +96,9 @@ const UserStatisticsView = () => {
...
@@ -74,6 +96,9 @@ const UserStatisticsView = () => {
</
Tooltip
>
</
Tooltip
>
</
div
>
</
div
>
</
div
>
</
div
>
<
div
className=
"w-full pb-2"
>
<
ActivityCalendar
month=
{
monthString
}
selectedDate=
{
new
Date
().
toDateString
()
}
data=
{
activityStats
}
/>
</
div
>
<
div
className=
"w-full grid grid-cols-1 gap-x-4"
>
<
div
className=
"w-full grid grid-cols-1 gap-x-4"
>
<
div
className=
"w-full flex justify-between items-center"
>
<
div
className=
"w-full flex justify-between items-center"
>
<
div
className=
"w-auto flex justify-start items-center"
>
<
div
className=
"w-auto flex justify-start items-center"
>
...
...
web/src/layouts/RootLayout.tsx
View file @
10c9bb08
...
@@ -14,31 +14,16 @@ const RootLayout = () => {
...
@@ -14,31 +14,16 @@ const RootLayout = () => {
const
location
=
useLocation
();
const
location
=
useLocation
();
const
{
sm
}
=
useResponsiveWidth
();
const
{
sm
}
=
useResponsiveWidth
();
const
currentUser
=
useCurrentUser
();
const
currentUser
=
useCurrentUser
();
const
[
lastVisited
]
=
useLocalStorage
<
string
>
(
"lastVisited"
,
"/home"
);
const
[
collapsed
,
setCollapsed
]
=
useLocalStorage
<
boolean
>
(
"navigation-collapsed"
,
false
);
const
[
collapsed
,
setCollapsed
]
=
useLocalStorage
<
boolean
>
(
"navigation-collapsed"
,
false
);
const
[
initialized
,
setInitialized
]
=
useState
(
false
);
const
[
initialized
,
setInitialized
]
=
useState
(
false
);
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
!
currentUser
)
{
if
(
!
currentUser
)
{
if
(
if
(([
Routes
.
ROOT
,
Routes
.
RESOURCES
,
Routes
.
INBOX
,
Routes
.
ARCHIVED
,
Routes
.
SETTING
]
as
string
[]).
includes
(
location
.
pathname
))
{
([
Routes
.
ROOT
,
Routes
.
HOME
,
Routes
.
TIMELINE
,
Routes
.
RESOURCES
,
Routes
.
INBOX
,
Routes
.
ARCHIVED
,
Routes
.
SETTING
]
as
string
[]).
includes
(
location
.
pathname
,
)
)
{
window
.
location
.
href
=
Routes
.
EXPLORE
;
window
.
location
.
href
=
Routes
.
EXPLORE
;
return
;
return
;
}
}
}
else
{
if
(
location
.
pathname
===
Routes
.
ROOT
)
{
if
(
lastVisited
&&
([
Routes
.
HOME
,
Routes
.
TIMELINE
]
as
string
[]).
includes
(
lastVisited
))
{
window
.
location
.
href
=
lastVisited
;
}
else
{
window
.
location
.
href
=
Routes
.
HOME
;
}
return
;
}
}
}
setInitialized
(
true
);
setInitialized
(
true
);
},
[]);
},
[]);
...
...
web/src/locales/en.json
View file @
10c9bb08
...
@@ -307,8 +307,5 @@
...
@@ -307,8 +307,5 @@
"delete-confirm"
:
"Are you sure to delete this tag? All related memos will be archived."
,
"delete-confirm"
:
"Are you sure to delete this tag? All related memos will be archived."
,
"delete-tag"
:
"Delete Tag"
,
"delete-tag"
:
"Delete Tag"
,
"no-tag-found"
:
"No tag found"
"no-tag-found"
:
"No tag found"
},
"timeline"
:
{
"title"
:
"Timeline"
}
}
}
}
web/src/pages/Home.tsx
View file @
10c9bb08
import
{
Button
}
from
"@mui/joy"
;
import
{
Button
}
from
"@mui/joy"
;
import
clsx
from
"clsx"
;
import
clsx
from
"clsx"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
useLocalStorage
from
"react-use/lib/useLocalStorage"
;
import
Empty
from
"@/components/Empty"
;
import
Empty
from
"@/components/Empty"
;
import
{
HomeSidebar
,
HomeSidebarDrawer
}
from
"@/components/HomeSidebar"
;
import
{
HomeSidebar
,
HomeSidebarDrawer
}
from
"@/components/HomeSidebar"
;
import
Icon
from
"@/components/Icon"
;
import
Icon
from
"@/components/Icon"
;
...
@@ -13,7 +12,6 @@ import { getTimeStampByDate } from "@/helpers/datetime";
...
@@ -13,7 +12,6 @@ import { getTimeStampByDate } from "@/helpers/datetime";
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useFilterWithUrlParams
from
"@/hooks/useFilterWithUrlParams"
;
import
useFilterWithUrlParams
from
"@/hooks/useFilterWithUrlParams"
;
import
useResponsiveWidth
from
"@/hooks/useResponsiveWidth"
;
import
useResponsiveWidth
from
"@/hooks/useResponsiveWidth"
;
import
{
Routes
}
from
"@/router"
;
import
{
useMemoList
,
useMemoStore
}
from
"@/store/v1"
;
import
{
useMemoList
,
useMemoStore
}
from
"@/store/v1"
;
import
{
RowStatus
}
from
"@/types/proto/api/v1/common"
;
import
{
RowStatus
}
from
"@/types/proto/api/v1/common"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
...
@@ -24,7 +22,6 @@ const Home = () => {
...
@@ -24,7 +22,6 @@ const Home = () => {
const
user
=
useCurrentUser
();
const
user
=
useCurrentUser
();
const
memoStore
=
useMemoStore
();
const
memoStore
=
useMemoStore
();
const
memoList
=
useMemoList
();
const
memoList
=
useMemoList
();
const
[,
setLastVisited
]
=
useLocalStorage
<
string
>
(
"lastVisited"
,
Routes
.
HOME
);
const
[
isRequesting
,
setIsRequesting
]
=
useState
(
true
);
const
[
isRequesting
,
setIsRequesting
]
=
useState
(
true
);
const
[
nextPageToken
,
setNextPageToken
]
=
useState
<
string
>
(
""
);
const
[
nextPageToken
,
setNextPageToken
]
=
useState
<
string
>
(
""
);
const
filter
=
useFilterWithUrlParams
();
const
filter
=
useFilterWithUrlParams
();
...
@@ -33,10 +30,6 @@ const Home = () => {
...
@@ -33,10 +30,6 @@ const Home = () => {
.
sort
((
a
,
b
)
=>
getTimeStampByDate
(
b
.
displayTime
)
-
getTimeStampByDate
(
a
.
displayTime
))
.
sort
((
a
,
b
)
=>
getTimeStampByDate
(
b
.
displayTime
)
-
getTimeStampByDate
(
a
.
displayTime
))
.
sort
((
a
,
b
)
=>
Number
(
b
.
pinned
)
-
Number
(
a
.
pinned
));
.
sort
((
a
,
b
)
=>
Number
(
b
.
pinned
)
-
Number
(
a
.
pinned
));
useEffect
(()
=>
{
setLastVisited
(
Routes
.
HOME
);
},
[]);
useEffect
(()
=>
{
useEffect
(()
=>
{
memoList
.
reset
();
memoList
.
reset
();
fetchMemos
(
""
);
fetchMemos
(
""
);
...
...
web/src/pages/Timeline.tsx
deleted
100644 → 0
View file @
17ecfb5c
import
{
Button
,
IconButton
}
from
"@mui/joy"
;
import
clsx
from
"clsx"
;
import
dayjs
from
"dayjs"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
useLocalStorage
from
"react-use/lib/useLocalStorage"
;
import
ActivityCalendar
from
"@/components/ActivityCalendar"
;
import
Empty
from
"@/components/Empty"
;
import
Icon
from
"@/components/Icon"
;
import
showMemoEditorDialog
from
"@/components/MemoEditor/MemoEditorDialog"
;
import
MemoView
from
"@/components/MemoView"
;
import
MobileHeader
from
"@/components/MobileHeader"
;
import
{
TimelineSidebar
,
TimelineSidebarDrawer
}
from
"@/components/TimelineSidebar"
;
import
{
memoServiceClient
}
from
"@/grpcweb"
;
import
{
DAILY_TIMESTAMP
,
DEFAULT_LIST_MEMOS_PAGE_SIZE
}
from
"@/helpers/consts"
;
import
{
getTimeStampByDate
}
from
"@/helpers/datetime"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useFilterWithUrlParams
from
"@/hooks/useFilterWithUrlParams"
;
import
useResponsiveWidth
from
"@/hooks/useResponsiveWidth"
;
import
i18n
from
"@/i18n"
;
import
{
Routes
}
from
"@/router"
;
import
{
useMemoList
,
useMemoStore
}
from
"@/store/v1"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
const
Timeline
=
()
=>
{
const
t
=
useTranslate
();
const
{
md
}
=
useResponsiveWidth
();
const
user
=
useCurrentUser
();
const
memoStore
=
useMemoStore
();
const
memoList
=
useMemoList
();
const
[,
setLastVisited
]
=
useLocalStorage
<
string
>
(
"lastVisited"
,
Routes
.
TIMELINE
);
const
filter
=
useFilterWithUrlParams
();
const
[
activityStats
,
setActivityStats
]
=
useState
<
Record
<
string
,
number
>>
({});
const
[
selectedDateString
,
setSelectedDateString
]
=
useState
<
string
>
(
new
Date
().
toDateString
());
const
[
isRequesting
,
setIsRequesting
]
=
useState
(
true
);
const
[
nextPageToken
,
setNextPageToken
]
=
useState
<
string
>
(
""
);
const
sortedMemos
=
memoList
.
value
.
sort
((
a
,
b
)
=>
getTimeStampByDate
(
a
.
displayTime
)
-
getTimeStampByDate
(
b
.
displayTime
));
const
monthString
=
dayjs
(
selectedDateString
).
format
(
"YYYY-MM"
);
useEffect
(()
=>
{
setLastVisited
(
Routes
.
TIMELINE
);
},
[]);
useEffect
(()
=>
{
memoList
.
reset
();
fetchMemos
(
""
);
},
[
selectedDateString
,
filter
.
text
,
filter
.
tag
,
filter
.
memoPropertyFilter
]);
useEffect
(()
=>
{
(
async
()
=>
{
const
filters
=
[
`row_status == "NORMAL"`
];
const
{
stats
}
=
await
memoServiceClient
.
getUserMemosStats
({
name
:
user
.
name
,
timezone
:
Intl
.
DateTimeFormat
().
resolvedOptions
().
timeZone
,
filter
:
filters
.
join
(
" && "
),
});
setActivityStats
(
Object
.
fromEntries
(
Object
.
entries
(
stats
).
filter
(([
date
])
=>
{
return
dayjs
(
date
).
format
(
"YYYY-MM"
)
===
monthString
;
}),
),
);
})();
},
[
sortedMemos
.
length
]);
const
fetchMemos
=
async
(
nextPageToken
:
string
)
=>
{
setIsRequesting
(
true
);
const
filters
=
[
`creator == "
${
user
.
name
}
"`
,
`row_status == "NORMAL"`
];
const
contentSearch
:
string
[]
=
[];
if
(
filter
.
text
)
{
contentSearch
.
push
(
JSON
.
stringify
(
filter
.
text
));
}
if
(
contentSearch
.
length
>
0
)
{
filters
.
push
(
`content_search == [
${
contentSearch
.
join
(
", "
)}
]`
);
}
if
(
filter
.
tag
)
{
filters
.
push
(
`tag == "
${
filter
.
tag
}
"`
);
}
if
(
filter
.
memoPropertyFilter
)
{
if
(
filter
.
memoPropertyFilter
.
hasLink
)
{
filters
.
push
(
`has_link == true`
);
}
if
(
filter
.
memoPropertyFilter
.
hasTaskList
)
{
filters
.
push
(
`has_task_list == true`
);
}
if
(
filter
.
memoPropertyFilter
.
hasCode
)
{
filters
.
push
(
`has_code == true`
);
}
}
if
(
selectedDateString
)
{
const
selectedDateStamp
=
getTimeStampByDate
(
selectedDateString
);
filters
.
push
(
...[
`display_time_after ==
${
selectedDateStamp
/
1000
}
`
,
`display_time_before ==
${(
selectedDateStamp
+
DAILY_TIMESTAMP
)
/
1000
}
`
],
);
}
const
response
=
await
memoStore
.
fetchMemos
({
pageSize
:
DEFAULT_LIST_MEMOS_PAGE_SIZE
,
filter
:
filters
.
join
(
" && "
),
pageToken
:
nextPageToken
,
});
setIsRequesting
(
false
);
setNextPageToken
(
response
.
nextPageToken
);
};
const
handleSelectedDataChange
=
(
date
:
string
)
=>
{
if
(
dayjs
(
date
).
isValid
())
{
setSelectedDateString
(
new
Date
(
date
).
toDateString
());
}
};
const
handleNewMemo
=
()
=>
{
showMemoEditorDialog
({});
};
return
(
<
section
className=
"@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8"
>
{
!
md
&&
(
<
MobileHeader
>
<
TimelineSidebarDrawer
/>
</
MobileHeader
>
)
}
<
div
className=
{
clsx
(
"w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4"
)
}
>
<
div
className=
{
clsx
(
md
?
"w-[calc(100%-15rem)]"
:
"w-full"
)
}
>
<
div
className=
"w-full shadow flex flex-col justify-start items-start px-4 py-3 rounded-xl bg-white dark:bg-zinc-800 text-black dark:text-gray-300"
>
<
div
className=
"relative w-full flex flex-row justify-between items-center"
>
<
div
>
<
div
className=
"py-1 flex flex-row justify-start items-center select-none opacity-80"
onClick=
{
()
=>
setSelectedDateString
(
new
Date
().
toDateString
())
}
>
<
Icon
.
GanttChartSquare
className=
"w-6 h-auto mr-1 opacity-80"
/>
<
span
className=
"text-lg"
>
{
t
(
"timeline.title"
)
}
</
span
>
</
div
>
</
div
>
<
div
className=
"flex justify-end items-center gap-2"
>
<
IconButton
variant=
"outlined"
size=
"sm"
onClick=
{
()
=>
handleNewMemo
()
}
>
<
Icon
.
Plus
className=
"w-5 h-auto"
/>
</
IconButton
>
</
div
>
</
div
>
<
div
className=
"w-full h-auto flex flex-col justify-start items-start"
>
<
div
className=
"flex flex-col justify-start items-start w-full mt-2"
>
<
div
className=
"w-full flex shrink-0 flex-row justify-between pl-1 mt-1 mb-3"
>
<
div
className=
"w-auto flex flex-col"
>
<
div
className=
"relative font-medium text-3xl sm:text-4xl"
>
{
new
Date
(
selectedDateString
).
toLocaleDateString
(
i18n
.
language
,
{
month
:
"short"
,
day
:
"numeric"
})
}
<
input
className=
"inset-0 absolute z-1 opacity-0"
type=
"date"
max=
{
dayjs
().
format
(
"YYYY-MM-DD"
)
}
value=
{
dayjs
(
selectedDateString
).
format
(
"YYYY-MM-DD"
)
}
onFocus=
{
(
e
:
any
)
=>
e
.
target
.
showPicker
()
}
onChange=
{
(
e
)
=>
handleSelectedDataChange
(
e
.
target
.
value
)
}
/>
</
div
>
<
span
className=
"opacity-60 text-lg"
>
{
dayjs
(
monthString
).
year
()
}
</
span
>
</
div
>
<
ActivityCalendar
month=
{
monthString
}
selectedDate=
{
selectedDateString
}
data=
{
activityStats
}
onClick=
{
(
date
)
=>
setSelectedDateString
(
date
)
}
/>
</
div
>
<
div
className=
{
clsx
(
"w-full flex flex-col justify-start items-start"
)
}
>
{
sortedMemos
.
map
((
memo
)
=>
(
<
MemoView
key=
{
`${memo.name}-${memo.displayTime}`
}
className=
"!border w-full !border-gray-100 dark:!border-zinc-700"
memo=
{
memo
}
displayTimeFormat=
"time"
compact
/>
))
}
</
div
>
</
div
>
{
isRequesting
?
(
<
div
className=
"flex flex-row justify-center items-center w-full my-4 text-gray-400"
>
<
Icon
.
Loader
className=
"w-4 h-auto animate-spin mr-1"
/>
<
p
className=
"text-sm italic"
>
{
t
(
"memo.fetching-data"
)
}
</
p
>
</
div
>
)
:
!
nextPageToken
?
(
sortedMemos
.
length
===
0
&&
(
<
div
className=
"w-full mt-12 mb-8 flex flex-col justify-center items-center italic"
>
<
Empty
/>
<
p
className=
"mt-2 text-gray-600 dark:text-gray-400"
>
{
t
(
"message.no-data"
)
}
</
p
>
</
div
>
)
)
:
(
<
div
className=
"w-full flex flex-row justify-center items-center my-4"
>
<
Button
variant=
"plain"
endDecorator=
{
<
Icon
.
ArrowDown
className=
"w-5 h-auto"
/>
}
onClick=
{
()
=>
fetchMemos
(
nextPageToken
)
}
>
{
t
(
"memo.fetch-more"
)
}
</
Button
>
</
div
>
)
}
</
div
>
</
div
>
</
div
>
{
md
&&
(
<
div
className=
"sticky top-0 left-0 shrink-0 -mt-6 w-56 h-full"
>
<
TimelineSidebar
className=
"py-6"
/>
</
div
>
)
}
</
div
>
</
section
>
);
};
export
default
Timeline
;
web/src/router/index.tsx
View file @
10c9bb08
...
@@ -15,13 +15,10 @@ import Resources from "@/pages/Resources";
...
@@ -15,13 +15,10 @@ import Resources from "@/pages/Resources";
import
Setting
from
"@/pages/Setting"
;
import
Setting
from
"@/pages/Setting"
;
import
SignIn
from
"@/pages/SignIn"
;
import
SignIn
from
"@/pages/SignIn"
;
import
SignUp
from
"@/pages/SignUp"
;
import
SignUp
from
"@/pages/SignUp"
;
import
Timeline
from
"@/pages/Timeline"
;
import
UserProfile
from
"@/pages/UserProfile"
;
import
UserProfile
from
"@/pages/UserProfile"
;
export
enum
Routes
{
export
enum
Routes
{
ROOT
=
"/"
,
ROOT
=
"/"
,
HOME
=
"/home"
,
TIMELINE
=
"/timeline"
,
RESOURCES
=
"/resources"
,
RESOURCES
=
"/resources"
,
INBOX
=
"/inbox"
,
INBOX
=
"/inbox"
,
ARCHIVED
=
"/archived"
,
ARCHIVED
=
"/archived"
,
...
@@ -59,13 +56,9 @@ const router = createBrowserRouter([
...
@@ -59,13 +56,9 @@ const router = createBrowserRouter([
element
:
<
RootLayout
/>,
element
:
<
RootLayout
/>,
children
:
[
children
:
[
{
{
path
:
Routes
.
HOME
,
path
:
""
,
element
:
<
Home
/>,
element
:
<
Home
/>,
},
},
{
path
:
Routes
.
TIMELINE
,
element
:
<
Timeline
/>,
},
{
{
path
:
Routes
.
RESOURCES
,
path
:
Routes
.
RESOURCES
,
element
:
<
Resources
/>,
element
:
<
Resources
/>,
...
...
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