@@ -136,6 +136,7 @@ export default class ConfigDialog extends React.Component {
136136 optionsMenuOpen : false ,
137137 error : null ,
138138 syntaxColors : null ,
139+ showDiscardConfirm : false ,
139140 } ;
140141 if ( props . param . length > 0 ) {
141142 let initialValue = props . value ;
@@ -154,8 +155,17 @@ export default class ConfigDialog extends React.Component {
154155 confirmOverride : false ,
155156 error : initialError ,
156157 syntaxColors : null ,
158+ showDiscardConfirm : false ,
157159 } ;
158160 }
161+
162+ // Store initial values for change detection
163+ this . initialValues = {
164+ name : this . state . name ,
165+ type : this . state . type ,
166+ value : this . state . value ,
167+ masterKeyOnly : this . state . masterKeyOnly ,
168+ } ;
159169 }
160170
161171 componentDidMount ( ) {
@@ -330,7 +340,47 @@ export default class ConfigDialog extends React.Component {
330340 return true ;
331341 }
332342
343+ handleCancel ( ) {
344+ if ( this . state . showDiscardConfirm ) {
345+ this . setState ( { showDiscardConfirm : false } ) ;
346+ return ;
347+ }
348+ if ( this . hasChanges ( ) ) {
349+ this . setState ( { showDiscardConfirm : true } ) ;
350+ } else {
351+ this . props . onCancel ( ) ;
352+ }
353+ }
354+
355+ hasChanges ( ) {
356+ if (
357+ this . state . name !== this . initialValues . name ||
358+ this . state . type !== this . initialValues . type ||
359+ this . state . masterKeyOnly !== this . initialValues . masterKeyOnly
360+ ) {
361+ return true ;
362+ }
363+ const currVal = this . state . value ;
364+ const initVal = this . initialValues . value ;
365+ if ( currVal === initVal ) {
366+ return false ;
367+ }
368+ if ( this . state . type === 'Date' && currVal && initVal ) {
369+ return new Date ( currVal ) . getTime ( ) !== new Date ( initVal ) . getTime ( ) ;
370+ }
371+ if ( this . state . type === 'GeoPoint' && currVal && initVal ) {
372+ return currVal . latitude !== initVal . latitude || currVal . longitude !== initVal . longitude ;
373+ }
374+ if ( this . state . type === 'File' && currVal && initVal ) {
375+ return currVal . url ( ) !== initVal . url ( ) ;
376+ }
377+ return true ;
378+ }
379+
333380 submit ( ) {
381+ if ( this . state . showDiscardConfirm ) {
382+ return ;
383+ }
334384 this . props . onConfirm ( {
335385 name : this . state . name ,
336386 type : this . state . type ,
@@ -381,7 +431,7 @@ export default class ConfigDialog extends React.Component {
381431 // don't reset the editor value — preserve user edits.
382432 // Auto-enable the Diff toggle and reset the override confirmation.
383433 if ( this . props . conflict && ( ! prevProps . conflict || this . props . value !== prevProps . value ) ) {
384- this . setState ( { showDiff : true , confirmOverride : false } ) ;
434+ this . setState ( { showDiff : true , confirmOverride : false , showDiscardConfirm : false } ) ;
385435 return ;
386436 }
387437
@@ -399,6 +449,8 @@ export default class ConfigDialog extends React.Component {
399449 error,
400450 masterKeyOnly : this . props . masterKeyOnly ,
401451 } ) ;
452+ this . initialValues . value = updatedValue ;
453+ this . initialValues . masterKeyOnly = this . props . masterKeyOnly ;
402454 }
403455 }
404456
@@ -631,7 +683,7 @@ export default class ConfigDialog extends React.Component {
631683 ) }
632684 </ div >
633685 < div style = { { display : 'flex' , gap : '12px' } } >
634- < Button value = "Cancel" onClick = { this . props . onCancel } />
686+ < Button value = "Cancel" onClick = { this . handleCancel . bind ( this ) } />
635687 < Button
636688 primary = { true }
637689 color = "blue"
@@ -645,21 +697,34 @@ export default class ConfigDialog extends React.Component {
645697 ) ;
646698
647699 return (
648- < Modal
649- type = { Modal . Types . INFO }
650- title = { newParam ? 'New parameter' : 'Edit parameter' }
651- icon = "gear-solid"
652- iconSize = { 30 }
653- subtitle = { 'Dynamically configure parts of your app' }
654- customFooter = { customFooter }
655- disabled = { ! this . valid ( ) || this . props . loading || ( this . props . conflict && ! this . state . confirmOverride ) }
656- onCancel = { this . props . onCancel }
657- onConfirm = { this . submit . bind ( this ) }
658- >
659- < LoaderContainer loading = { this . props . loading } >
660- { dialogContent }
661- </ LoaderContainer >
662- </ Modal >
700+ < >
701+ < Modal
702+ type = { Modal . Types . INFO }
703+ title = { newParam ? 'New parameter' : 'Edit parameter' }
704+ icon = "gear-solid"
705+ iconSize = { 30 }
706+ subtitle = { 'Dynamically configure parts of your app' }
707+ customFooter = { customFooter }
708+ disabled = { ! this . valid ( ) || this . props . loading || ( this . props . conflict && ! this . state . confirmOverride ) }
709+ onCancel = { this . handleCancel . bind ( this ) }
710+ onConfirm = { this . submit . bind ( this ) }
711+ >
712+ < LoaderContainer loading = { this . props . loading } >
713+ { dialogContent }
714+ </ LoaderContainer >
715+ </ Modal >
716+ { this . state . showDiscardConfirm && (
717+ < Modal
718+ type = { Modal . Types . DANGER }
719+ title = "Discard unsaved changes?"
720+ subtitle = "Your changes will be lost."
721+ confirmText = "Discard"
722+ cancelText = "Keep editing"
723+ onConfirm = { this . props . onCancel }
724+ onCancel = { ( ) => this . setState ( { showDiscardConfirm : false } ) }
725+ />
726+ ) }
727+ </ >
663728 ) ;
664729 }
665730}
0 commit comments