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' ;
32import Checkmark from '@spectrum-icons/workflow/Checkmark' ;
43import Close from '@spectrum-icons/workflow/Close' ;
54import FileAdd from '@spectrum-icons/workflow/FileAdd' ;
65import Hand from '@spectrum-icons/workflow/Hand' ;
76import Launch from '@spectrum-icons/workflow/Launch' ;
7+ import UploadToCloud from '@spectrum-icons/workflow/UploadToCloud' ;
88import React , { useState } from 'react' ;
9+ import { Controller , FormProvider , useForm } from 'react-hook-form' ;
910import { toastRequest } from '../utils/api' ;
1011import { ScriptRoots , ScriptType } from '../utils/api.types' ;
1112import { Strings } from '../utils/strings' ;
@@ -14,43 +15,53 @@ interface CodeSaveButtonProps extends React.ComponentProps<typeof Button> {
1415 code : string ;
1516}
1617
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+
1732function detectScriptType ( code : string ) : ScriptType {
1833 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 * \{ / ;
1934 return automaticPattern . test ( code ) ? ScriptType . AUTOMATIC : ScriptType . MANUAL ;
2035}
2136
2237const CodeSaveButton : React . FC < CodeSaveButtonProps > = ( { code, ...buttonProps } ) => {
2338 const [ dialogOpen , setDialogOpen ] = useState ( false ) ;
24- const [ scriptType , setScriptType ] = useState < ScriptType > ( detectScriptType ( code ) ) ;
25- const [ scriptName , setScriptName ] = useState ( '' ) ;
2639 const [ saving , setSaving ] = useState ( false ) ;
2740
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' ;
3047
31- const handleOpen = ( ) => setDialogOpen ( true ) ;
48+ const handleOpen = ( ) => {
49+ reset ( getFormDefaults ( code ) ) ;
50+ setDialogOpen ( true ) ;
51+ } ;
3252
3353 const handleClose = ( ) => {
3454 setDialogOpen ( false ) ;
35- setScriptName ( '' ) ;
3655 setSaving ( false ) ;
56+ reset ( getFormDefaults ( code ) ) ;
3757 } ;
3858
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 ) => {
4960 setSaving ( true ) ;
5061 try {
5162 await toastRequest ( {
5263 operation : 'Save script' ,
53- url : '/apps/acm/api/script.json' ,
64+ url : '/apps/acm/api/script.json?action=save ' ,
5465 method : 'POST' ,
5566 data : {
5667 code : {
@@ -59,17 +70,19 @@ const CodeSaveButton: React.FC<CodeSaveButtonProps> = ({ code, ...buttonProps })
5970 } ,
6071 } ,
6172 } ) ;
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+ }
6280 handleClose ( ) ;
6381 } finally {
6482 setSaving ( false ) ;
6583 }
6684 } ;
6785
68- const handleFormSubmit = async ( e : React . FormEvent ) => {
69- e . preventDefault ( ) ;
70- await handleSave ( ) ;
71- } ;
72-
7386 return (
7487 < >
7588 < Button onPress = { handleOpen } { ...buttonProps } >
@@ -79,43 +92,70 @@ const CodeSaveButton: React.FC<CodeSaveButtonProps> = ({ code, ...buttonProps })
7992 < DialogContainer onDismiss = { handleClose } >
8093 { dialogOpen && (
8194 < 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 >
119159 </ Dialog >
120160 ) }
121161 </ DialogContainer >
0 commit comments