@@ -1290,3 +1290,170 @@ function initScoreboardSubmissions() {
12901290 } ) ;
12911291 } ) ;
12921292}
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