@@ -31,6 +31,7 @@ export type ReplSaveContextType = {
31
31
isDirty : boolean
32
32
isEffectivelyDirty : boolean
33
33
isSaving : boolean
34
+ isForking : boolean
34
35
allowSave : boolean
35
36
allowFork : boolean
36
37
}
@@ -48,6 +49,7 @@ export default function ReplSaveProvider({ children }: { children: React.ReactNo
48
49
const { flushPendingChanges } = useWritableModels ( )
49
50
const [ savedState , setSavedState ] = useReplSavedState ( )
50
51
const [ isSaving , setIsSaving ] = useState ( false )
52
+ const [ isForking , setIsForking ] = useState ( false )
51
53
52
54
const formatOnSave = useMemo (
53
55
( ) => userStoredState . workbench . formatOnSave ,
@@ -67,18 +69,18 @@ export default function ReplSaveProvider({ children }: { children: React.ReactNo
67
69
const isDirty = useMemo < boolean > ( ( ) => checkDirty ( state , savedState ) , [ state , savedState ] )
68
70
69
71
const allowSave = useMemo < boolean > (
70
- ( ) => ! isSaving && ( isNew || isDirty ) ,
71
- [ isSaving , isNew , isDirty ]
72
+ ( ) => ! isSaving && ! isForking && ( isNew || isEffectivelyDirty ) ,
73
+ [ isSaving , isForking , isNew , isEffectivelyDirty ]
72
74
)
73
75
74
- const allowFork = useMemo < boolean > ( ( ) => ! isSaving && ! isNew , [ isSaving , isNew ] )
76
+ const allowFork = useMemo < boolean > ( ( ) => ! isForking && ! isNew , [ isForking , isNew ] )
75
77
76
78
useEffect ( ( ) => {
77
79
stateRef . current = state
78
80
} , [ state ] )
79
81
80
- const handleSaveError = useCallback (
81
- async ( error : unknown ) => {
82
+ const handleSaveOrForkError = useCallback (
83
+ async ( type : 'save' | 'fork' , error : unknown ) => {
82
84
const isAborted = isAbortError ( error instanceof ResponseError ? error . cause : error )
83
85
if ( isAborted ) {
84
86
return
@@ -88,21 +90,26 @@ export default function ReplSaveProvider({ children }: { children: React.ReactNo
88
90
89
91
if ( error instanceof ResponseError ) {
90
92
if ( error . status === 401 ) {
91
- toast . info ( 'Please sign in to save your changes.' , {
92
- action : {
93
- label : 'Sign in with Github' ,
94
- onClick : ( ) => {
95
- signInWithGithub ( { popup : true } )
93
+ toast . info (
94
+ type === 'save'
95
+ ? 'Please sign in to save your changes.'
96
+ : 'Please sign in to fork this REPL.' ,
97
+ {
98
+ action : {
99
+ label : 'Sign in with Github' ,
100
+ onClick : ( ) => {
101
+ signInWithGithub ( { popup : true } )
102
+ } ,
96
103
} ,
97
- } ,
98
- } )
104
+ }
105
+ )
99
106
} else {
100
107
toast . error ( error . message , {
101
108
description : `${ error . status } ${ error . statusText } : ${ error . cause ?? 'Something went wrong :(' } ` ,
102
109
} )
103
110
}
104
111
} else {
105
- toast . error ( ' Failed to save' , {
112
+ toast . error ( ` Failed to ${ type } ` , {
106
113
description : error instanceof Error ? error . message : 'Unknown error' ,
107
114
} )
108
115
}
@@ -123,7 +130,7 @@ export default function ReplSaveProvider({ children }: { children: React.ReactNo
123
130
124
131
return await callback ( { signal } )
125
132
} catch ( error ) {
126
- handleSaveError ( error )
133
+ handleSaveOrForkError ( 'save' , error )
127
134
return false
128
135
} finally {
129
136
if ( signal === abortControllerRef . current ?. signal ) {
@@ -132,7 +139,32 @@ export default function ReplSaveProvider({ children }: { children: React.ReactNo
132
139
}
133
140
}
134
141
} ,
135
- [ handleSaveError ]
142
+ [ handleSaveOrForkError ]
143
+ )
144
+
145
+ const forkWrapper = useCallback (
146
+ async ( callback : ( args : { signal : AbortSignal } ) => Promise < boolean > ) => {
147
+ let signal : AbortSignal | null = null
148
+
149
+ try {
150
+ setIsForking ( true )
151
+
152
+ abortControllerRef . current ?. abort ( )
153
+ abortControllerRef . current = new AbortController ( )
154
+ signal = abortControllerRef . current . signal
155
+
156
+ return await callback ( { signal } )
157
+ } catch ( error ) {
158
+ handleSaveOrForkError ( 'fork' , error )
159
+ return false
160
+ } finally {
161
+ if ( signal === abortControllerRef . current ?. signal ) {
162
+ setIsForking ( false )
163
+ abortControllerRef . current = null
164
+ }
165
+ }
166
+ } ,
167
+ [ handleSaveOrForkError ]
136
168
)
137
169
138
170
const saveState = useCallback < ( ) => Promise < boolean > > ( async ( ) => {
@@ -229,7 +261,7 @@ export default function ReplSaveProvider({ children }: { children: React.ReactNo
229
261
] )
230
262
231
263
const forkState = useCallback < ( ) => Promise < boolean > > ( async ( ) => {
232
- return await saveWrapper ( forkFn )
264
+ return await forkWrapper ( forkFn )
233
265
234
266
async function forkFn ( { signal } : { signal : AbortSignal } ) {
235
267
if ( ! user ) {
@@ -280,7 +312,7 @@ export default function ReplSaveProvider({ children }: { children: React.ReactNo
280
312
user ,
281
313
setState ,
282
314
setSavedState ,
283
- saveWrapper ,
315
+ forkWrapper ,
284
316
signInWithGithub ,
285
317
queryClient ,
286
318
supabase ,
@@ -296,6 +328,7 @@ export default function ReplSaveProvider({ children }: { children: React.ReactNo
296
328
isEffectivelyDirty,
297
329
isDirty,
298
330
isSaving,
331
+ isForking,
299
332
allowSave,
300
333
allowFork,
301
334
} }
0 commit comments