@@ -30,13 +30,20 @@ import {
3030} from 'react-admin' ;
3131import { useFormContext , useWatch } from 'react-hook-form' ;
3232import {
33- Alert ,
3433 Button ,
34+ ButtonGroup ,
35+ ClickAwayListener ,
3536 Dialog ,
3637 DialogActions ,
3738 DialogContent ,
39+ Grow ,
40+ MenuItem ,
41+ MenuList ,
42+ Paper ,
43+ Popper ,
44+ Stack ,
3845} from '@mui/material' ;
39- import { Link , useSearchParams } from 'react-router-dom ' ;
46+ import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown ' ;
4047
4148// Client side id generation. We start from 100 to avoid querying the post list to get the next id as we
4249// may be offline and accessing this page directly (without going through the list page first) which would
@@ -51,78 +58,86 @@ const getNewId = (mutationMode: MutationMode) => {
5158
5259const PostCreateToolbar = ( {
5360 mutationMode,
61+ setMutationMode,
5462} : {
5563 mutationMode : MutationMode ;
64+ setMutationMode : ( mutationMode : MutationMode ) => void ;
5665} ) => {
5766 const notify = useNotify ( ) ;
5867 const redirect = useRedirect ( ) ;
5968 const { reset } = useFormContext ( ) ;
6069
6170 return (
62- < Toolbar >
63- < SaveButton label = "post.action.save_and_edit" variant = "text" />
64- < SaveButton
65- label = "post.action.save_and_show"
66- type = "button"
67- variant = "text"
68- mutationOptions = { {
69- onSuccess : data => {
70- notify ( 'resources.posts.notifications.created' , {
71- type : 'info' ,
72- messageArgs : { smart_count : 1 } ,
73- undoable : mutationMode === 'undoable' ,
74- } ) ;
75- redirect ( 'show' , 'posts' , data . id ) ;
76- } ,
77- } }
78- transform = { data => ( {
79- ...data ,
80- id : getNewId ( mutationMode ) ,
81- average_note : 10 ,
82- } ) }
83- sx = { { display : { xs : 'none' , sm : 'flex' } } }
84- />
85- < SaveButton
86- label = "post.action.save_and_add"
87- type = "button"
88- variant = "text"
89- mutationOptions = { {
90- onSuccess : ( ) => {
91- reset ( ) ;
92- window . scrollTo ( 0 , 0 ) ;
93- notify ( 'resources.posts.notifications.created' , {
94- type : 'info' ,
95- messageArgs : { smart_count : 1 } ,
96- undoable : mutationMode === 'undoable' ,
97- } ) ;
98- } ,
99- } }
100- transform = { data => ( {
101- ...data ,
102- id : getNewId ( mutationMode ) ,
103- average_note : 10 ,
104- } ) }
105- />
106- < SaveButton
107- label = "post.action.save_with_average_note"
108- type = "button"
109- variant = "text"
110- mutationOptions = { {
111- onSuccess : data => {
112- notify ( 'resources.posts.notifications.created' , {
113- type : 'info' ,
114- messageArgs : { smart_count : 1 } ,
115- undoable : mutationMode === 'undoable' ,
116- } ) ;
117- redirect ( 'show' , 'posts' , data . id ) ;
118- } ,
119- } }
120- transform = { data => ( {
121- ...data ,
122- id : getNewId ( mutationMode ) ,
123- average_note : 10 ,
124- } ) }
125- sx = { { display : { xs : 'none' , sm : 'flex' } } }
71+ < Toolbar sx = { { gap : 1 } } >
72+ < Stack direction = "row" spacing = { 1 } sx = { { flexGrow : 1 } } >
73+ < SaveButton label = "post.action.save_and_edit" variant = "text" />
74+ < SaveButton
75+ label = "post.action.save_and_show"
76+ type = "button"
77+ variant = "text"
78+ mutationOptions = { {
79+ onSuccess : data => {
80+ notify ( 'resources.posts.notifications.created' , {
81+ type : 'info' ,
82+ messageArgs : { smart_count : 1 } ,
83+ undoable : mutationMode === 'undoable' ,
84+ } ) ;
85+ redirect ( 'show' , 'posts' , data . id ) ;
86+ } ,
87+ } }
88+ transform = { data => ( {
89+ ...data ,
90+ id : getNewId ( mutationMode ) ,
91+ average_note : 10 ,
92+ } ) }
93+ sx = { { display : { xs : 'none' , sm : 'flex' } } }
94+ />
95+ < SaveButton
96+ label = "post.action.save_and_add"
97+ type = "button"
98+ variant = "text"
99+ mutationOptions = { {
100+ onSuccess : ( ) => {
101+ reset ( ) ;
102+ window . scrollTo ( 0 , 0 ) ;
103+ notify ( 'resources.posts.notifications.created' , {
104+ type : 'info' ,
105+ messageArgs : { smart_count : 1 } ,
106+ undoable : mutationMode === 'undoable' ,
107+ } ) ;
108+ } ,
109+ } }
110+ transform = { data => ( {
111+ ...data ,
112+ id : getNewId ( mutationMode ) ,
113+ average_note : 10 ,
114+ } ) }
115+ />
116+ < SaveButton
117+ label = "post.action.save_with_average_note"
118+ type = "button"
119+ variant = "text"
120+ mutationOptions = { {
121+ onSuccess : data => {
122+ notify ( 'resources.posts.notifications.created' , {
123+ type : 'info' ,
124+ messageArgs : { smart_count : 1 } ,
125+ undoable : mutationMode === 'undoable' ,
126+ } ) ;
127+ redirect ( 'show' , 'posts' , data . id ) ;
128+ } ,
129+ } }
130+ transform = { data => ( {
131+ ...data ,
132+ id : getNewId ( mutationMode ) ,
133+ average_note : 10 ,
134+ } ) }
135+ sx = { { display : { xs : 'none' , sm : 'flex' } } }
136+ />
137+ </ Stack >
138+ < MutationModesSelector
139+ mutationMode = { mutationMode }
140+ setMutationMode = { setMutationMode }
126141 />
127142 </ Toolbar >
128143 ) ;
@@ -135,43 +150,29 @@ const backlinksDefaultValue = [
135150 } ,
136151] ;
137152
138- const useMutationMode = ( ) : MutationMode => {
139- const [ searchParams ] = useSearchParams ( ) ;
140- const mutationMode = searchParams . get ( 'mutationMode' ) ?? 'pessimistic' ;
141-
142- return [ 'optimistic' , 'undoable' , 'pessimistic' ] . includes ( mutationMode )
143- ? ( mutationMode as MutationMode )
144- : 'pessimistic' ;
145- } ;
146-
147153const PostCreate = ( ) => {
148154 const defaultValues = useMemo (
149155 ( ) => ( {
150156 average_note : 0 ,
151157 } ) ,
152158 [ ]
153159 ) ;
154- const mutationMode = useMutationMode ( ) ;
160+ const [ mutationMode , setMutationMode ] =
161+ React . useState < MutationMode > ( 'pessimistic' ) ;
155162 const dateDefaultValue = useMemo ( ( ) => new Date ( ) , [ ] ) ;
156163 return (
157164 < Create
158165 redirect = "edit"
159166 mutationMode = { mutationMode }
160167 transform = { data => ( { ...data , id : getNewId ( mutationMode ) } ) }
161168 >
162- < Alert severity = "info" role = "presentation" >
163- To test offline support, add either{ ' ' }
164- < Link to = "/posts/create?mutationMode=optimistic" >
165- < code > ?mutationMode=optimistic</ code >
166- </ Link > { ' ' }
167- or
168- < Link to = "/posts/create?mutationMode=undoable" >
169- < code > ?mutationMode=undoable</ code >
170- </ Link > { ' ' }
171- to the page search parameters.
172- </ Alert >
173169 < SimpleFormConfigurable
174- toolbar = { < PostCreateToolbar mutationMode = { mutationMode } /> }
170+ toolbar = {
171+ < PostCreateToolbar
172+ mutationMode = { mutationMode }
173+ setMutationMode = { setMutationMode }
174+ />
175+ }
175176 defaultValues = { defaultValues }
176177 sx = { { maxWidth : { md : 'auto' , lg : '30em' } } }
177178 >
@@ -326,3 +327,95 @@ const CreateUser = () => {
326327 </ Dialog >
327328 ) ;
328329} ;
330+
331+ const MutationModes = [ 'pessimistic' , 'optimistic' , 'undoable' ] as const ;
332+ const MutationModesSelector = ( props : {
333+ mutationMode : MutationMode ;
334+ setMutationMode : ( mode : MutationMode ) => void ;
335+ } ) => {
336+ const { setMutationMode, mutationMode } = props ;
337+ const [ open , setOpen ] = React . useState ( false ) ;
338+ const anchorRef = React . useRef < HTMLDivElement > ( null ) ;
339+ const buttonRef = React . useRef < HTMLButtonElement > ( null ) ;
340+
341+ const handleMenuItemClick = ( mutationMode : MutationMode ) => {
342+ setOpen ( false ) ;
343+ setMutationMode ( mutationMode ) ;
344+ } ;
345+
346+ const handleToggle = ( ) => {
347+ setOpen ( prevOpen => ! prevOpen ) ;
348+ } ;
349+
350+ const handleClose = ( event : Event ) => {
351+ if (
352+ anchorRef . current &&
353+ anchorRef . current . contains ( event . target as HTMLElement )
354+ ) {
355+ return ;
356+ }
357+
358+ setOpen ( false ) ;
359+ } ;
360+
361+ return (
362+ < >
363+ < ButtonGroup
364+ variant = "text"
365+ ref = { anchorRef }
366+ aria-label = "Button group with a nested menu"
367+ >
368+ < Button ref = { buttonRef } > { mutationMode } </ Button >
369+ < Button
370+ size = "small"
371+ aria-controls = { open ? 'split-button-menu' : undefined }
372+ aria-expanded = { open ? 'true' : undefined }
373+ aria-label = "select merge strategy"
374+ aria-haspopup = "menu"
375+ onClick = { handleToggle }
376+ >
377+ < ArrowDropDownIcon />
378+ </ Button >
379+ </ ButtonGroup >
380+ < Popper
381+ sx = { { zIndex : 1 } }
382+ open = { open }
383+ anchorEl = { anchorRef . current }
384+ role = { undefined }
385+ transition
386+ disablePortal
387+ >
388+ { ( { TransitionProps, placement } ) => (
389+ < Grow
390+ { ...TransitionProps }
391+ style = { {
392+ transformOrigin :
393+ placement === 'bottom'
394+ ? 'center top'
395+ : 'center bottom' ,
396+ } }
397+ >
398+ < Paper >
399+ < ClickAwayListener onClickAway = { handleClose } >
400+ < MenuList id = "split-button-menu" autoFocusItem >
401+ { MutationModes . map ( mutationMode => (
402+ < MenuItem
403+ key = { mutationMode }
404+ onClick = { ( ) =>
405+ handleMenuItemClick (
406+ mutationMode
407+ )
408+ }
409+ >
410+ { mutationMode }
411+ </ MenuItem >
412+ ) ) }
413+ </ MenuList >
414+ </ ClickAwayListener >
415+ </ Paper >
416+ </ Grow >
417+ ) }
418+ </ Popper >
419+ </ >
420+ ) ;
421+ } ;
0 commit comments