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
f2cfc528
Commit
f2cfc528
authored
Jul 15, 2024
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: introduce tag view option
parent
260a4f89
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
196 additions
and
37 deletions
+196
-37
TagsSection.tsx
web/src/components/HomeSidebar/TagsSection.tsx
+54
-37
TagTree.tsx
web/src/components/TagTree.tsx
+142
-0
No files found.
web/src/components/HomeSidebar/TagsSection.tsx
View file @
f2cfc528
import
{
Dropdown
,
Menu
,
MenuButton
,
MenuItem
}
from
"@mui/joy"
;
import
{
Dropdown
,
Menu
,
MenuButton
,
MenuItem
,
Switch
}
from
"@mui/joy"
;
import
clsx
from
"clsx"
;
import
toast
from
"react-hot-toast"
;
import
{
useLocation
}
from
"react-router-dom"
;
import
useDebounce
from
"react-use/lib/useDebounce"
;
import
useLocalStorage
from
"react-use/lib/useLocalStorage"
;
import
{
memoServiceClient
}
from
"@/grpcweb"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
{
useFilterStore
}
from
"@/store/module"
;
...
...
@@ -10,6 +11,7 @@ import { useMemoList, useTagStore } from "@/store/v1";
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
Icon
from
"../Icon"
;
import
showRenameTagDialog
from
"../RenameTagDialog"
;
import
TagTree
from
"../TagTree"
;
interface
Props
{
readonly
?:
boolean
;
...
...
@@ -22,6 +24,7 @@ const TagsSection = (props: Props) => {
const
filterStore
=
useFilterStore
();
const
tagStore
=
useTagStore
();
const
memoList
=
useMemoList
();
const
[
treeMode
,
setTreeMode
]
=
useLocalStorage
<
boolean
>
(
"tag-view-as-tree"
,
false
);
const
tagAmounts
=
Object
.
entries
(
tagStore
.
getState
().
tagAmounts
)
.
sort
((
a
,
b
)
=>
a
[
0
].
localeCompare
(
b
[
0
]))
.
sort
((
a
,
b
)
=>
b
[
1
]
-
a
[
1
]);
...
...
@@ -54,48 +57,62 @@ const TagsSection = (props: Props) => {
return
(
<
div
className=
"flex flex-col justify-start items-start w-full mt-3 px-1 h-auto shrink-0 flex-nowrap hide-scrollbar"
>
<
div
className=
"flex flex-row justify-
start
items-center w-full gap-1 mb-1 text-sm leading-6 text-gray-400 select-none"
>
<
div
className=
"flex flex-row justify-
between
items-center w-full gap-1 mb-1 text-sm leading-6 text-gray-400 select-none"
>
<
span
>
{
t
(
"common.tags"
)
}
</
span
>
{
tagAmounts
.
length
>
0
&&
<
span
className=
"shrink-0"
>
(
{
tagAmounts
.
length
}
)
</
span
>
}
<
Dropdown
>
<
MenuButton
slots=
{
{
root
:
"div"
}
}
>
<
Icon
.
MoreHorizontal
className=
"w-4 h-auto shrink-0 opacity-60"
/>
</
MenuButton
>
<
Menu
size=
"sm"
placement=
"bottom-end"
>
<
MenuItem
>
<
span
className=
"text-sm shrink-0 mr-2"
>
Tree mode
</
span
>
<
Switch
size=
"sm"
checked=
{
treeMode
}
onChange=
{
(
event
)
=>
setTreeMode
(
event
.
target
.
checked
)
}
/>
</
MenuItem
>
</
Menu
>
</
Dropdown
>
</
div
>
{
tagAmounts
.
length
>
0
?
(
<
div
className=
"w-full flex flex-row justify-start items-center relative flex-wrap gap-x-2 gap-y-1"
>
{
tagAmounts
.
map
(([
tag
,
amount
])
=>
(
<
div
key=
{
tag
}
className=
"shrink-0 w-auto max-w-full text-sm rounded-md leading-6 flex flex-row justify-start items-center select-none hover:opacity-80 text-gray-600 dark:text-gray-400 dark:border-zinc-800"
>
<
Dropdown
>
<
MenuButton
slots=
{
{
root
:
"div"
}
}
>
<
div
className=
"shrink-0 group"
>
<
Icon
.
Hash
className=
"group-hover:hidden w-4 h-auto shrink-0 opacity-40"
/>
<
Icon
.
MoreVertical
className=
"hidden group-hover:block w-4 h-auto shrink-0 opacity-60"
/>
</
div
>
</
MenuButton
>
<
Menu
size=
"sm"
placement=
"bottom-start"
>
<
MenuItem
onClick=
{
()
=>
showRenameTagDialog
({
tag
:
tag
})
}
>
<
Icon
.
Edit3
className=
"w-4 h-auto"
/>
{
t
(
"common.rename"
)
}
</
MenuItem
>
<
MenuItem
color=
"danger"
onClick=
{
()
=>
handleDeleteTag
(
tag
)
}
>
<
Icon
.
Trash
className=
"w-4 h-auto"
/>
{
t
(
"common.delete"
)
}
</
MenuItem
>
</
Menu
>
</
Dropdown
>
treeMode
?
(
<
TagTree
tags=
{
tagAmounts
.
map
((
t
)
=>
t
[
0
])
}
/>
)
:
(
<
div
className=
"w-full flex flex-row justify-start items-center relative flex-wrap gap-x-2 gap-y-1"
>
{
tagAmounts
.
map
(([
tag
,
amount
])
=>
(
<
div
className=
{
clsx
(
"inline-flex flex-nowrap ml-0.5 gap-0.5 cursor-pointer max-w-[calc(100%-16px)]"
,
filterStore
.
state
.
tag
===
tag
&&
"text-blue-600 dark:text-blue-400"
,
)
}
onClick=
{
()
=>
handleTagClick
(
tag
)
}
key=
{
tag
}
className=
"shrink-0 w-auto max-w-full text-sm rounded-md leading-6 flex flex-row justify-start items-center select-none hover:opacity-80 text-gray-600 dark:text-gray-400 dark:border-zinc-800"
>
<
span
className=
"truncate dark:opacity-80"
>
{
tag
}
</
span
>
{
amount
>
1
&&
<
span
className=
"opacity-60 shrink-0"
>
(
{
amount
}
)
</
span
>
}
<
Dropdown
>
<
MenuButton
slots=
{
{
root
:
"div"
}
}
>
<
div
className=
"shrink-0 group"
>
<
Icon
.
Hash
className=
"group-hover:hidden w-4 h-auto shrink-0 opacity-40"
/>
<
Icon
.
MoreVertical
className=
"hidden group-hover:block w-4 h-auto shrink-0 opacity-60"
/>
</
div
>
</
MenuButton
>
<
Menu
size=
"sm"
placement=
"bottom-start"
>
<
MenuItem
onClick=
{
()
=>
showRenameTagDialog
({
tag
:
tag
})
}
>
<
Icon
.
Edit3
className=
"w-4 h-auto"
/>
{
t
(
"common.rename"
)
}
</
MenuItem
>
<
MenuItem
color=
"danger"
onClick=
{
()
=>
handleDeleteTag
(
tag
)
}
>
<
Icon
.
Trash
className=
"w-4 h-auto"
/>
{
t
(
"common.delete"
)
}
</
MenuItem
>
</
Menu
>
</
Dropdown
>
<
div
className=
{
clsx
(
"inline-flex flex-nowrap ml-0.5 gap-0.5 cursor-pointer max-w-[calc(100%-16px)]"
,
filterStore
.
state
.
tag
===
tag
&&
"text-blue-600 dark:text-blue-400"
,
)
}
onClick=
{
()
=>
handleTagClick
(
tag
)
}
>
<
span
className=
"truncate dark:opacity-80"
>
{
tag
}
</
span
>
{
amount
>
1
&&
<
span
className=
"opacity-60 shrink-0"
>
(
{
amount
}
)
</
span
>
}
</
div
>
</
div
>
</
div
>
))
}
</
div
>
))
}
</
div
>
)
)
:
(
!
props
.
readonly
&&
(
<
div
className=
"p-2 border border-dashed dark:border-zinc-800 rounded-md flex flex-row justify-start items-start gap-1 text-gray-400 dark:text-gray-500"
>
...
...
web/src/components/TagTree.tsx
0 → 100644
View file @
f2cfc528
import
{
useEffect
,
useState
}
from
"react"
;
import
useToggle
from
"react-use/lib/useToggle"
;
import
{
useFilterStore
}
from
"@/store/module"
;
import
Icon
from
"./Icon"
;
interface
Tag
{
key
:
string
;
text
:
string
;
subTags
:
Tag
[];
}
interface
Props
{
tags
:
string
[];
}
const
TagTree
=
({
tags
:
rawTags
}:
Props
)
=>
{
const
filterStore
=
useFilterStore
();
const
filter
=
filterStore
.
state
;
const
[
tags
,
setTags
]
=
useState
<
Tag
[]
>
([]);
useEffect
(()
=>
{
const
sortedTags
=
Array
.
from
(
rawTags
).
sort
();
const
root
:
Tag
=
{
key
:
""
,
text
:
""
,
subTags
:
[],
};
for
(
const
tag
of
sortedTags
)
{
const
subtags
=
tag
.
split
(
"/"
);
let
tempObj
=
root
;
let
tagText
=
""
;
for
(
let
i
=
0
;
i
<
subtags
.
length
;
i
++
)
{
const
key
=
subtags
[
i
];
if
(
i
===
0
)
{
tagText
+=
key
;
}
else
{
tagText
+=
"/"
+
key
;
}
let
obj
=
null
;
for
(
const
t
of
tempObj
.
subTags
)
{
if
(
t
.
text
===
tagText
)
{
obj
=
t
;
break
;
}
}
if
(
!
obj
)
{
obj
=
{
key
,
text
:
tagText
,
subTags
:
[],
};
tempObj
.
subTags
.
push
(
obj
);
}
tempObj
=
obj
;
}
}
setTags
(
root
.
subTags
as
Tag
[]);
},
[
rawTags
]);
return
(
<
div
className=
"flex flex-col justify-start items-start relative w-full h-auto flex-nowrap gap-2 mt-1"
>
{
tags
.
map
((
t
,
idx
)
=>
(
<
TagItemContainer
key=
{
t
.
text
+
"-"
+
idx
}
tag=
{
t
}
tagQuery=
{
filter
.
tag
}
/>
))
}
</
div
>
);
};
interface
TagItemContainerProps
{
tag
:
Tag
;
tagQuery
?:
string
;
}
const
TagItemContainer
:
React
.
FC
<
TagItemContainerProps
>
=
(
props
:
TagItemContainerProps
)
=>
{
const
filterStore
=
useFilterStore
();
const
{
tag
,
tagQuery
}
=
props
;
const
isActive
=
tagQuery
===
tag
.
text
;
const
hasSubTags
=
tag
.
subTags
.
length
>
0
;
const
[
showSubTags
,
toggleSubTags
]
=
useToggle
(
false
);
const
handleTagClick
=
()
=>
{
if
(
isActive
)
{
filterStore
.
setTagFilter
(
undefined
);
}
else
{
filterStore
.
setTagFilter
(
tag
.
text
);
}
};
const
handleToggleBtnClick
=
(
event
:
React
.
MouseEvent
)
=>
{
event
.
stopPropagation
();
toggleSubTags
();
};
return
(
<>
<
div
className=
"relative flex flex-row justify-between items-center w-full leading-6 py-0 mt-px rounded-lg text-sm select-none shrink-0"
>
<
div
className=
{
`flex flex-row justify-start items-center truncate shrink leading-5 mr-1 text-gray-600 dark:text-gray-400 ${
isActive && "!text-blue-600"
}`
}
>
<
div
className=
"shrink-0"
>
<
Icon
.
Hash
className=
"w-4 h-auto shrink-0 mr-1 text-gray-400 dark:text-gray-500"
/>
</
div
>
<
span
className=
"truncate cursor-pointer hover:opacity-80"
onClick=
{
handleTagClick
}
>
{
tag
.
key
}
</
span
>
</
div
>
<
div
className=
"flex flex-row justify-end items-center"
>
{
hasSubTags
?
(
<
span
className=
{
`flex flex-row justify-center items-center w-6 h-6 shrink-0 transition-all rotate-0 ${showSubTags && "rotate-90"}`
}
onClick=
{
handleToggleBtnClick
}
>
<
Icon
.
ChevronRight
className=
"w-5 h-5 cursor-pointer text-gray-400 dark:text-gray-500"
/>
</
span
>
)
:
null
}
</
div
>
</
div
>
{
hasSubTags
?
(
<
div
className=
{
`w-[calc(100%-0.5rem)] flex flex-col justify-start items-start h-auto ml-2 pl-2 border-l-2 border-l-gray-200 dark:border-l-zinc-800 ${
!showSubTags && "!hidden"
}`
}
>
{
tag
.
subTags
.
map
((
st
,
idx
)
=>
(
<
TagItemContainer
key=
{
st
.
text
+
"-"
+
idx
}
tag=
{
st
}
tagQuery=
{
tagQuery
}
/>
))
}
</
div
>
)
:
null
}
</>
);
};
export
default
TagTree
;
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