@@ -23,6 +23,8 @@ import { reduceFieldsToValuesWithValidation } from '../../utilities/reduceFields
23
23
import './index.scss'
24
24
25
25
const baseClass = 'autosave'
26
+ // The minimum time the saving state should be shown
27
+ const minimumAnimationTime = 1000
26
28
27
29
export type Props = {
28
30
collection ?: ClientCollectionConfig
@@ -80,14 +82,19 @@ export const Autosave: React.FC<Props> = ({
80
82
// Store locale in ref so the autosave func
81
83
// can always retrieve the most to date locale
82
84
localeRef . current = locale
83
- console . log ( modifiedRef . current , modified )
85
+
84
86
// When debounced fields change, autosave
85
87
useEffect ( ( ) => {
86
88
const abortController = new AbortController ( )
87
89
let autosaveTimeout = undefined
90
+ // We need to log the time in order to figure out if we need to trigger the state off later
91
+ let startTimestamp = undefined
92
+ let endTimestamp = undefined
88
93
89
94
const autosave = ( ) => {
90
95
if ( modified ) {
96
+ startTimestamp = new Date ( ) . getTime ( )
97
+
91
98
setSaving ( true )
92
99
93
100
let url : string
@@ -107,100 +114,112 @@ export const Autosave: React.FC<Props> = ({
107
114
}
108
115
109
116
if ( url ) {
110
- autosaveTimeout = setTimeout ( async ( ) => {
111
- if ( modifiedRef . current ) {
112
- const { data, valid } = {
113
- ...reduceFieldsToValuesWithValidation ( fieldRef . current , true ) ,
114
- }
115
- data . _status = 'draft'
116
- const skipSubmission =
117
- submitted && ! valid && versionsConfig ?. drafts && versionsConfig ?. drafts ?. validate
118
-
119
- if ( ! skipSubmission ) {
120
- const res = await fetch ( url , {
121
- body : JSON . stringify ( data ) ,
122
- credentials : 'include' ,
123
- headers : {
124
- 'Accept-Language' : i18n . language ,
125
- 'Content-Type' : 'application/json' ,
126
- } ,
127
- method,
128
- signal : abortController . signal ,
129
- } )
117
+ if ( modifiedRef . current ) {
118
+ const { data, valid } = {
119
+ ...reduceFieldsToValuesWithValidation ( fieldRef . current , true ) ,
120
+ }
121
+ data . _status = 'draft'
122
+ const skipSubmission =
123
+ submitted && ! valid && versionsConfig ?. drafts && versionsConfig ?. drafts ?. validate
130
124
131
- if ( res . status === 200 ) {
125
+ if ( ! skipSubmission ) {
126
+ void fetch ( url , {
127
+ body : JSON . stringify ( data ) ,
128
+ credentials : 'include' ,
129
+ headers : {
130
+ 'Accept-Language' : i18n . language ,
131
+ 'Content-Type' : 'application/json' ,
132
+ } ,
133
+ method,
134
+ signal : abortController . signal ,
135
+ } )
136
+ . then ( ( res ) => {
132
137
const newDate = new Date ( )
133
- setLastSaved ( newDate . getTime ( ) )
134
- setModified ( false )
135
- reportUpdate ( {
136
- id,
137
- entitySlug,
138
- updatedAt : newDate . toISOString ( ) ,
139
- } )
140
- void getVersions ( )
141
- }
142
-
143
- if (
144
- versionsConfig ?. drafts &&
145
- versionsConfig ?. drafts ?. validate &&
146
- res . status === 400
147
- ) {
148
- const json = await res . json ( )
149
- if ( Array . isArray ( json . errors ) ) {
150
- const [ fieldErrors , nonFieldErrors ] = json . errors . reduce (
151
- ( [ fieldErrs , nonFieldErrs ] , err ) => {
152
- const newFieldErrs = [ ]
153
- const newNonFieldErrs = [ ]
154
-
155
- if ( err ?. message ) {
156
- newNonFieldErrs . push ( err )
157
- }
158
-
159
- if ( Array . isArray ( err ?. data ) ) {
160
- err . data . forEach ( ( dataError ) => {
161
- if ( dataError ?. field ) {
162
- newFieldErrs . push ( dataError )
163
- } else {
164
- newNonFieldErrs . push ( dataError )
165
- }
166
- } )
167
- }
168
-
169
- return [
170
- [ ...fieldErrs , ...newFieldErrs ] ,
171
- [ ...nonFieldErrs , ...newNonFieldErrs ] ,
172
- ]
173
- } ,
174
- [ [ ] , [ ] ] ,
175
- )
138
+ // We need to log the time in order to figure out if we need to trigger the state off later
139
+ endTimestamp = newDate . getTime ( )
176
140
177
- dispatchFields ( {
178
- type : 'ADD_SERVER_ERRORS' ,
179
- errors : fieldErrors ,
180
- } )
141
+ if ( res . status === 200 ) {
142
+ setLastSaved ( newDate . getTime ( ) )
181
143
182
- nonFieldErrors . forEach ( ( err ) => {
183
- toast . error ( err . message || i18n . t ( 'error:unknown' ) )
144
+ reportUpdate ( {
145
+ id,
146
+ entitySlug,
147
+ updatedAt : newDate . toISOString ( ) ,
184
148
} )
149
+ setModified ( false )
150
+ void getVersions ( )
151
+ } else {
152
+ return res . json ( )
153
+ }
154
+ } )
155
+ . then ( ( json ) => {
156
+ if ( versionsConfig ?. drafts && versionsConfig ?. drafts ?. validate && json . errors ) {
157
+ if ( Array . isArray ( json . errors ) ) {
158
+ const [ fieldErrors , nonFieldErrors ] = json . errors . reduce (
159
+ ( [ fieldErrs , nonFieldErrs ] , err ) => {
160
+ const newFieldErrs = [ ]
161
+ const newNonFieldErrs = [ ]
162
+
163
+ if ( err ?. message ) {
164
+ newNonFieldErrs . push ( err )
165
+ }
185
166
186
- setSubmitted ( true )
167
+ if ( Array . isArray ( err ?. data ) ) {
168
+ err . data . forEach ( ( dataError ) => {
169
+ if ( dataError ?. field ) {
170
+ newFieldErrs . push ( dataError )
171
+ } else {
172
+ newNonFieldErrs . push ( dataError )
173
+ }
174
+ } )
175
+ }
176
+
177
+ return [
178
+ [ ...fieldErrs , ...newFieldErrs ] ,
179
+ [ ...nonFieldErrs , ...newNonFieldErrs ] ,
180
+ ]
181
+ } ,
182
+ [ [ ] , [ ] ] ,
183
+ )
184
+
185
+ dispatchFields ( {
186
+ type : 'ADD_SERVER_ERRORS' ,
187
+ errors : fieldErrors ,
188
+ } )
189
+
190
+ nonFieldErrors . forEach ( ( err ) => {
191
+ toast . error ( err . message || i18n . t ( 'error:unknown' ) )
192
+ } )
193
+
194
+ setSubmitted ( true )
195
+ setSaving ( false )
196
+ return
197
+ }
198
+ }
199
+ } )
200
+ . then ( ( ) => {
201
+ // If request was faster than minimum animation time, animate the difference
202
+ if ( endTimestamp - startTimestamp < minimumAnimationTime ) {
203
+ autosaveTimeout = setTimeout (
204
+ ( ) => {
205
+ setSaving ( false )
206
+ } ,
207
+ minimumAnimationTime - ( endTimestamp - startTimestamp ) ,
208
+ )
209
+ } else {
187
210
setSaving ( false )
188
- return
189
211
}
190
- }
191
- }
212
+ } )
192
213
}
193
-
194
- setSaving ( false )
195
- } , 1000 )
214
+ }
196
215
}
197
216
}
198
217
}
199
218
200
219
void autosave ( )
201
220
202
221
return ( ) => {
203
- clearTimeout ( autosaveTimeout )
222
+ if ( autosaveTimeout ) clearTimeout ( autosaveTimeout )
204
223
if ( abortController . signal ) abortController . abort ( )
205
224
setSaving ( false )
206
225
}
@@ -234,7 +253,7 @@ export const Autosave: React.FC<Props> = ({
234
253
return (
235
254
< div className = { baseClass } >
236
255
{ saving && t ( 'general:saving' ) }
237
- { ! saving && lastSaved && (
256
+ { ! saving && Boolean ( lastSaved ) && (
238
257
< React . Fragment >
239
258
{ t ( 'version:lastSavedAgo' , {
240
259
distance : formatTimeToNow ( { date : lastSaved , i18n } ) ,
0 commit comments