@@ -12,6 +12,8 @@ import { V4TBuildItems } from "./V4TItem/V4TBuiltItems";
1212import { V4TItem } from "./V4TItem/V4TItem" ;
1313import { V4TItemType } from "./V4TItem/V4TItemType" ;
1414import { Validators } from "./Validators" ;
15+ import * as fs from "fs" ;
16+ import * as path from "path" ;
1517
1618/**
1719 * Tree view that lists extension's basic options like:
@@ -195,7 +197,6 @@ export class CoursesProvider implements vscode.TreeDataProvider<V4TItem> {
195197 // Only axios requests throw error
196198 APIClient . handleAxiosError ( error ) ;
197199 }
198-
199200 }
200201
201202 /**
@@ -227,7 +228,7 @@ export class CoursesProvider implements vscode.TreeDataProvider<V4TItem> {
227228 if ( item . item && instanceOfCourse ( item . item ) ) {
228229 try {
229230 const selectedOption = await vscode . window . showWarningMessage ( "Are you sure you want to delete " + item . item . name + "?" , { modal : true } , "Accept" ) ;
230- if ( ( selectedOption === "Accept" ) && CurrentUser . isLoggedIn ( ) && CurrentUser . getUserInfo ( ) . courses ) {
231+ if ( selectedOption === "Accept" && CurrentUser . isLoggedIn ( ) && CurrentUser . getUserInfo ( ) . courses ) {
231232 const response = await APIClient . deleteCourse ( item . item . id ) ;
232233 console . debug ( response ) ;
233234 await CurrentUser . updateUserInfo ( ) ;
@@ -283,17 +284,17 @@ export class CoursesProvider implements vscode.TreeDataProvider<V4TItem> {
283284 try {
284285 this . loading = true ;
285286 CoursesProvider . triggerTreeReload ( ) ;
286- const addExerciseData = await APIClient . addExercise ( course . id , { name } ) ;
287+ const addExerciseData = await APIClient . addExercises ( course . id , [ { name } ] ) ;
287288 console . debug ( addExerciseData ) ;
288289 try {
289290 // When exercise is createdupload template
290291 const zipContent = await FileZipUtil . getZipFromUris ( fileUris ) ;
291- const response = await APIClient . uploadExerciseTemplate ( addExerciseData . data . id , zipContent ) ;
292+ const response = await APIClient . uploadExerciseTemplate ( addExerciseData . data [ 0 ] . id , zipContent ) ;
292293 console . debug ( response ) ;
293294 } catch ( uploadError ) {
294295 try {
295296 // If upload fails delete the exercise and show error
296- const response = await APIClient . deleteExercise ( addExerciseData . data . id ) ;
297+ const response = await APIClient . deleteExercise ( addExerciseData . data [ 0 ] . id ) ;
297298 console . debug ( response ) ;
298299 APIClient . handleAxiosError ( uploadError ) ;
299300 } catch ( deleteError ) {
@@ -302,6 +303,7 @@ export class CoursesProvider implements vscode.TreeDataProvider<V4TItem> {
302303 } finally {
303304 this . loading = false ;
304305 CoursesProvider . triggerTreeReload ( ) ;
306+ vscode . window . showInformationMessage ( "Exercise added." ) ;
305307 }
306308 } catch ( error ) {
307309 APIClient . handleAxiosError ( error ) ;
@@ -311,6 +313,65 @@ export class CoursesProvider implements vscode.TreeDataProvider<V4TItem> {
311313 }
312314 }
313315
316+ /**
317+ * Prepare and send multiple exercises' creation request
318+ * @param item course
319+ */
320+ public async addMultipleExercises ( item : V4TItem ) {
321+ if ( item . item && instanceOfCourse ( item . item ) ) {
322+ const course = item . item ;
323+ // Explain user how to organize their exercises' directory
324+ vscode . window . showInformationMessage ( "To upload multiple exercises, prepare a directory with a folder for each exercise, each folder including the exercise's corresponding template. When ready, click 'Accept'." , "Accept" ) . then ( async ( ans ) => {
325+ if ( ans === "Accept" ) {
326+ // Ask user to select a directory
327+ // This directory has to contain exercises (1 folder = 1 new exercise)
328+ const parentDirectoryUri = await vscode . window . showOpenDialog ( {
329+ canSelectFiles : false ,
330+ canSelectFolders : true ,
331+ canSelectMany : false ,
332+ openLabel : "Select directory" ,
333+ } ) ;
334+ if ( parentDirectoryUri ) {
335+ const fsUri = parentDirectoryUri [ 0 ] . fsPath ;
336+ // Get every folder from a selected directory
337+ const exercisesDirectories = fs . readdirSync ( fsUri , { withFileTypes : true } ) . filter ( ( d ) => d . isDirectory ( ) ) ;
338+ // Get the number of directories
339+ const availableFolderNumber = exercisesDirectories . length ;
340+ // Prepare count of successfully uploaded exercises
341+ let uploadedExercises = 0 ;
342+ // Unsuccessful responses' control (true if there were any)
343+ let errorCaught = false ;
344+ if ( exercisesDirectories . length > 1 ) {
345+ // Exercises are uploaded in batches of 3 exercises
346+ while ( exercisesDirectories . length > 0 ) {
347+ const exercisesDirChunk = exercisesDirectories . splice ( 0 , 3 ) ;
348+ // Collect exercises' names from directories' names
349+ const exerciseData = await APIClient . addExercises (
350+ course . id ,
351+ exercisesDirChunk . map ( ( d ) => ( { name : d . name } ) )
352+ ) ;
353+
354+ // When added to DB, templates of each exercise are sent
355+ exerciseData . data . map ( async ( ex , index ) => {
356+ APIClient . uploadExerciseTemplate ( ex . id , await FileZipUtil . getZipFromUris ( [ vscode . Uri . parse ( fsUri + path . sep + exercisesDirChunk [ index ] . name ) ] ) , false )
357+ . then ( ( _ ) => uploadedExercises ++ )
358+ . catch ( ( _ ) => ( errorCaught = true ) ) ;
359+ } ) ;
360+ }
361+ if ( errorCaught || availableFolderNumber !== uploadedExercises ) {
362+ vscode . window . showInformationMessage ( "All exercises were successfully uploaded." ) ;
363+ } else {
364+ vscode . window . showErrorMessage ( "One or more exercises were not properly uploaded." ) ;
365+ }
366+ } else {
367+ vscode . window . showErrorMessage ( "No exercises have been uploaded since there were not any to upload in the selected folder." ) ;
368+ }
369+ }
370+ }
371+ } ) ;
372+ }
373+ }
374+
314375 /**
315376 * Show form for editing an exercise then call client.
316377 * @param item exercise
@@ -517,7 +578,7 @@ export class CoursesProvider implements vscode.TreeDataProvider<V4TItem> {
517578 * @param validator validator (check model/Validators.ts)
518579 * @param options available options for input box
519580 */
520- private async getInput ( prompt : string , validator : ( ( value : string ) => string | undefined | null | Thenable < string | undefined | null > ) , options ?: { value ?: string , password ?: boolean } ) {
581+ private async getInput ( prompt : string , validator : ( value : string ) => string | undefined | null | Thenable < string | undefined | null > , options ?: { value ?: string ; password ?: boolean } ) {
521582 let inputOptions : vscode . InputBoxOptions = { prompt } ;
522583 if ( options ) {
523584 if ( options . value ) {
0 commit comments