1
- import { Button , ButtonGroup , Content , Dialog , DialogContainer , Divider , Form , Heading , Radio , RadioGroup , Text , TextField } from '@adobe/react-spectrum' ;
2
- import { ToastQueue } from '@react-spectrum/toast' ;
1
+ import { Button , ButtonGroup , Checkbox , Content , Dialog , DialogContainer , Divider , Form , Heading , InlineAlert , Radio , RadioGroup , Text , TextField } from '@adobe/react-spectrum' ;
3
2
import Checkmark from '@spectrum-icons/workflow/Checkmark' ;
4
3
import Close from '@spectrum-icons/workflow/Close' ;
5
4
import FileAdd from '@spectrum-icons/workflow/FileAdd' ;
6
5
import Hand from '@spectrum-icons/workflow/Hand' ;
7
6
import Launch from '@spectrum-icons/workflow/Launch' ;
7
+ import UploadToCloud from '@spectrum-icons/workflow/UploadToCloud' ;
8
8
import React , { useState } from 'react' ;
9
+ import { Controller , FormProvider , useForm } from 'react-hook-form' ;
9
10
import { toastRequest } from '../utils/api' ;
10
11
import { ScriptRoots , ScriptType } from '../utils/api.types' ;
11
12
import { Strings } from '../utils/strings' ;
@@ -14,43 +15,53 @@ interface CodeSaveButtonProps extends React.ComponentProps<typeof Button> {
14
15
code : string ;
15
16
}
16
17
18
+ interface CodeFormValues {
19
+ scriptName : string ;
20
+ scriptType : ScriptType ;
21
+ sync : boolean ;
22
+ }
23
+
24
+ function getFormDefaults ( code : string ) : CodeFormValues {
25
+ return {
26
+ scriptName : '' ,
27
+ scriptType : detectScriptType ( code ) ,
28
+ sync : true ,
29
+ } ;
30
+ }
31
+
17
32
function detectScriptType ( code : string ) : ScriptType {
18
33
const automaticPattern = / ( d e f | S c h e d u l e ) \s + s c h e d u l e R u n \s * ( \( \s * \) ) ? \s * \{ / ;
19
34
return automaticPattern . test ( code ) ? ScriptType . AUTOMATIC : ScriptType . MANUAL ;
20
35
}
21
36
22
37
const CodeSaveButton : React . FC < CodeSaveButtonProps > = ( { code, ...buttonProps } ) => {
23
38
const [ dialogOpen , setDialogOpen ] = useState ( false ) ;
24
- const [ scriptType , setScriptType ] = useState < ScriptType > ( detectScriptType ( code ) ) ;
25
- const [ scriptName , setScriptName ] = useState ( '' ) ;
26
39
const [ saving , setSaving ] = useState ( false ) ;
27
40
28
- const scriptNameValid = Strings . checkFilePath ( scriptName ) ;
29
- const scriptId = ScriptRoots [ scriptType ] + '/' + ( Strings . removeEnd ( scriptName . trim ( ) , '.groovy' ) || '{name}' ) + '.groovy' ;
41
+ const methods = useForm < CodeFormValues > ( { defaultValues : getFormDefaults ( code ) } ) ;
42
+ const { control, handleSubmit, formState, reset, watch } = methods ;
43
+ const scriptName = watch ( 'scriptName' ) ;
44
+ const scriptType = watch ( 'scriptType' ) ;
45
+ const sync = watch ( 'sync' ) ;
46
+ const scriptId = ScriptRoots [ scriptType ] + '/' + ( Strings . removeEnd ( scriptName ?. trim ( ) , '.groovy' ) || '{name}' ) + '.groovy' ;
30
47
31
- const handleOpen = ( ) => setDialogOpen ( true ) ;
48
+ const handleOpen = ( ) => {
49
+ reset ( getFormDefaults ( code ) ) ;
50
+ setDialogOpen ( true ) ;
51
+ } ;
32
52
33
53
const handleClose = ( ) => {
34
54
setDialogOpen ( false ) ;
35
- setScriptName ( '' ) ;
36
55
setSaving ( false ) ;
56
+ reset ( getFormDefaults ( code ) ) ;
37
57
} ;
38
58
39
- const handleSave = async ( ) => {
40
- if ( ! scriptName . trim ( ) ) {
41
- ToastQueue . negative ( 'Script name is required!' ) ;
42
- return ;
43
- }
44
- if ( ! scriptNameValid ) {
45
- ToastQueue . negative ( 'Script name is invalid!' ) ;
46
- return ;
47
- }
48
-
59
+ const onSubmit = async ( data : CodeFormValues ) => {
49
60
setSaving ( true ) ;
50
61
try {
51
62
await toastRequest ( {
52
63
operation : 'Save script' ,
53
- url : '/apps/acm/api/script.json' ,
64
+ url : '/apps/acm/api/script.json?action=save ' ,
54
65
method : 'POST' ,
55
66
data : {
56
67
code : {
@@ -59,17 +70,19 @@ const CodeSaveButton: React.FC<CodeSaveButtonProps> = ({ code, ...buttonProps })
59
70
} ,
60
71
} ,
61
72
} ) ;
73
+ if ( scriptType === ScriptType . AUTOMATIC && data . sync ) {
74
+ await toastRequest ( {
75
+ method : 'POST' ,
76
+ url : `/apps/acm/api/script.json?action=sync` ,
77
+ operation : `Synchronize scripts` ,
78
+ } ) ;
79
+ }
62
80
handleClose ( ) ;
63
81
} finally {
64
82
setSaving ( false ) ;
65
83
}
66
84
} ;
67
85
68
- const handleFormSubmit = async ( e : React . FormEvent ) => {
69
- e . preventDefault ( ) ;
70
- await handleSave ( ) ;
71
- } ;
72
-
73
86
return (
74
87
< >
75
88
< Button onPress = { handleOpen } { ...buttonProps } >
@@ -79,43 +92,70 @@ const CodeSaveButton: React.FC<CodeSaveButtonProps> = ({ code, ...buttonProps })
79
92
< DialogContainer onDismiss = { handleClose } >
80
93
{ dialogOpen && (
81
94
< Dialog minWidth = "40vw" >
82
- < Heading > Save Script</ Heading >
83
- < Divider />
84
- < Content >
85
- < Form validationBehavior = "native" onSubmit = { handleFormSubmit } >
86
- < RadioGroup label = "Type" isRequired value = { scriptType } onChange = { ( value ) => setScriptType ( value as ScriptType ) } orientation = "horizontal" >
87
- < Radio value = { ScriptType . MANUAL } >
88
- < Hand size = "XS" />
89
- < Text marginStart = "size-50" > Manual</ Text >
90
- </ Radio >
91
- < Radio value = { ScriptType . AUTOMATIC } >
92
- < Launch size = "XS" />
93
- < Text marginStart = "size-50" > Automatic</ Text >
94
- </ Radio >
95
- </ RadioGroup >
96
- < TextField
97
- label = "Name"
98
- width = "100%"
99
- value = { scriptName }
100
- onChange = { setScriptName }
101
- isRequired
102
- marginTop = "size-200"
103
- validationState = { scriptName && ! scriptNameValid ? 'invalid' : undefined }
104
- errorMessage = { scriptName && ! scriptNameValid ? 'Invalid file path' : undefined }
105
- />
106
- < TextField label = "ID" width = "100%" value = { scriptId } isDisabled marginTop = "size-200" />
107
- </ Form >
108
- </ Content >
109
- < ButtonGroup >
110
- < Button variant = "secondary" onPress = { handleClose } >
111
- < Close size = "XS" />
112
- < Text > Cancel</ Text >
113
- </ Button >
114
- < Button variant = "cta" type = "submit" isPending = { saving } isDisabled = { ! scriptNameValid || saving } >
115
- < Checkmark size = "XS" />
116
- < Text > Save</ Text >
117
- </ Button >
118
- </ ButtonGroup >
95
+ < FormProvider { ...methods } >
96
+ < Heading > Save Script</ Heading >
97
+ < Divider />
98
+ < Content >
99
+ < Form onSubmit = { handleSubmit ( onSubmit ) } >
100
+ < Controller
101
+ name = "scriptType"
102
+ control = { control }
103
+ render = { ( { field } ) => (
104
+ < RadioGroup { ...field } label = "Type" isRequired value = { field . value } onChange = { field . onChange } orientation = "horizontal" >
105
+ < Radio value = { ScriptType . MANUAL } >
106
+ < Hand size = "XS" />
107
+ < Text marginStart = "size-50" > Manual</ Text >
108
+ </ Radio >
109
+ < Radio value = { ScriptType . AUTOMATIC } >
110
+ < Launch size = "XS" />
111
+ < Text marginStart = "size-50" > Automatic</ Text >
112
+ </ Radio >
113
+ </ RadioGroup >
114
+ ) }
115
+ />
116
+ < Controller
117
+ name = "scriptName"
118
+ control = { control }
119
+ rules = { {
120
+ required : 'Value is required' ,
121
+ validate : ( value ) => Strings . checkFilePath ( value ) || 'Value has invalid format' ,
122
+ } }
123
+ render = { ( { field } ) => (
124
+ < TextField { ...field } label = "Name" width = "100%" isRequired marginTop = "size-200" validationState = { formState . errors . scriptName ? 'invalid' : undefined } errorMessage = { formState . errors . scriptName ?. message } />
125
+ ) }
126
+ />
127
+ < TextField label = "ID" width = "100%" value = { scriptId } isDisabled marginTop = "size-200" />
128
+ < Controller
129
+ name = "sync"
130
+ control = { control }
131
+ render = { ( { field : { value, onChange, onBlur, name, ref } } ) => (
132
+ < Checkbox isHidden = { scriptType === ScriptType . MANUAL } isSelected = { value } onChange = { onChange } onBlur = { onBlur } name = { name } ref = { ref } marginTop = "size-200" >
133
+ < UploadToCloud size = "XS" />
134
+ < Text marginStart = "size-50" > Synchronize</ Text >
135
+ </ Checkbox >
136
+ ) }
137
+ />
138
+ </ Form >
139
+ { scriptType === ScriptType . AUTOMATIC && (
140
+ < InlineAlert width = "100%" variant = "notice" marginTop = "size-100" UNSAFE_style = { { padding : '8px' } } >
141
+ < Heading > Warning</ Heading >
142
+ < Content UNSAFE_style = { { padding : '6px' , marginTop : '6px' } } >
143
+ < Text > { sync ? 'This action may cause immediate execution on the author and publish instances.' : 'This action may cause immediate execution on the author instance.' } </ Text >
144
+ </ Content >
145
+ </ InlineAlert >
146
+ ) }
147
+ </ Content >
148
+ < ButtonGroup >
149
+ < Button variant = "secondary" onPress = { handleClose } >
150
+ < Close size = "XS" />
151
+ < Text > Cancel</ Text >
152
+ </ Button >
153
+ < Button variant = "cta" isPending = { saving } isDisabled = { saving } onPress = { ( ) => handleSubmit ( onSubmit ) ( ) } type = "button" >
154
+ < Checkmark size = "XS" />
155
+ < Text > Save</ Text >
156
+ </ Button >
157
+ </ ButtonGroup >
158
+ </ FormProvider >
119
159
</ Dialog >
120
160
) }
121
161
</ DialogContainer >
0 commit comments