1
- import React , { useEffect , useState } from 'react'
1
+ import React , { useEffect , useRef , useState } from 'react'
2
2
import { useHistory , useLocation } from 'react-router-dom'
3
3
import { useSelector , useDispatch } from 'react-redux'
4
4
import cx from 'classnames'
5
5
import { EuiListGroup , EuiLoadingContent } from '@elastic/eui'
6
+ import { isEmpty } from 'lodash'
6
7
import { CodeButtonParams , ExecuteButtonMode } from 'uiSrc/pages/workbench/components/enablement-area/interfaces'
7
- import { EnablementAreaComponent , IEnablementAreaItem } from 'uiSrc/slices/interfaces'
8
+ import { DefaultCustomTutorialsItems , EnablementAreaComponent , IEnablementAreaItem } from 'uiSrc/slices/interfaces'
8
9
import { EnablementAreaProvider , IInternalPage } from 'uiSrc/pages/workbench/contexts/enablementAreaContext'
9
- import { appContextWorkbenchEA , resetWorkbenchEAItem } from 'uiSrc/slices/app/context'
10
+ import { appContextWorkbenchEA , resetWorkbenchEASearch } from 'uiSrc/slices/app/context'
10
11
import { ApiEndpoints } from 'uiSrc/constants'
11
- import { getWBSourcePath } from './utils/getFileInfo'
12
+ import { deleteCustomTutorial , uploadCustomTutorial } from 'uiSrc/slices/workbench/wb-custom-tutorials'
13
+ import { Nullable } from 'uiSrc/utils'
14
+ import {
15
+ getMarkPathDownByManifest ,
16
+ getWBSourcePath
17
+ } from './utils/getFileInfo'
12
18
import {
13
19
CodeButton ,
14
20
Group ,
15
21
InternalLink ,
16
22
LazyCodeButton ,
17
23
LazyInternalPage ,
18
24
PlainText ,
25
+ UploadTutorialForm ,
19
26
} from './components'
20
27
28
+ import {
29
+ EAItemActions ,
30
+ EAManifestFirstKey
31
+ } from './constants'
32
+
21
33
import styles from './styles.module.scss'
22
34
23
35
const padding = parseInt ( styles . paddingHorizontal )
24
36
25
37
export interface Props {
26
38
guides : Record < string , IEnablementAreaItem >
27
39
tutorials : Record < string , IEnablementAreaItem >
40
+ customTutorials : DefaultCustomTutorialsItems
28
41
loading : boolean
29
42
openScript : (
30
43
script : string ,
@@ -39,6 +52,7 @@ const EnablementArea = (props: Props) => {
39
52
const {
40
53
guides = { } ,
41
54
tutorials = { } ,
55
+ customTutorials = { } ,
42
56
openScript,
43
57
loading,
44
58
onOpenInternalPage,
@@ -47,65 +61,170 @@ const EnablementArea = (props: Props) => {
47
61
const { search } = useLocation ( )
48
62
const history = useHistory ( )
49
63
const dispatch = useDispatch ( )
50
- const { itemPath : itemFromContext } = useSelector ( appContextWorkbenchEA )
64
+ const { search : searchEAContext } = useSelector ( appContextWorkbenchEA )
51
65
const [ isInternalPageVisible , setIsInternalPageVisible ] = useState ( false )
66
+ const [ isCreateOpen , setIsCreateOpen ] = useState ( false )
52
67
const [ internalPage , setInternalPage ] = useState < IInternalPage > ( { path : '' } )
68
+ const [ manifest , setManifest ] = useState < Nullable < Record < string , IEnablementAreaItem > > > ( null )
69
+
70
+ const searchRef = useRef < string > ( '' )
53
71
54
72
useEffect ( ( ) => {
73
+ searchRef . current = search
55
74
const pagePath = new URLSearchParams ( search ) . get ( 'item' )
75
+
56
76
if ( pagePath ) {
57
77
setIsInternalPageVisible ( true )
58
78
setInternalPage ( { path : pagePath } )
59
79
return
60
80
}
61
- if ( itemFromContext ) {
62
- handleOpenInternalPage ( { path : itemFromContext } )
63
- return
81
+
82
+ const contextPath = new URLSearchParams ( searchEAContext ) . get ( 'item' )
83
+
84
+ if ( contextPath ) {
85
+ handleOpenInternalPage ( { path : contextPath } )
64
86
}
87
+
65
88
setIsInternalPageVisible ( false )
66
89
} , [ search ] )
67
90
91
+ useEffect ( ( ) => {
92
+ const manifestPath = new URLSearchParams ( search ) . get ( 'path' )
93
+ const contextManifestPath = new URLSearchParams ( searchEAContext ) . get ( 'path' )
94
+ const { manifest, prefixFolder } = getManifestByPath ( manifestPath )
95
+ setManifest ( manifest )
96
+
97
+ if ( isEmpty ( manifest ) && ! contextManifestPath ) {
98
+ return
99
+ }
100
+
101
+ const path = getMarkPathDownByManifest ( manifest as Record < string , IEnablementAreaItem > , manifestPath , prefixFolder )
102
+ if ( path ) {
103
+ setIsInternalPageVisible ( true )
104
+ setInternalPage ( { path, manifestPath } )
105
+
106
+ return
107
+ }
108
+
109
+ if ( contextManifestPath ) {
110
+ handleOpenInternalPage ( { path : '' , manifestPath : contextManifestPath } )
111
+
112
+ return
113
+ }
114
+
115
+ setIsInternalPageVisible ( false )
116
+ } , [ search , customTutorials ] )
117
+
118
+ const getManifestByPath = ( path : Nullable < string > = '' ) => {
119
+ const manifestPath = path ?. replace ( / ^ \/ / , '' ) || ''
120
+ if ( manifestPath . startsWith ( EAManifestFirstKey . CUSTOM_TUTORIALS ) ) {
121
+ return ( { manifest : customTutorials , prefixFolder : ApiEndpoints . CUSTOM_TUTORIALS_PATH } )
122
+ }
123
+ if ( manifestPath . startsWith ( EAManifestFirstKey . TUTORIALS ) ) {
124
+ return ( { manifest : tutorials , prefixFolder : ApiEndpoints . TUTORIALS_PATH } )
125
+ }
126
+ if ( manifestPath . startsWith ( EAManifestFirstKey . GUIDES ) ) {
127
+ return ( { manifest : guides , prefixFolder : ApiEndpoints . GUIDES_PATH } )
128
+ }
129
+
130
+ return { manifest : null }
131
+ }
132
+
133
+ const getManifestItems = ( manifest : Record < string , IEnablementAreaItem > ) =>
134
+ Object . keys ( manifest ) . map ( ( key ) => ( { ...manifest [ key ] , _key : key } ) )
135
+
68
136
const handleOpenInternalPage = ( page : IInternalPage ) => {
69
137
history . push ( {
70
- search : `?item=${ page . path } `
138
+ search : page . manifestPath ? `?path= ${ page . manifestPath } ` : `?item=${ page . path } `
71
139
} )
72
140
onOpenInternalPage ( page )
73
141
}
74
142
75
143
const handleCloseInternalPage = ( ) => {
76
- dispatch ( resetWorkbenchEAItem ( ) )
144
+ dispatch ( resetWorkbenchEASearch ( ) )
77
145
history . push ( {
78
146
// TODO: better to use query-string parser and update only one parameter (instead of replacing all)
79
147
search : ''
80
148
} )
81
149
}
82
150
83
- const renderSwitch = ( item : IEnablementAreaItem , sourcePath : string , level : number ) => {
84
- const { label, type, children, id, args } = item
151
+ const onDeleteCustomTutorial = ( id : string ) => {
152
+ dispatch ( deleteCustomTutorial ( id ) )
153
+ }
154
+
155
+ const submitCreate = ( { file, name } : { file : File , name : string } ) => {
156
+ const formData = new FormData ( )
157
+ formData . append ( 'file' , file )
158
+ formData . append ( 'name' , name )
159
+
160
+ dispatch ( uploadCustomTutorial (
161
+ formData ,
162
+ ( ) => {
163
+ setIsCreateOpen ( false )
164
+ }
165
+ ) )
166
+ }
167
+
168
+ const renderSwitch = (
169
+ item : IEnablementAreaItem ,
170
+ { sourcePath, manifestPath = '' } : { sourcePath : string , manifestPath ?: string } ,
171
+ level : number ,
172
+ ) => {
173
+ const { label, type, children, id, args, _actions : actions , _path : uriPath , _key : key } = item
174
+
85
175
const paddingsStyle = { paddingLeft : `${ padding + level * 8 } px` , paddingRight : `${ padding } px` }
86
- const borderStyle = { border : 'none' , borderTop : '1px solid var(--separatorColor)' }
176
+ const currentSourcePath = sourcePath + ( uriPath ? `${ uriPath } ` : ( args ?. path ?? '' ) )
177
+ const currentManifestPath = ( manifestPath + ( uriPath ? `${ uriPath } ` : `/${ key } ` ) )
178
+
87
179
switch ( type ) {
88
180
case EnablementAreaComponent . Group :
89
181
return (
90
- < Group triggerStyle = { paddingsStyle } testId = { id } label = { label } { ...args } >
91
- { renderTreeView ( Object . values ( children || { } ) || [ ] , sourcePath , level + 1 ) }
182
+ < Group
183
+ triggerStyle = { paddingsStyle }
184
+ id = { id }
185
+ label = { label }
186
+ actions = { actions }
187
+ isShowActions = { currentSourcePath . startsWith ( ApiEndpoints . CUSTOM_TUTORIALS_PATH ) }
188
+ onCreate = { ( ) => setIsCreateOpen ( ( v ) => ! v ) }
189
+ onDelete = { onDeleteCustomTutorial }
190
+ { ...args }
191
+ >
192
+ < >
193
+ { isCreateOpen && actions ?. includes ( EAItemActions . Create ) && (
194
+ < UploadTutorialForm onSubmit = { submitCreate } onCancel = { ( ) => setIsCreateOpen ( false ) } />
195
+ ) }
196
+ { renderTreeView (
197
+ children ? Object . keys ( children ) . map ( ( key ) => ( { ...children [ key ] , _key : key } ) ) : [ ] ,
198
+ { sourcePath : currentSourcePath , manifestPath : currentManifestPath } ,
199
+ level + 1
200
+ ) }
201
+ </ >
92
202
</ Group >
93
203
)
94
204
case EnablementAreaComponent . CodeButton :
95
205
return (
96
206
< >
97
- < div style = { paddingsStyle } className = "divider" > < hr style = { borderStyle } /> </ div >
98
- < div style = { { marginTop : '18px' , ...paddingsStyle } } >
207
+ < div style = { paddingsStyle } className = "divider" >
208
+ < hr style = { { border : 'none' , borderTop : '1px solid var(--separatorColor)' } } />
209
+ </ div >
210
+ < div style = { { marginTop : '10px' , marginBottom : '10px' , ...paddingsStyle } } >
99
211
{ args ?. path
100
- ? < LazyCodeButton label = { label } { ...args } />
212
+ ? < LazyCodeButton label = { label } sourcePath = { sourcePath } { ...args } />
101
213
: < CodeButton onClick = { ( ) => openScript ( args ?. content || '' ) } label = { label } { ...args } /> }
102
214
</ div >
103
215
</ >
104
216
105
217
)
106
218
case EnablementAreaComponent . InternalLink :
107
219
return (
108
- < InternalLink sourcePath = { sourcePath } style = { paddingsStyle } testId = { id || label } label = { label } { ...args } >
220
+ < InternalLink
221
+ manifestPath = { currentManifestPath }
222
+ sourcePath = { currentSourcePath }
223
+ style = { paddingsStyle }
224
+ testId = { id || label }
225
+ label = { label }
226
+ { ...args }
227
+ >
109
228
{ args ?. content || label }
110
229
</ InternalLink >
111
230
)
@@ -114,10 +233,14 @@ const EnablementArea = (props: Props) => {
114
233
}
115
234
}
116
235
117
- const renderTreeView = ( elements : IEnablementAreaItem [ ] , sourcePath : string , level : number = 0 ) => (
236
+ const renderTreeView = (
237
+ elements : IEnablementAreaItem [ ] ,
238
+ paths : { sourcePath : string , manifestPath ?: string } ,
239
+ level : number = 0 ,
240
+ ) => (
118
241
elements ?. map ( ( item ) => (
119
242
< div className = "fluid" key = { item . id } >
120
- { renderSwitch ( item , sourcePath , level ) }
243
+ { renderSwitch ( item , paths , level ) }
121
244
</ div >
122
245
) ) )
123
246
@@ -137,8 +260,9 @@ const EnablementArea = (props: Props) => {
137
260
flush
138
261
className = { cx ( styles . innerContainer ) }
139
262
>
140
- { renderTreeView ( Object . values ( guides ) , ApiEndpoints . GUIDES_PATH ) }
141
- { renderTreeView ( Object . values ( tutorials ) , ApiEndpoints . TUTORIALS_PATH ) }
263
+ { renderTreeView ( getManifestItems ( guides ) , { sourcePath : ApiEndpoints . GUIDES_PATH } ) }
264
+ { renderTreeView ( getManifestItems ( tutorials ) , { sourcePath : ApiEndpoints . TUTORIALS_PATH } ) }
265
+ { renderTreeView ( getManifestItems ( customTutorials ) , { sourcePath : ApiEndpoints . CUSTOM_TUTORIALS_PATH } ) }
142
266
</ EuiListGroup >
143
267
) }
144
268
< div
@@ -152,7 +276,10 @@ const EnablementArea = (props: Props) => {
152
276
onClose = { handleCloseInternalPage }
153
277
title = { internalPage ?. label }
154
278
path = { internalPage ?. path }
279
+ manifest = { manifest }
280
+ manifestPath = { internalPage ?. manifestPath }
155
281
sourcePath = { getWBSourcePath ( internalPage ?. path ) }
282
+ search = { searchRef . current }
156
283
/>
157
284
) }
158
285
</ div >
0 commit comments