@@ -21,6 +21,8 @@ import TableView from 'dashboard/TableView.react';
21
21
import Toolbar from 'components/Toolbar/Toolbar.react' ;
22
22
import browserStyles from 'dashboard/Data/Browser/Browser.scss' ;
23
23
import { CurrentApp } from 'context/currentApp' ;
24
+ import Modal from 'components/Modal/Modal.react' ;
25
+ import equal from 'fast-deep-equal' ;
24
26
25
27
@subscribeTo ( 'Config' , 'config' )
26
28
class Config extends TableView {
@@ -38,6 +40,7 @@ class Config extends TableView {
38
40
modalValue : '' ,
39
41
modalMasterKeyOnly : false ,
40
42
loading : false ,
43
+ confirmModalOpen : false ,
41
44
} ;
42
45
}
43
46
@@ -55,11 +58,14 @@ class Config extends TableView {
55
58
this . loadData ( ) ;
56
59
}
57
60
58
- loadData ( ) {
61
+ async loadData ( ) {
59
62
this . setState ( { loading : true } ) ;
60
- this . props . config . dispatch ( ActionTypes . FETCH ) . finally ( ( ) => {
63
+ try {
64
+ await this . props . config . dispatch ( ActionTypes . FETCH ) ;
65
+ this . cacheData = new Map ( this . props . config . data ) ;
66
+ } finally {
61
67
this . setState ( { loading : false } ) ;
62
- } ) ;
68
+ }
63
69
}
64
70
65
71
renderToolbar ( ) {
@@ -90,6 +96,7 @@ class Config extends TableView {
90
96
value = { this . state . modalValue }
91
97
masterKeyOnly = { this . state . modalMasterKeyOnly }
92
98
parseServerVersion = { this . context . serverInfo ?. parseServerVersion }
99
+ loading = { this . state . loading }
93
100
/>
94
101
) ;
95
102
} else if ( this . state . showDeleteParameterDialog ) {
@@ -101,11 +108,35 @@ class Config extends TableView {
101
108
/>
102
109
) ;
103
110
}
111
+
112
+ if ( this . state . confirmModalOpen ) {
113
+ extras = (
114
+ < Modal
115
+ type = { Modal . Types . INFO }
116
+ icon = "warn-outline"
117
+ title = { 'Are you sure?' }
118
+ confirmText = "Continue"
119
+ cancelText = "Cancel"
120
+ onCancel = { ( ) => this . setState ( { confirmModalOpen : false } ) }
121
+ onConfirm = { ( ) => {
122
+ this . setState ( { confirmModalOpen : false } ) ;
123
+ this . saveParam ( {
124
+ ...this . confirmData ,
125
+ override : true ,
126
+ } ) ;
127
+ } }
128
+ >
129
+ < div className = { [ browserStyles . confirmConfig ] } >
130
+ This parameter changed while you were editing it. If you continue, the latest changes will be lost and replaced with your version. Do you want to proceed?
131
+ </ div >
132
+ </ Modal >
133
+ ) ;
134
+ }
104
135
return extras ;
105
136
}
106
137
107
- renderRow ( data ) {
108
- let value = data . value ;
138
+ parseValueForModal ( dataValue ) {
139
+ let value = dataValue ;
109
140
let modalValue = value ;
110
141
let type = typeof value ;
111
142
@@ -120,11 +151,11 @@ class Config extends TableView {
120
151
} else if ( value instanceof Parse . GeoPoint ) {
121
152
type = 'GeoPoint' ;
122
153
value = `(${ value . latitude } , ${ value . longitude } )` ;
123
- modalValue = data . value . toJSON ( ) ;
124
- } else if ( data . value instanceof Parse . File ) {
154
+ modalValue = dataValue . toJSON ( ) ;
155
+ } else if ( dataValue instanceof Parse . File ) {
125
156
type = 'File' ;
126
157
value = (
127
- < a target = "_blank" href = { data . value . url ( ) } rel = "noreferrer" >
158
+ < a target = "_blank" href = { dataValue . url ( ) } rel = "noreferrer" >
128
159
Open in new window
129
160
</ a >
130
161
) ;
@@ -139,14 +170,53 @@ class Config extends TableView {
139
170
}
140
171
type = type . substr ( 0 , 1 ) . toUpperCase ( ) + type . substr ( 1 ) ;
141
172
}
142
- const openModal = ( ) =>
173
+
174
+ return {
175
+ value : value ,
176
+ modalValue : modalValue ,
177
+ type : type ,
178
+ } ;
179
+ }
180
+
181
+ renderRow ( data ) {
182
+ // Parse modal data
183
+ const { value, modalValue, type } = this . parseValueForModal ( data . value ) ;
184
+
185
+ /**
186
+ * Opens the modal dialog to edit the Config parameter.
187
+ */
188
+ const openModal = async ( ) => {
189
+
190
+ // Show dialog
143
191
this . setState ( {
192
+ loading : true ,
144
193
modalOpen : true ,
145
194
modalParam : data . param ,
146
195
modalType : type ,
147
196
modalValue : modalValue ,
148
197
modalMasterKeyOnly : data . masterKeyOnly ,
149
198
} ) ;
199
+
200
+ // Fetch config data
201
+ await this . loadData ( ) ;
202
+
203
+ // Get latest param values
204
+ const fetchedParams = this . props . config . data . get ( 'params' ) ;
205
+ const fetchedValue = fetchedParams . get ( this . state . modalParam ) ;
206
+ const fetchedMasterKeyOnly = this . props . config . data . get ( 'masterKeyOnly' ) ?. get ( this . state . modalParam ) || false ;
207
+
208
+ // Parse fetched data
209
+ const { modalValue : fetchedModalValue } = this . parseValueForModal ( fetchedValue ) ;
210
+
211
+ // Update dialog
212
+ this . setState ( {
213
+ modalValue : fetchedModalValue ,
214
+ modalMasterKeyOnly : fetchedMasterKeyOnly ,
215
+ loading : false ,
216
+ } ) ;
217
+ } ;
218
+
219
+ // Define column styles
150
220
const columnStyleLarge = { width : '30%' , cursor : 'pointer' } ;
151
221
const columnStyleSmall = { width : '15%' , cursor : 'pointer' } ;
152
222
@@ -244,58 +314,95 @@ class Config extends TableView {
244
314
return data ;
245
315
}
246
316
247
- saveParam ( { name, value, type, masterKeyOnly } ) {
248
- this . props . config
249
- . dispatch ( ActionTypes . SET , {
317
+ async saveParam ( { name, value, type, masterKeyOnly, override } ) {
318
+ try {
319
+ this . setState ( { loading : true } ) ;
320
+
321
+ const fetchedParams = this . props . config . data . get ( 'params' ) ;
322
+ const currentValue = fetchedParams . get ( name ) ;
323
+ await this . props . config . dispatch ( ActionTypes . FETCH ) ;
324
+ const fetchedParamsAfter = this . props . config . data . get ( 'params' ) ;
325
+ const currentValueAfter = fetchedParamsAfter . get ( name ) ;
326
+ const valuesAreEqual = equal ( currentValue , currentValueAfter ) ;
327
+
328
+ if ( ! valuesAreEqual && ! override ) {
329
+ this . setState ( {
330
+ confirmModalOpen : true ,
331
+ modalOpen : false ,
332
+ loading : false ,
333
+ } ) ;
334
+ this . confirmData = {
335
+ name,
336
+ value,
337
+ type,
338
+ masterKeyOnly,
339
+ } ;
340
+ return ;
341
+ }
342
+
343
+ await this . props . config . dispatch ( ActionTypes . SET , {
250
344
param : name ,
251
345
value : value ,
252
346
masterKeyOnly : masterKeyOnly ,
253
- } )
254
- . then (
255
- ( ) => {
256
- this . setState ( { modalOpen : false } ) ;
257
- const limit = this . context . cloudConfigHistoryLimit ;
258
- const applicationId = this . context . applicationId ;
259
- let transformedValue = value ;
260
- if ( type === 'Date' ) {
261
- transformedValue = { __type : 'Date' , iso : value } ;
262
- }
263
- if ( type === 'File' ) {
264
- transformedValue = { name : value . _name , url : value . _url } ;
265
- }
266
- const configHistory = localStorage . getItem ( `${ applicationId } _configHistory` ) ;
267
- if ( ! configHistory ) {
268
- localStorage . setItem (
269
- `${ applicationId } _configHistory` ,
270
- JSON . stringify ( {
271
- [ name ] : [
272
- {
273
- time : new Date ( ) ,
274
- value : transformedValue ,
275
- } ,
276
- ] ,
277
- } )
278
- ) ;
279
- } else {
280
- const oldConfigHistory = JSON . parse ( configHistory ) ;
281
- localStorage . setItem (
282
- `${ applicationId } _configHistory` ,
283
- JSON . stringify ( {
284
- ...oldConfigHistory ,
285
- [ name ] : ! oldConfigHistory [ name ]
286
- ? [ { time : new Date ( ) , value : transformedValue } ]
287
- : [
288
- { time : new Date ( ) , value : transformedValue } ,
289
- ...oldConfigHistory [ name ] ,
290
- ] . slice ( 0 , limit || 100 ) ,
291
- } )
292
- ) ;
293
- }
294
- } ,
295
- ( ) => {
296
- // Catch the error
297
- }
347
+ } ) ;
348
+
349
+ // Update the cached data after successful save
350
+ const params = this . cacheData . get ( 'params' ) ;
351
+ params . set ( name , value ) ;
352
+ if ( masterKeyOnly ) {
353
+ const masterKeyOnlyParams = this . cacheData . get ( 'masterKeyOnly' ) || new Map ( ) ;
354
+ masterKeyOnlyParams . set ( name , masterKeyOnly ) ;
355
+ this . cacheData . set ( 'masterKeyOnly' , masterKeyOnlyParams ) ;
356
+ }
357
+
358
+ this . setState ( { modalOpen : false } ) ;
359
+
360
+ // Update config history in localStorage
361
+ const limit = this . context . cloudConfigHistoryLimit ;
362
+ const applicationId = this . context . applicationId ;
363
+ let transformedValue = value ;
364
+
365
+ if ( type === 'Date' ) {
366
+ transformedValue = { __type : 'Date' , iso : value } ;
367
+ }
368
+ if ( type === 'File' ) {
369
+ transformedValue = { name : value . _name , url : value . _url } ;
370
+ }
371
+
372
+ const configHistory = localStorage . getItem ( `${ applicationId } _configHistory` ) ;
373
+ const newHistoryEntry = {
374
+ time : new Date ( ) ,
375
+ value : transformedValue ,
376
+ } ;
377
+
378
+ if ( ! configHistory ) {
379
+ localStorage . setItem (
380
+ `${ applicationId } _configHistory` ,
381
+ JSON . stringify ( {
382
+ [ name ] : [ newHistoryEntry ] ,
383
+ } )
384
+ ) ;
385
+ } else {
386
+ const oldConfigHistory = JSON . parse ( configHistory ) ;
387
+ const updatedHistory = ! oldConfigHistory [ name ]
388
+ ? [ newHistoryEntry ]
389
+ : [ newHistoryEntry , ...oldConfigHistory [ name ] ] . slice ( 0 , limit || 100 ) ;
390
+
391
+ localStorage . setItem (
392
+ `${ applicationId } _configHistory` ,
393
+ JSON . stringify ( {
394
+ ...oldConfigHistory ,
395
+ [ name ] : updatedHistory ,
396
+ } )
397
+ ) ;
398
+ }
399
+ } catch ( error ) {
400
+ this . context . showError ?. (
401
+ `Failed to save parameter: ${ error . message || 'Unknown error occurred' } `
298
402
) ;
403
+ } finally {
404
+ this . setState ( { loading : false } ) ;
405
+ }
299
406
}
300
407
301
408
deleteParam ( name ) {
0 commit comments