2
2
* cloud functions list sidebar
3
3
***************************/
4
4
5
- import { useEffect , useState } from "react" ;
5
+ import React , { useEffect , useState } from "react" ;
6
6
import { useNavigate , useParams } from "react-router-dom" ;
7
7
import { AddIcon , DeleteIcon , EditIcon , Search2Icon } from "@chakra-ui/icons" ;
8
8
import { Badge , HStack , Input , InputGroup , InputLeftElement , useColorMode } from "@chakra-ui/react" ;
@@ -24,7 +24,6 @@ import { useDeleteFunctionMutation, useFunctionListQuery } from "../../service";
24
24
import useFunctionStore from "../../store" ;
25
25
import TriggerModal from "../TriggerModal" ;
26
26
27
- // import PromptModal from "./CreateModal/PromptModal";
28
27
import CreateModal from "./CreateModal" ;
29
28
30
29
import { TFunction } from "@/apis/typing" ;
@@ -36,18 +35,27 @@ type TagItem = {
36
35
selected : boolean ;
37
36
} ;
38
37
38
+ type TreeNode = {
39
+ _id : string ;
40
+ name : string ;
41
+ level ?: number ;
42
+ isExpanded ?: boolean ;
43
+ children : ( TreeNode | TFunction ) [ ] ;
44
+ } ;
45
+
39
46
export default function FunctionList ( ) {
40
47
const { setCurrentFunction, currentFunction, setAllFunctionList, allFunctionList } =
41
48
useFunctionStore ( ( store ) => store ) ;
42
49
43
50
const functionCache = useFunctionCache ( ) ;
51
+ const [ root , setRoot ] = useState < TreeNode > ( { _id : "" , name : "" , children : [ ] } ) ;
44
52
45
53
const [ keywords , setKeywords ] = useState ( "" ) ;
46
54
47
55
const { colorMode } = useColorMode ( ) ;
48
56
const darkMode = colorMode === COLOR_MODE . dark ;
49
57
50
- const { currentApp } = useGlobalStore ( ) ;
58
+ const { currentApp, showSuccess } = useGlobalStore ( ) ;
51
59
52
60
const { id : functionName } = useParams ( ) ;
53
61
const navigate = useNavigate ( ) ;
@@ -63,9 +71,38 @@ export default function FunctionList() {
63
71
return flag ;
64
72
} ) ;
65
73
74
+ function generateRoot ( data : TFunction [ ] ) {
75
+ const root = { _id : "" , name : "" , level : 0 , isExpanded : true , children : [ ] } ;
76
+ data . forEach ( ( item ) => {
77
+ const nameParts = item . name . split ( "/" ) ;
78
+ let currentNode : TreeNode = root ;
79
+ nameParts . forEach ( ( part , index ) => {
80
+ if ( index === nameParts . length - 1 ) {
81
+ currentNode . children . push ( item ) ;
82
+ return ;
83
+ }
84
+ let existingNode = currentNode . children . find ( ( node ) => node . name === part ) ;
85
+ if ( ! existingNode ) {
86
+ const newNode = {
87
+ _id : item . _id ,
88
+ name : part ,
89
+ level : index ,
90
+ isExpanded : false ,
91
+ children : [ ] ,
92
+ } ;
93
+ currentNode . children . push ( newNode ) ;
94
+ existingNode = newNode ;
95
+ }
96
+ currentNode = existingNode as TreeNode ;
97
+ } ) ;
98
+ } ) ;
99
+ return root ;
100
+ }
101
+
66
102
useFunctionListQuery ( {
67
103
onSuccess : ( data ) => {
68
104
setAllFunctionList ( data . data ) ;
105
+ setRoot ( generateRoot ( data . data ) ) ;
69
106
const tags = data . data . reduce ( ( pre : any , item : any ) => {
70
107
return pre . concat ( item . tags ) ;
71
108
} , [ ] ) ;
@@ -125,6 +162,92 @@ export default function FunctionList() {
125
162
) : null ;
126
163
} ;
127
164
165
+ function renderSectionItems ( items : TreeNode [ ] , isFuncList = false ) {
166
+ items . sort ( ( a : TreeNode , b : TreeNode ) => {
167
+ const isFolderA = a . children && a . children . length > 0 ;
168
+ const isFolderB = b . children && b . children . length > 0 ;
169
+ if ( isFolderA && ! isFolderB ) {
170
+ return - 1 ;
171
+ } else if ( ! isFolderA && isFolderB ) {
172
+ return 1 ;
173
+ }
174
+ return 0 ;
175
+ } ) ;
176
+
177
+ return items . map ( ( item , index ) => {
178
+ let fileType = FileType . ts ;
179
+ if ( item . children ?. length ) {
180
+ fileType = FileType . folder ;
181
+ }
182
+ const level = item . level || item ?. name . split ( "/" ) . length - 1 ;
183
+
184
+ return (
185
+ < React . Fragment key = { index } >
186
+ < SectionList . Item
187
+ isActive = { item ?. name === currentFunction ?. name }
188
+ key = { index as any }
189
+ className = "group"
190
+ onClick = { ( ) => {
191
+ if ( ! item ?. children ?. length ) {
192
+ setCurrentFunction ( item ) ;
193
+ navigate ( `/app/${ currentApp ?. appid } /${ Pages . function } /${ item ?. name } ` ) ;
194
+ } else {
195
+ item . isExpanded = ! item . isExpanded ;
196
+ setRoot ( { ...root } ) ;
197
+ }
198
+ } }
199
+ >
200
+ < div
201
+ className = { clsx (
202
+ "overflow-hidden text-ellipsis whitespace-nowrap" ,
203
+ ! isFuncList ? `ml-${ 2 * level } ` : "" ,
204
+ ) }
205
+ >
206
+ < FileTypeIcon type = { fileType } width = "12px" />
207
+ < span className = "ml-2 text-base" >
208
+ { item . children ?. length || isFuncList ? item ?. name : item ?. name . split ( "/" ) [ level ] }
209
+ </ span >
210
+ </ div >
211
+ { ! item . children ?. length && (
212
+ < HStack spacing = { 1 } >
213
+ { functionCache . getCache ( item ?. _id , ( item as any ) ?. source ?. code ) !==
214
+ ( item as any ) ?. source ?. code && (
215
+ < span className = "mt-[1px] inline-block h-1 w-1 flex-none rounded-full bg-warn-700" > </ span >
216
+ ) }
217
+ < MoreButton isHidden = { item . name !== currentFunction ?. name } label = { t ( "Operation" ) } >
218
+ < >
219
+ < CreateModal functionItem = { item } tagList = { tagsList } >
220
+ < IconText icon = { < EditIcon /> } text = { t ( "Edit" ) } />
221
+ </ CreateModal >
222
+ < ConfirmButton
223
+ onSuccessAction = { async ( ) => {
224
+ const res = await deleteFunctionMutation . mutateAsync ( item ) ;
225
+ if ( ! res . error ) {
226
+ showSuccess ( t ( "DeleteSuccess" ) ) ;
227
+ }
228
+ } }
229
+ headerText = { String ( t ( "Delete" ) ) }
230
+ bodyText = { String ( t ( "FunctionPanel.DeleteConfirm" ) ) }
231
+ >
232
+ < IconText
233
+ icon = { < DeleteIcon /> }
234
+ text = { t ( "Delete" ) }
235
+ className = "hover:!text-error-600"
236
+ />
237
+ </ ConfirmButton >
238
+ </ >
239
+ </ MoreButton >
240
+ </ HStack >
241
+ ) }
242
+ </ SectionList . Item >
243
+ { item . isExpanded &&
244
+ item ?. children ?. length &&
245
+ renderSectionItems ( item . children as TreeNode [ ] ) }
246
+ </ React . Fragment >
247
+ ) ;
248
+ } ) ;
249
+ }
250
+
128
251
return (
129
252
< Panel className = "min-w-[215px] flex-grow overflow-hidden" >
130
253
< Panel . Header
@@ -172,56 +295,12 @@ export default function FunctionList() {
172
295
{ renderSelectedTags ( ) }
173
296
174
297
< div className = "flex-grow" style = { { overflowY : "auto" } } >
175
- { allFunctionList ?. length ? (
298
+ { keywords || currentTag ? (
176
299
< SectionList >
177
- { filterFunctions . map ( ( func : any ) => {
178
- return (
179
- < SectionList . Item
180
- isActive = { func ?. name === currentFunction ?. name }
181
- key = { func ?. name || "" }
182
- className = "group"
183
- onClick = { ( ) => {
184
- setCurrentFunction ( func ) ;
185
- navigate ( `/app/${ currentApp ?. appid } /${ Pages . function } /${ func ?. name } ` ) ;
186
- } }
187
- >
188
- < div className = "overflow-hidden text-ellipsis whitespace-nowrap" >
189
- < FileTypeIcon type = { FileType . ts } />
190
- < span className = "ml-2 text-base" > { func ?. name } </ span >
191
- </ div >
192
- < HStack spacing = { 1 } >
193
- { functionCache . getCache ( func ?. _id , func ?. source ?. code ) !==
194
- func ?. source ?. code && (
195
- < span className = "mt-[1px] inline-block h-1 w-1 flex-none rounded-full bg-warn-700" > </ span >
196
- ) }
197
- < MoreButton
198
- isHidden = { func . name !== currentFunction ?. name }
199
- label = { t ( "Operation" ) }
200
- >
201
- < >
202
- < CreateModal functionItem = { func } tagList = { tagsList } >
203
- < IconText icon = { < EditIcon /> } text = { t ( "Edit" ) } />
204
- </ CreateModal >
205
- < ConfirmButton
206
- onSuccessAction = { async ( ) => {
207
- await deleteFunctionMutation . mutateAsync ( func ) ;
208
- } }
209
- headerText = { String ( t ( "Delete" ) ) }
210
- bodyText = { String ( t ( "FunctionPanel.DeleteConfirm" ) ) }
211
- >
212
- < IconText
213
- icon = { < DeleteIcon /> }
214
- text = { t ( "Delete" ) }
215
- className = "hover:!text-error-600"
216
- />
217
- </ ConfirmButton >
218
- </ >
219
- </ MoreButton >
220
- </ HStack >
221
- </ SectionList . Item >
222
- ) ;
223
- } ) }
300
+ { renderSectionItems ( filterFunctions as unknown as TreeNode [ ] , true ) }
224
301
</ SectionList >
302
+ ) : root . children ?. length ? (
303
+ < SectionList > { renderSectionItems ( root . children as TreeNode [ ] ) } </ SectionList >
225
304
) : (
226
305
< EmptyBox hideIcon >
227
306
< p > { t ( "FunctionPanel.EmptyFunctionTip" ) } </ p >
0 commit comments