@@ -1290,3 +1290,170 @@ function initScoreboardSubmissions() {
1290
1290
} ) ;
1291
1291
} ) ;
1292
1292
}
1293
+
1294
+ const editors = [ ] ;
1295
+ function initDiffEditor ( editorId ) {
1296
+ const wrapper = $ ( `#${ editorId } -wrapper` ) ;
1297
+
1298
+ // TODO: store and restore tag preference in local storage.
1299
+ const initialSelect = "" ;
1300
+ const select = wrapper . find ( ".diff-select" ) ;
1301
+ select [ 0 ] . selectedIndex = 0 ;
1302
+
1303
+ const initialDiffMode = getDiffMode ( ) ;
1304
+ const radios = wrapper . find ( `.diff-mode > input[type='radio']` ) ;
1305
+ radios . each ( ( _ , radio ) => {
1306
+ radio . checked = radio . value === initialDiffMode
1307
+ } ) ;
1308
+
1309
+ const download = wrapper . find ( ".download" ) [ 0 ] ;
1310
+ const edit = wrapper . find ( ".edit" ) [ 0 ] ;
1311
+ const updateTabRank = ( rank ) => {
1312
+ if ( rank ) {
1313
+ let url = new URL ( download . href ) ;
1314
+ url . searchParams . set ( "fetch" , rank ) ;
1315
+ download . href = url ;
1316
+
1317
+ url = new URL ( edit . href ) ;
1318
+ url . searchParams . set ( "rank" , rank ) ;
1319
+ edit . href = url ;
1320
+ } else {
1321
+ download . href = "#" ;
1322
+ edit . href = "#" ;
1323
+ }
1324
+ } ;
1325
+ wrapper . find ( ".nav" ) . on ( 'show.bs.tab' , ( e ) => {
1326
+ updateTabRank ( e . target . dataset . rank ) ;
1327
+ } )
1328
+
1329
+ const editor = {
1330
+ 'getDiffMode' : ( ) => {
1331
+ for ( let radio of radios ) {
1332
+ if ( radio . checked ) {
1333
+ return radio . value ;
1334
+ }
1335
+ }
1336
+ } ,
1337
+ 'getDiffSelection' : ( ) => {
1338
+ let s = select [ 0 ] ;
1339
+ return s . options [ s . selectedIndex ] . value ;
1340
+ } ,
1341
+ 'updateIcon' : ( rank , icon ) => {
1342
+ const element = wrapper . find ( ".nav-link[data-rank]" ) [ rank ] . querySelector ( '.fa-fw' ) ;
1343
+ element . className = 'fas fa-fw fa-' + icon ;
1344
+ } ,
1345
+ 'onDiffModeChange' : ( f ) => {
1346
+ radios . change ( ( e ) => {
1347
+ const diffMode = e . target . value ;
1348
+ f ( diffMode ) ;
1349
+ } ) ;
1350
+ } ,
1351
+ 'onDiffSelectChange' : ( f ) => {
1352
+ select . change ( ( e ) => {
1353
+ const submitId = e . target . value ;
1354
+ const noDiff = submitId === "" ;
1355
+ f ( submitId , noDiff ) ;
1356
+ } ) ;
1357
+ }
1358
+ } ;
1359
+ editors [ editorId ] = editor ;
1360
+
1361
+ const updateMode = ( diffMode ) => {
1362
+ setDiffMode ( diffMode ) ;
1363
+ } ;
1364
+ updateMode ( initialDiffMode ) ;
1365
+ editor . onDiffModeChange ( updateMode ) ;
1366
+
1367
+ const updateSelect = ( submitId , noDiff ) => {
1368
+ radios . each ( ( _ , radio ) => {
1369
+ radio . disabled = noDiff ;
1370
+ } ) ;
1371
+ // TODO: add tab panes for deleted source files.
1372
+ } ;
1373
+ updateSelect ( "" , true ) ;
1374
+ editor . onDiffSelectChange ( updateSelect ) ;
1375
+ }
1376
+
1377
+ function initDiffEditorTab ( editorId , diffId , rank , models , modifiedModel ) {
1378
+ const empty = monaco . editor . getModel ( monaco . Uri . file ( "empty" ) ) ?? monaco . editor . createModel ( "" , undefined , monaco . Uri . file ( "empty" ) ) ;
1379
+
1380
+ const diffEditor = monaco . editor . createDiffEditor (
1381
+ document . getElementById ( diffId ) , {
1382
+ scrollbar : {
1383
+ alwaysConsumeMouseWheel : false ,
1384
+ vertical : 'auto' ,
1385
+ horizontal : 'auto'
1386
+ } ,
1387
+ scrollBeyondLastLine : false ,
1388
+ automaticLayout : true ,
1389
+ readOnly : true ,
1390
+ theme : getCurrentEditorTheme ( ) ,
1391
+ } ) ;
1392
+
1393
+ const updateSelect = ( submitId , noDiff ) => {
1394
+ if ( ! noDiff ) {
1395
+ const model = models [ submitId ] ;
1396
+ if ( model === undefined ) {
1397
+ models [ submitId ] = { 'model' : empty } ;
1398
+ } else if ( model !== undefined && ! model [ 'model' ] ) {
1399
+ // TODO: show source code instead of diff to empty file?
1400
+ model [ 'model' ] = monaco . editor . createModel ( model [ 'source' ] , undefined , monaco . Uri . file ( "test/" + submitId + "/" + model [ 'filename' ] ) ) ;
1401
+ }
1402
+ }
1403
+
1404
+ diffEditor . updateOptions ( {
1405
+ renderOverviewRuler : ! noDiff ,
1406
+ } ) ;
1407
+ if ( noDiff ) {
1408
+ diffEditor . updateOptions ( {
1409
+ renderSideBySide : false ,
1410
+ } ) ;
1411
+ } else {
1412
+ // Reset the diff mode to the currently selected mode.
1413
+ updateMode ( editors [ editorId ] . getDiffMode ( ) )
1414
+ }
1415
+ // TODO: handle single-file submission case with renamed file.
1416
+ const oldViewState = diffEditor . saveViewState ( ) ;
1417
+ diffEditor . setModel ( {
1418
+ original : noDiff ? modifiedModel : models [ submitId ] [ 'model' ] ,
1419
+ modified : modifiedModel ,
1420
+ } ) ;
1421
+ diffEditor . restoreViewState ( oldViewState ) ;
1422
+
1423
+ diffEditor . getOriginalEditor ( ) . updateOptions ( {
1424
+ lineNumbers : ! noDiff ,
1425
+ } ) ;
1426
+ diffEditor . getModifiedEditor ( ) . updateOptions ( {
1427
+ minimap : {
1428
+ enabled : noDiff ,
1429
+ } ,
1430
+ } )
1431
+ } ;
1432
+ editors [ editorId ] . onDiffSelectChange ( updateSelect ) ;
1433
+ updateSelect ( "" , true ) ;
1434
+
1435
+ const updateIcon = ( ) => {
1436
+ const noDiff = editors [ editorId ] . getDiffSelection ( ) === "" ;
1437
+ if ( noDiff ) {
1438
+ editors [ editorId ] . updateIcon ( rank , 'file' ) ;
1439
+ return ;
1440
+ }
1441
+
1442
+ const lineChanges = diffEditor . getLineChanges ( ) ;
1443
+ if ( diffEditor . getModel ( ) . original == empty ) {
1444
+ editors [ editorId ] . updateIcon ( rank , 'file-circle-plus' ) ;
1445
+ } else if ( lineChanges !== null && lineChanges . length > 0 ) {
1446
+ editors [ editorId ] . updateIcon ( rank , 'file-circle-exclamation' ) ;
1447
+ } else {
1448
+ editors [ editorId ] . updateIcon ( rank , 'file-circle-check' ) ;
1449
+ }
1450
+ }
1451
+ diffEditor . onDidUpdateDiff ( updateIcon ) ;
1452
+
1453
+ const updateMode = ( diffMode ) => {
1454
+ diffEditor . updateOptions ( {
1455
+ renderSideBySide : diffMode === 'side-by-side' ,
1456
+ } ) ;
1457
+ } ;
1458
+ editors [ editorId ] . onDiffModeChange ( updateMode ) ;
1459
+ }
0 commit comments