@@ -8,11 +8,28 @@ import * as React from 'react';
8
8
import { useEffect } from 'react' ;
9
9
import { Assignment } from '../../../model/assignment' ;
10
10
import { Lecture } from '../../../model/lecture' ;
11
- import { generateAssignment , getAssignment , pullAssignment , pushAssignment } from '../../../services/assignments.service' ;
11
+ import {
12
+ generateAssignment ,
13
+ getAssignment ,
14
+ pullAssignment ,
15
+ pushAssignment
16
+ } from '../../../services/assignments.service' ;
12
17
import GetAppRoundedIcon from '@mui/icons-material/GetAppRounded' ;
13
18
import OpenInBrowserIcon from '@mui/icons-material/OpenInBrowser' ;
14
19
import { CommitDialog } from '../../util/dialog' ;
15
- import { Box , Button , Card , CardActions , CardContent , CardHeader , Chip , IconButton , Tab , Tabs , Tooltip } from '@mui/material' ;
20
+ import {
21
+ Box ,
22
+ Button ,
23
+ Card ,
24
+ CardActions ,
25
+ CardContent ,
26
+ CardHeader ,
27
+ Chip ,
28
+ IconButton ,
29
+ Tab ,
30
+ Tabs ,
31
+ Tooltip
32
+ } from '@mui/material' ;
16
33
import ReplayIcon from '@mui/icons-material/Replay' ;
17
34
import TerminalIcon from '@mui/icons-material/Terminal' ;
18
35
import AddIcon from '@mui/icons-material/Add' ;
@@ -24,7 +41,10 @@ import { Contents } from '@jupyterlab/services';
24
41
import { openBrowser , openTerminal } from '../overview/util' ;
25
42
import { PageConfig } from '@jupyterlab/coreutils' ;
26
43
import PublishRoundedIcon from '@mui/icons-material/PublishRounded' ;
27
- import { getRemoteStatus , lectureBasePath } from '../../../services/file.service' ;
44
+ import {
45
+ getRemoteStatus ,
46
+ lectureBasePath
47
+ } from '../../../services/file.service' ;
28
48
import { RepoType } from '../../util/repo-type' ;
29
49
import { enqueueSnackbar } from 'notistack' ;
30
50
import { useNavigate } from 'react-router-dom' ;
@@ -34,59 +54,80 @@ import { loadString, storeString } from '../../../services/storage.service';
34
54
import { queryClient } from '../../../widgets/assignmentmanage' ;
35
55
import { RemoteFileStatus } from '../../../model/remoteFileStatus' ;
36
56
import { GitLogModal } from './git-log' ;
57
+ import moment from 'moment' ;
37
58
38
59
export interface IFilesProps {
39
60
lecture : Lecture ;
40
61
assignment : Assignment ;
41
62
onAssignmentChange : ( assignment : Assignment ) => void ;
42
63
}
43
64
44
- export const Files = ( { lecture, assignment, onAssignmentChange } : IFilesProps ) => {
65
+ export const Files = ( {
66
+ lecture,
67
+ assignment,
68
+ onAssignmentChange
69
+ } : IFilesProps ) => {
45
70
const navigate = useNavigate ( ) ;
46
71
const reloadPage = ( ) => navigate ( 0 ) ;
47
72
const serverRoot = PageConfig . getOption ( 'serverRoot' ) ;
48
-
73
+
49
74
const { data : updatedLecture = lecture } = useQuery ( {
50
75
queryKey : [ 'lecture' , lecture . id ] ,
51
- queryFn : ( ) => getLecture ( lecture . id , true ) ,
76
+ queryFn : ( ) => getLecture ( lecture . id , true )
52
77
} ) ;
53
78
54
79
const { data : updatedAssignment = assignment } = useQuery ( {
55
80
queryKey : [ 'assignment' , lecture . id , assignment . id ] ,
56
- queryFn : ( ) => getAssignment ( lecture . id , assignment . id , true ) ,
81
+ queryFn : ( ) => getAssignment ( lecture . id , assignment . id , true )
57
82
} ) ;
58
83
59
- const { data : selectedDir = 'source' , refetch : refetchSelectedDir } = useQuery ( {
60
- queryKey : [ 'selectedDir' ] ,
61
- queryFn : async ( ) => {
62
- const data = await loadString ( 'files-selected-dir' ) ;
63
- return data || 'source' ;
64
- } ,
65
- } ) ;
84
+ const { data : selectedDir = 'source' , refetch : refetchSelectedDir } =
85
+ useQuery ( {
86
+ queryKey : [ 'selectedDir' ] ,
87
+ queryFn : async ( ) => {
88
+ const data = loadString ( 'files-selected-dir' ) ;
89
+ if ( data ) {
90
+ return data as 'source' | 'release' ;
91
+ } else {
92
+ return 'source' ;
93
+ }
94
+ }
95
+ } ) ;
66
96
67
97
const { data : repoStatus , refetch : refetchRepoStatus } = useQuery ( {
68
98
queryKey : [ 'repoStatus' , lecture . id , assignment . id ] ,
69
99
queryFn : async ( ) => {
70
- const response = await getRemoteStatus ( lecture , assignment , RepoType . SOURCE , true ) ;
100
+ const response = await getRemoteStatus (
101
+ lecture ,
102
+ assignment ,
103
+ RepoType . SOURCE ,
104
+ true
105
+ ) ;
71
106
return response . status ;
72
- } ,
107
+ }
73
108
} ) ;
74
109
75
-
76
- useEffect ( ( ) => {
110
+ openBrowser (
111
+ `${ lectureBasePath } ${ lecture . code } /${ selectedDir } /${ assignment . id } `
112
+ ) ;
113
+
114
+ React . useEffect ( ( ) => {
77
115
const srcPath = `${ lectureBasePath } ${ lecture . code } /source/${ assignment . id } ` ;
78
116
GlobalObjects . docManager . services . contents . fileChanged . connect (
79
117
( sender : Contents . IManager , change : Contents . IChangedArgs ) => {
80
118
const { oldValue, newValue } = change ;
81
- if ( ( newValue && ! newValue . path . includes ( srcPath ) ) || ( oldValue && ! oldValue . path . includes ( srcPath ) ) ) {
119
+ if (
120
+ ( newValue && ! newValue . path . includes ( srcPath ) ) ||
121
+ ( oldValue && ! oldValue . path . includes ( srcPath ) )
122
+ ) {
82
123
return ;
83
124
}
84
125
reloadPage ( ) ;
85
126
refetchRepoStatus ( ) ;
86
127
} ,
87
128
this
88
129
) ;
89
- } , [ assignment . id , lecture . id ] ) ;
130
+ } , [ assignment , lecture ] ) ;
90
131
91
132
/**
92
133
* Switches between source and release directory.
@@ -111,20 +152,39 @@ export const Files = ({ lecture, assignment, onAssignmentChange }: IFilesProps)
111
152
) ;
112
153
} ) ;
113
154
} else {
114
- setSelectedDir ( dir ) ;
155
+ await setSelectedDir ( dir ) ;
115
156
}
116
157
} ;
117
158
118
159
const setSelectedDir = async ( dir : 'source' | 'release' ) => {
119
160
storeString ( 'files-selected-dir' , dir ) ;
120
- refetchSelectedDir ( ) ;
161
+ refetchSelectedDir ( ) . then ( ( ) => {
162
+ openBrowser (
163
+ `${ lectureBasePath } ${ lecture . code } /${ selectedDir } /${ assignment . id } `
164
+ ) ;
165
+ } ) ;
121
166
} ;
122
167
123
- const handlePushAssignment = async ( commitMessage : string , selectedFiles : string [ ] ) => {
168
+ const handlePushAssignment = async (
169
+ commitMessage : string ,
170
+ selectedFiles : string [ ]
171
+ ) => {
124
172
try {
125
173
// Note: has to be in this order (release -> source)
126
- await pushAssignment ( lecture . id , assignment . id , 'release' , commitMessage , selectedFiles ) ;
127
- await pushAssignment ( lecture . id , assignment . id , 'source' , commitMessage , selectedFiles ) ;
174
+ await pushAssignment (
175
+ lecture . id ,
176
+ assignment . id ,
177
+ 'release' ,
178
+ commitMessage ,
179
+ selectedFiles
180
+ ) ;
181
+ await pushAssignment (
182
+ lecture . id ,
183
+ assignment . id ,
184
+ 'source' ,
185
+ commitMessage ,
186
+ selectedFiles
187
+ ) ;
128
188
await queryClient . invalidateQueries ( { queryKey : [ 'assignments' ] } ) ;
129
189
enqueueSnackbar ( 'Successfully Pushed Assignment' , { variant : 'success' } ) ;
130
190
refetchRepoStatus ( ) ;
@@ -137,7 +197,7 @@ export const Files = ({ lecture, assignment, onAssignmentChange }: IFilesProps)
137
197
try {
138
198
await pullAssignment ( lecture . id , assignment . id , 'source' ) ;
139
199
enqueueSnackbar ( 'Successfully Pulled Assignment' , { variant : 'success' } ) ;
140
- refetchRepoStatus ( ) ;
200
+ await refetchRepoStatus ( ) ;
141
201
} catch ( err ) {
142
202
enqueueSnackbar ( `Error Pulling Assignment: ${ err } ` , { variant : 'error' } ) ;
143
203
}
@@ -154,7 +214,7 @@ export const Files = ({ lecture, assignment, onAssignmentChange }: IFilesProps)
154
214
case RemoteFileStatus . StatusEnum . Divergent :
155
215
return 'The local and remote files are divergent.' ;
156
216
case RemoteFileStatus . StatusEnum . NoRemoteRepo :
157
- return 'There is no remote repository yet. Push your assignment to create it.'
217
+ return 'There is no remote repository yet. Push your assignment to create it.' ;
158
218
default :
159
219
return '' ;
160
220
}
@@ -170,23 +230,73 @@ export const Files = ({ lecture, assignment, onAssignmentChange }: IFilesProps)
170
230
171
231
const getStatusChip = ( status : RemoteFileStatus . StatusEnum ) => {
172
232
// Define the statusMap with allowed `Chip` color values
173
- const statusMap : Record < RemoteFileStatus . StatusEnum , { label : string , color : "default" | "primary" | "secondary" | "error" | "warning" | "info" | "success" , icon : JSX . Element } > = {
174
- UP_TO_DATE : { label : 'Up To Date' , color : 'success' , icon : < CheckIcon /> } ,
175
- PULL_NEEDED : { label : 'Pull Needed' , color : 'warning' , icon : < GetAppRoundedIcon /> } ,
176
- PUSH_NEEDED : { label : 'Push Needed' , color : 'warning' , icon : < PublishRoundedIcon /> } ,
177
- DIVERGENT : { label : 'Divergent' , color : 'error' , icon : < ErrorOutlineIcon /> } ,
178
- NO_REMOTE_REPO : { label : 'No Remote Repository' , color : 'primary' , icon : < CheckIcon /> }
233
+ const statusMap : Record <
234
+ RemoteFileStatus . StatusEnum ,
235
+ {
236
+ label : string ;
237
+ color :
238
+ | 'default'
239
+ | 'primary'
240
+ | 'secondary'
241
+ | 'error'
242
+ | 'warning'
243
+ | 'info'
244
+ | 'success' ;
245
+ icon : JSX . Element ;
246
+ }
247
+ > = {
248
+ UP_TO_DATE : {
249
+ label : 'Up To Date' ,
250
+ color : 'success' ,
251
+ icon : < CheckIcon />
252
+ } ,
253
+ PULL_NEEDED : {
254
+ label : 'Pull Needed' ,
255
+ color : 'warning' ,
256
+ icon : < GetAppRoundedIcon />
257
+ } ,
258
+ PUSH_NEEDED : {
259
+ label : 'Push Needed' ,
260
+ color : 'warning' ,
261
+ icon : < PublishRoundedIcon />
262
+ } ,
263
+ DIVERGENT : {
264
+ label : 'Divergent' ,
265
+ color : 'error' ,
266
+ icon : < ErrorOutlineIcon />
267
+ } ,
268
+ NO_REMOTE_REPO : {
269
+ label : 'No Remote Repository' ,
270
+ color : 'primary' ,
271
+ icon : < CheckIcon />
272
+ }
179
273
} ;
180
-
274
+
181
275
// Fallback if the status is not in the statusMap (it should be)
182
276
const { label, color, icon } = statusMap [ status ] || { } ;
183
-
277
+
184
278
// Return the Chip component with appropriate props or null if status is invalid
185
- return label ? < Chip sx = { { mb : 1 } } label = { label } color = { color } size = "small" icon = { icon } /> : null ;
279
+ return label ? (
280
+ < Chip
281
+ sx = { { mb : 1 } }
282
+ label = { label }
283
+ color = { color }
284
+ size = "small"
285
+ icon = { icon }
286
+ />
287
+ ) : null ;
186
288
} ;
187
-
289
+
188
290
return (
189
- < Card sx = { { overflowX : 'auto' , m : 3 , flex : 1 , display : 'flex' , flexDirection : 'column' } } >
291
+ < Card
292
+ sx = { {
293
+ overflowX : 'auto' ,
294
+ m : 3 ,
295
+ flex : 1 ,
296
+ display : 'flex' ,
297
+ flexDirection : 'column'
298
+ } }
299
+ >
190
300
< CardHeader
191
301
title = "Files"
192
302
titleTypographyProps = { { display : 'inline' } }
@@ -207,7 +317,11 @@ export const Files = ({ lecture, assignment, onAssignmentChange }: IFilesProps)
207
317
subheaderTypographyProps = { { display : 'inline' , ml : 2 } }
208
318
/>
209
319
< CardContent sx = { { overflow : 'auto' } } >
210
- < Tabs variant = "fullWidth" value = { selectedDir } onChange = { ( e , dir ) => handleSwitchDir ( dir ) } >
320
+ < Tabs
321
+ variant = "fullWidth"
322
+ value = { selectedDir }
323
+ onChange = { ( e , dir ) => handleSwitchDir ( dir ) }
324
+ >
211
325
< Tab label = "Source" value = "source" />
212
326
< Tab label = "Release" value = "release" />
213
327
</ Tabs >
@@ -221,16 +335,30 @@ export const Files = ({ lecture, assignment, onAssignmentChange }: IFilesProps)
221
335
</ Box >
222
336
</ CardContent >
223
337
< CardActions sx = { { marginTop : 'auto' } } >
224
- < CommitDialog handleCommit = { handlePushAssignment } lecture = { lecture } assignment = { assignment } >
338
+ < CommitDialog
339
+ handleCommit = { handlePushAssignment }
340
+ lecture = { lecture }
341
+ assignment = { assignment }
342
+ >
225
343
< Tooltip title = "Commit Changes" >
226
- < Button variant = "outlined" size = "small" color = "primary" >
344
+ < Button
345
+ variant = "outlined"
346
+ size = "small"
347
+ color = "primary"
348
+ sx = { { mt : - 1 } }
349
+ >
227
350
< PublishRoundedIcon fontSize = "small" sx = { { mr : 1 } } />
228
351
Push
229
352
</ Button >
230
353
</ Tooltip >
231
354
</ CommitDialog >
232
355
< Tooltip title = "Pull from Remote" >
233
- < Button variant = "outlined" size = "small" onClick = { handlePullAssignment } >
356
+ < Button
357
+ variant = "outlined"
358
+ size = "small"
359
+ onClick = { handlePullAssignment }
360
+ sx = { { mt : - 1 } }
361
+ >
234
362
< GetAppRoundedIcon fontSize = "small" sx = { { mr : 1 } } />
235
363
Pull
236
364
</ Button >
0 commit comments