From 44f548c388ba609e680f372e60b8ec793d157588 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Thu, 13 Jul 2023 13:16:00 +0530 Subject: [PATCH 01/39] Issue #42 feat: Added an interface for match the following type question --- .../src/lib/interfaces/MtfForm.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts diff --git a/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts b/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts new file mode 100644 index 000000000..489d3f99e --- /dev/null +++ b/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts @@ -0,0 +1,56 @@ +import * as _ from "lodash-es"; + +export class MtfOption { + constructor(public leftOption: string, public rightOption: string) {} +} + +export interface MtfData { + question: string; + options: Array; + answer?: object; + learningOutcome?: string; + complexityLevel?: string; + maxScore?: number; +} + +export interface MtfConfig { + templateId?: string; + numberOfOptions?: number; + maximumOptions?: number; + +} + +export class MtfForm { + public question: string; + public options: Array; + public templateId: string; + public answer: object; + public learningOutcome?: string; + public complexityLevel?: string; + public maxScore?: number; + public maximumOptions; + public numberOfOptions; + + constructor({question, options, answer, learningOutcome, complexityLevel, maxScore,}: MtfData,{ templateId, numberOfOptions, maximumOptions }: MtfConfig) { + this.question = question; + this.options = options || []; + this.templateId = templateId; + this.answer = answer || {}; + this.learningOutcome = learningOutcome; + this.complexityLevel = complexityLevel; + this.numberOfOptions = numberOfOptions || 2; + this.maximumOptions = maximumOptions || 4; + this.maxScore = maxScore; + if (!this.options || !this.options.length) { + _.times(this.numberOfOptions, index => this.options.push(new MtfOption("",""))); + } + } + + addOptions() { + this.options.push(new MtfOption("", "")); + } + + deleteOptions(position: number) { + this.options.splice(position, 1); + } +} From cefe2a612bf9589b72fc40514ce1413e6412240f Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Fri, 14 Jul 2023 11:21:13 +0530 Subject: [PATCH 02/39] Used optional chaining --- .../questionset-editor-library/src/lib/interfaces/MtfForm.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts b/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts index 489d3f99e..5b2aab549 100644 --- a/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts +++ b/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts @@ -41,7 +41,7 @@ export class MtfForm { this.numberOfOptions = numberOfOptions || 2; this.maximumOptions = maximumOptions || 4; this.maxScore = maxScore; - if (!this.options || !this.options.length) { + if (!this.options?.length) { _.times(this.numberOfOptions, index => this.options.push(new MtfOption("",""))); } } From 0cc5c5038f894afff83e73a644da2b7db806688a Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Sat, 15 Jul 2023 12:24:41 +0530 Subject: [PATCH 03/39] updated answer type to string as per schema --- .../src/lib/interfaces/MtfForm.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts b/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts index 5b2aab549..9634569bc 100644 --- a/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts +++ b/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts @@ -7,7 +7,7 @@ export class MtfOption { export interface MtfData { question: string; options: Array; - answer?: object; + answer?: string; learningOutcome?: string; complexityLevel?: string; maxScore?: number; @@ -24,7 +24,7 @@ export class MtfForm { public question: string; public options: Array; public templateId: string; - public answer: object; + public answer: string; public learningOutcome?: string; public complexityLevel?: string; public maxScore?: number; @@ -35,19 +35,19 @@ export class MtfForm { this.question = question; this.options = options || []; this.templateId = templateId; - this.answer = answer || {}; + this.answer = answer; this.learningOutcome = learningOutcome; this.complexityLevel = complexityLevel; this.numberOfOptions = numberOfOptions || 2; this.maximumOptions = maximumOptions || 4; this.maxScore = maxScore; if (!this.options?.length) { - _.times(this.numberOfOptions, index => this.options.push(new MtfOption("",""))); + _.times(this.numberOfOptions, index => this.options.push(new MtfOption('', ''))); } } addOptions() { - this.options.push(new MtfOption("", "")); + this.options.push(new MtfOption('', '')); } deleteOptions(position: number) { From 803e8769f977ab27fd6dbb251b49f0e09b4dbc99 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Fri, 21 Jul 2023 09:38:22 +0530 Subject: [PATCH 04/39] Add Match The Following Question in question primary categories --- .../src/lib/services/config/editor.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/questionset-editor-library/src/lib/services/config/editor.config.json b/projects/questionset-editor-library/src/lib/services/config/editor.config.json index 195ea0ad1..20990e723 100644 --- a/projects/questionset-editor-library/src/lib/services/config/editor.config.json +++ b/projects/questionset-editor-library/src/lib/services/config/editor.config.json @@ -17,7 +17,7 @@ "accepted": "mp4, webm" } }, - "questionPrimaryCategories": ["Multiple Choice Question", "Subjective Question"], + "questionPrimaryCategories": ["Multiple Choice Question", "Subjective Question", "Match The Following Question"], "contentPrimaryCategories": ["Course Assessment", "eTextbook", "Explanation Content", "Learning Resource", "Practice Question Set"], "readQuestionFields": "body,primaryCategory,mimeType,qType,answer,templateId,responseDeclaration,interactionTypes,interactions,name,solutions,editorState,media,remarks,evidence,hints,instructions,outcomeDeclaration,", "omitFalseyProperties":["topic", "topicsIds", "targetTopicIds", "keywords"], From fff10952ce0566291e433db3b6d8121c2376392c Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Fri, 21 Jul 2023 11:21:08 +0530 Subject: [PATCH 05/39] Added Match The Following Question --- src/app/data.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/data.ts b/src/app/data.ts index e57fe1055..ecf3a4dac 100644 --- a/src/app/data.ts +++ b/src/app/data.ts @@ -135,7 +135,8 @@ export const questionSetEditorConfig = { children: { Question: [ 'Multiple Choice Question', - 'Subjective Question' + 'Subjective Question', + 'Match The Following Question', ] }, addFromLibrary: false, From f93a37331c43f2b4589234b1a428cd0fe686c3b4 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Fri, 21 Jul 2023 21:08:31 +0530 Subject: [PATCH 06/39] Issue #42 feat: Function for preparing Match the following body --- .../components/options/options.component.ts | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/options/options.component.ts b/projects/questionset-editor-library/src/lib/components/options/options.component.ts index daf368fc5..c2a058124 100644 --- a/projects/questionset-editor-library/src/lib/components/options/options.component.ts +++ b/projects/questionset-editor-library/src/lib/components/options/options.component.ts @@ -15,6 +15,7 @@ export class OptionsComponent implements OnInit, OnChanges { @Input() showFormError; @Input() sourcingSettings; @Input() questionPrimaryCategory; + @Input() questionInteractionType; @Input() mapping = []; @Input() isReadOnlyMode; @Input() maxScore; @@ -75,8 +76,16 @@ export class OptionsComponent implements OnInit, OnChanges { } editorDataHandler(event?) { - const body = this.prepareMcqBody(this.editorState); - this.editorDataOutput.emit({ body, mediaobj: event ? event.mediaobj : undefined }); + let body: any; + if (this.questionInteractionType === 'choice') { + body = this.prepareMcqBody(this.editorState); + } else if (this.questionInteractionType === 'match') { + body = this.prepareMtfBody(this.editorState); + } + this.editorDataOutput.emit({ + body, + mediaobj: event ? event.mediaobj : undefined, + }); } prepareMcqBody(editorState) { @@ -119,11 +128,40 @@ export class OptionsComponent implements OnInit, OnChanges { return metadata; } + prepareMtfBody(editorState) { + let metadata: any; + const correctAnswer = editorState.answer; + let options: any; + if (!_.isEmpty(correctAnswer)) { + options = _.reduce(this.editorState.options,function (acc, obj) { + acc.leftOption.push(obj.leftOption); + acc.rightOption.push(obj.rightOption); + return acc; + },{ leftOption: [], rightOption: [] } + ); + } + console.log(options); + console.log(editorState.answer); + metadata = { + templateId: this.templateType, + name: this.questionPrimaryCategory || 'Match The Following Question', + responseDeclaration: this.getResponseDeclaration(editorState), + outcomeDeclaration: this.getOutcomeDeclaration(), + interactionTypes: ['match'], + interactions: this.getInteractions(editorState.options), + editorState: { + options, + }, + qType: 'MTF', + primaryCategory: this.questionPrimaryCategory || "Match The Following Question", + }; + return metadata; + } getResponseDeclaration(editorState) { const responseDeclaration = { response1: { cardinality: this.getCardinality(), - type: 'integer', + type: this.questionInteractionType === 'choice' ? 'integer' : 'map', correctResponse: { value: editorState.answer, }, From fd99b99bf434ce8253cea0c4bd406d4714810b54 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Fri, 21 Jul 2023 21:22:43 +0530 Subject: [PATCH 07/39] Added some button labels --- .../src/lib/services/config/label.config.json | 376 +++++++++--------- 1 file changed, 190 insertions(+), 186 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/services/config/label.config.json b/projects/questionset-editor-library/src/lib/services/config/label.config.json index 29e13cd2d..6ecd46581 100644 --- a/projects/questionset-editor-library/src/lib/services/config/label.config.json +++ b/projects/questionset-editor-library/src/lib/services/config/label.config.json @@ -50,192 +50,196 @@ "add_page_numbers_to_questions_btn_label": "Pagination" }, "lbl":{ - "search":"Search", - "subject":"Subject", - "medium":"Medium", - "gradeLevel":"Class", - "contentType":"Content Type", - "reset":"Reset", - "apply":"apply", - "filterText":"Change Filters", - "Questiondetails":"Question details", - "selectContent":"Select content", - "noMatchingContent":"Sorry there is no matching content", - "changeFilterMessage":"Changing filter helps you find more content", - "changeFilter":"Change filters", - "whereDoYouWantToAddThisContent":"Where do you want to add this content?", - "selectContentToAdd":"Use search and filters above to find more content", - "addedToCollection":"Added to collection", - "changingFilters":"Changing filters make you find more content", - "ChangeFilters":"Change filters", - "addFromLibrary":"Add from Library", - "showContentAddedToCollection":"Show content added to collection", - "addContent":"Add content", - "sortBy":"Sort By", - "sortlabel":"A - Z", - "viewOnOrigin":"View Content on consumption", - "answers":"Answers", - "answersRequired":"Answer is required", - "answersPopupText":"Please provide an answer for the question. Check preview to understand how it would look.", - "selectImage":"Select Image", - "myImages":"My Images", - "allImage":"All Image", - "uploadAndUse":"Upload and Use", - "chooseOrDragImage":"Choose or drag and drop your image here", - "chooseOrDragVideo":"Choose or drag and drop your video here", - "uploadFromComputer":"Upload from Computer", - "upload":"Upload", - "maxFileSize":"Max File size:", - "allowedFileTypes":"Allowed file types are:", - "maximumAllowedFileSize":"Maximum allowed file size:", - "copyRightsAndLicense":"Copyright & License", - "dropChooseFile":"Drop or choose file to upload before entering the details", - "charactersLeft":"Characters left:", - "myVideos":"My Video(s)", - "allVideos":"All Video(s)", - "selectVideo":"Select Video", - "searchPlaceholder":"Search...", - "addAnImage":"Add an image", - "name":"Name", - "copyRightsAndYear":"Copyright & Yrar", - "license":"License", - "author":"Author", - "grade":"Grade", - "board":"Board", - "audience":"Audience", - "copyRight":"Copyright", - "licensedBy":"Licensed by", - "attributions":"Attributions", - "requestForQrCode":"Request for QR Codes", - "confirmDeleteContent":"Confirm Delete Content", - "confirmDeleteNode":"Are you sure want to delete the selected Node?", - "comments":"Comments", - "reviewComments":"Review Comments", - "questionSetPreview":"Question Set Preview", - "numberToolarge": "This number is too large for the request", - "acceptTerms":"Accepting Terms & Conditions", - "iAgreeSubmit":"I agree that by submitting / publishing this Content,", - "iconfirmContent":"I confirm that this Content complies with prescribed guidelines, including the Terms of Use and Content Policy and that I consent to publish it under the", - "createCommonFramework":"Creative Commons Framework in", - "accordance":"accordance with the", - "contentPolicy":"Content Policy.", - "privacyRights":"I have made sure that I do not violate others’ copyright or privacy rights." , - "viewComments":"View Comments", - "addReviewComments":"Add Review Comments", - "enterYourComments":"Enter your comments", - "publishCollection":"Publish ${objectType}", - "confirmPublishCollection":"Are you sure you want to publish this ${objectType}?", - "fillComments":"Fill comments", - "searchLibrary":"Search Library", - "options":"Options", - "optionsPopupText":"Please Preview to check how layout looks. Layout is responsive to the resolution of your device", - "selectOneAns":"Select one correct answer", - "fillThisOption":"Fill this option", - "reduceSize":"Please reduce the size", - "correctAns":"Correct answer", - "addOption":"Add option", - "question":"Question", - "pageNumber":"Page No", - "confirmQuestionNotSaved":"This question will not be saved, are you sure you want to go back to questionset?", - "video":"Video", - "textImage":"Text+Image", - "chooseType":"Choose type", - "solution":"Solution", - "optional":"(Optional)", - "questionRequired":"Question is required", - "addingTo":"Adding To", - "selectTemplate":"Select a template to get started", - "createNew":"Create new", - "selectLayout":"Select Layout", - "vertical":"Vertical", - "grid":"Grid", - "horizontal":"Horizontal", - "folders":"Folders", - "createHierarchyCsv":"Create folders using csv file", - "downloadFoldersInCSV":"Download folders as csv file", - "uploadUpdateCSV":"Update folder metadata using csv file", - "downloadSampleHierarchyCSv":"Upload csv file as per the given sample to create folders", - "makeSureFile":"Make sure that:", - "allColumnsAreAvailable":"The file has all the required columns", - "hasAllMandatoryColumn":"The file has all the required values filled in as per the format", - "noDuplicateRow":"There are no duplicate rows (with exactly same folder levels) in the file", - "downloadSampleCSV":"Download sample csv file", - "dragAndDropCSV":"Drag and Drop files to upload", - "selectFileToUpload":"Select file to upload", - "uploadEntries":"File accepted - csv", - "Or":"Or", - "pleaseWait":"Please wait", - "validateCsvFile":"Validating CSV file", - "hierarchyValidationError":"Error in processing the file", - "followingErrors":"Following errors are found in the file. Please correct and upload again", - "reUploadCSV":"Upload file again", - "hierarchyValidation":"Hierarchy Validation", - "hierarchyAdded":"Folders have been successfully created. Please close the dialog", - "hierarchyUpdated":"Folder metadata has been successfully updated. Please close the dialog", - "successful":"Successful !", - "csvDownloadInstruction":"Please make sure that this is the file downloaded using the “Download folders as csv file” option and changes are made to it", - "collaborators": "Collaborators", - "addCollaborators": "Add Collaborators", - "manageCollaborators": "Manage Collaborators", - "sliderValue":"Slider Value", - "left":"Left", - "stepSize":"Step size", - "right":"Right", - "translation":"Translation", - "publishchecklistTitle": "Please confirm that ALL the following items are verified (by ticking the check-boxes) before you can publish:", - "bulkUploadErrorMessage" : "The metadata file has following errors. Please check and upload again", - "bulkUploadQuestion": "Bulk Upload Question", - "lastUploaded": "Last uploaded", - "bulkInProgress": "Bulk upload in progress", - "viewDetails": "View Details", - "downloadSampleMetadataCsvFileAndCreate": "Download sample metadata CSV file and create your own metadata file using the format", - "makeSureYourFile": "Make sure your file", - "allColumnsAreAvailableShownFormat": "All columns are available as shown in format.", - "hasAllMandatoryColumnsFilledAsMarkedInFormat": "Has all mandatory columns filled, as marked in the format", - "hasNoDuplicateUrlsFilepathColumn": "Has no duplicate URLs in the filepath column", - "downloadSampleMetadataCsv": "Download sample metadata CSV", - "processingDroppedFiles" : "Processing dropped files...", - "retry": "retry", - "dragAndDrop": "Drag and Drop file", - "or" : "or", - "selectFile": "Select file", - "uploadCSVXlEntries": "Upload csv upto 300 entries", - "no": "No", - "yes": "Yes", - "cancel": "Cancel", - "ok": "OK", - "validatingCSVFile": "Validating CSV file", - "metadataFileValidationFailed": "Metadata file validation failed.", - "metadataFollowingError": "The metadata file has following errors. Please check and upload again", - "uploadingYourContentFromCSV": "Uploading your question from CSV", - "uploadFail" : "Upload failed", - "uploadSuccessful" : "Upload successful", - "uploadRemaining" : "Upload remaining", - "bulkUploadComplete" : "Bulk upload complete!", - "contentUploaded" : "Question uploaded", - "downloadReport": "Download report", - "next": "Next", - "back": "Back", - "close": "Close", - "bulkUploadNoticeLine1": "Uploading in bulk may take some time, click on", - "bulkUploadNoticeLine2": "to continue using Vidyadaan.", - "bulkUploadNoticeLine3": "Your upload will keep running in the background.", - "alreadyContentPresent": "Already present in this folder", - "loaderHeading": "Please wait", - "loaderMessage": "We are fetching details.", - "slidervalue": "Set the minimum and maximum values with which the slider would start and end respectively.", - "stepSizeInfo": "The step size would define the gap/jump between two values on the slider.", - "minSizeInfo": "The minimum slider value", - "maxSizeInfo": "The maximum slider value", - "questionsetAddFromLibraryItemLabel": "question", - "questionsetAddFromLibraryCollectionLabel": "question set", - "marks": "Marks", - "shuffleOnMessage": "Each question will carry equal weightage of 1 mark when using Shuffle. To provide different weightage to individual questions please turn off Shuffle.", - "editingConsentNote": "I confirm that I am allowing my reviewer to make edits to the content and metadata of the content. I consent my reviewer make changes, if any and publish the contain thereafter.", - "acceptBothConsentNote": "Agree to both conditions", - "totalScore": "Total Score", - "qualityReview": "If the score is less than or equal to 15 please re-evaluate the question and send back for corrections." - }, + "search": "Search", + "subject": "Subject", + "medium": "Medium", + "gradeLevel": "Class", + "contentType": "Content Type", + "reset": "Reset", + "apply": "apply", + "filterText": "Change Filters", + "Questiondetails": "Question details", + "selectContent": "Select content", + "noMatchingContent": "Sorry there is no matching content", + "changeFilterMessage": "Changing filter helps you find more content", + "changeFilter": "Change filters", + "whereDoYouWantToAddThisContent": "Where do you want to add this content?", + "selectContentToAdd": "Use search and filters above to find more content", + "addedToCollection": "Added to collection", + "changingFilters": "Changing filters make you find more content", + "ChangeFilters": "Change filters", + "addFromLibrary": "Add from Library", + "showContentAddedToCollection": "Show content added to collection", + "addContent": "Add content", + "sortBy": "Sort By", + "sortlabel": "A - Z", + "viewOnOrigin": "View Content on consumption", + "answers": "Answers", + "answersRequired": "Answer is required", + "answersPopupText": "Please provide an answer for the question. Check preview to understand how it would look.", + "selectImage": "Select Image", + "myImages": "My Images", + "allImage": "All Image", + "uploadAndUse": "Upload and Use", + "chooseOrDragImage": "Choose or drag and drop your image here", + "chooseOrDragVideo": "Choose or drag and drop your video here", + "uploadFromComputer": "Upload from Computer", + "upload": "Upload", + "maxFileSize": "Max File size:", + "allowedFileTypes": "Allowed file types are:", + "maximumAllowedFileSize": "Maximum allowed file size:", + "copyRightsAndLicense": "Copyright & License", + "dropChooseFile": "Drop or choose file to upload before entering the details", + "charactersLeft": "Characters left:", + "myVideos": "My Video(s)", + "allVideos": "All Video(s)", + "selectVideo": "Select Video", + "searchPlaceholder": "Search...", + "addAnImage": "Add an image", + "name": "Name", + "copyRightsAndYear": "Copyright & Yrar", + "license": "License", + "author": "Author", + "grade": "Grade", + "board": "Board", + "audience": "Audience", + "copyRight": "Copyright", + "licensedBy": "Licensed by", + "attributions": "Attributions", + "requestForQrCode": "Request for QR Codes", + "confirmDeleteContent": "Confirm Delete Content", + "confirmDeleteNode": "Are you sure want to delete the selected Node?", + "comments": "Comments", + "reviewComments": "Review Comments", + "questionSetPreview": "Question Set Preview", + "numberToolarge": "This number is too large for the request", + "acceptTerms": "Accepting Terms & Conditions", + "iAgreeSubmit": "I agree that by submitting / publishing this Content,", + "iconfirmContent": "I confirm that this Content complies with prescribed guidelines, including the Terms of Use and Content Policy and that I consent to publish it under the", + "createCommonFramework": "Creative Commons Framework in", + "accordance": "accordance with the", + "contentPolicy": "Content Policy.", + "privacyRights": "I have made sure that I do not violate others’ copyright or privacy rights.", + "viewComments": "View Comments", + "addReviewComments": "Add Review Comments", + "enterYourComments": "Enter your comments", + "publishCollection": "Publish ${objectType}", + "confirmPublishCollection": "Are you sure you want to publish this ${objectType}?", + "fillComments": "Fill comments", + "searchLibrary": "Search Library", + "options": "Options", + "optionsPopupText": "Please Preview to check how layout looks. Layout is responsive to the resolution of your device", + "selectOneAns": "Select one correct answer", + "fillThisOption": "Fill this option", + "reduceSize": "Please reduce the size", + "correctAns": "Correct answer", + "correctMatch": "Check if all match pairs are correct", + "addOption": "Add option", + "addPair": "Add pair", + "question": "Question", + "pageNumber": "Page No", + "setAnswers": "Set your answers", + "addQuestionAnswerPairText": "Add question-answer pairs to your question. Answers will be shuffled automatically", + "confirmQuestionNotSaved": "This question will not be saved, are you sure you want to go back to questionset?", + "video": "Video", + "textImage": "Text+Image", + "chooseType": "Choose type", + "solution": "Solution", + "optional": "(Optional)", + "questionRequired": "Question is required", + "addingTo": "Adding To", + "selectTemplate": "Select a template to get started", + "createNew": "Create new", + "selectLayout": "Select Layout", + "vertical": "Vertical", + "grid": "Grid", + "horizontal": "Horizontal", + "folders": "Folders", + "createHierarchyCsv": "Create folders using csv file", + "downloadFoldersInCSV": "Download folders as csv file", + "uploadUpdateCSV": "Update folder metadata using csv file", + "downloadSampleHierarchyCSv": "Upload csv file as per the given sample to create folders", + "makeSureFile": "Make sure that:", + "allColumnsAreAvailable": "The file has all the required columns", + "hasAllMandatoryColumn": "The file has all the required values filled in as per the format", + "noDuplicateRow": "There are no duplicate rows (with exactly same folder levels) in the file", + "downloadSampleCSV": "Download sample csv file", + "dragAndDropCSV": "Drag and Drop files to upload", + "selectFileToUpload": "Select file to upload", + "uploadEntries": "File accepted - csv", + "Or": "Or", + "pleaseWait": "Please wait", + "validateCsvFile": "Validating CSV file", + "hierarchyValidationError": "Error in processing the file", + "followingErrors": "Following errors are found in the file. Please correct and upload again", + "reUploadCSV": "Upload file again", + "hierarchyValidation": "Hierarchy Validation", + "hierarchyAdded": "Folders have been successfully created. Please close the dialog", + "hierarchyUpdated": "Folder metadata has been successfully updated. Please close the dialog", + "successful": "Successful !", + "csvDownloadInstruction": "Please make sure that this is the file downloaded using the “Download folders as csv file” option and changes are made to it", + "collaborators": "Collaborators", + "addCollaborators": "Add Collaborators", + "manageCollaborators": "Manage Collaborators", + "sliderValue": "Slider Value", + "left": "Left", + "stepSize": "Step size", + "right": "Right", + "translation": "Translation", + "publishchecklistTitle": "Please confirm that ALL the following items are verified (by ticking the check-boxes) before you can publish:", + "bulkUploadErrorMessage": "The metadata file has following errors. Please check and upload again", + "bulkUploadQuestion": "Bulk Upload Question", + "lastUploaded": "Last uploaded", + "bulkInProgress": "Bulk upload in progress", + "viewDetails": "View Details", + "downloadSampleMetadataCsvFileAndCreate": "Download sample metadata CSV file and create your own metadata file using the format", + "makeSureYourFile": "Make sure your file", + "allColumnsAreAvailableShownFormat": "All columns are available as shown in format.", + "hasAllMandatoryColumnsFilledAsMarkedInFormat": "Has all mandatory columns filled, as marked in the format", + "hasNoDuplicateUrlsFilepathColumn": "Has no duplicate URLs in the filepath column", + "downloadSampleMetadataCsv": "Download sample metadata CSV", + "processingDroppedFiles": "Processing dropped files...", + "retry": "retry", + "dragAndDrop": "Drag and Drop file", + "or": "or", + "selectFile": "Select file", + "uploadCSVXlEntries": "Upload csv upto 300 entries", + "no": "No", + "yes": "Yes", + "cancel": "Cancel", + "ok": "OK", + "validatingCSVFile": "Validating CSV file", + "metadataFileValidationFailed": "Metadata file validation failed.", + "metadataFollowingError": "The metadata file has following errors. Please check and upload again", + "uploadingYourContentFromCSV": "Uploading your question from CSV", + "uploadFail": "Upload failed", + "uploadSuccessful": "Upload successful", + "uploadRemaining": "Upload remaining", + "bulkUploadComplete": "Bulk upload complete!", + "contentUploaded": "Question uploaded", + "downloadReport": "Download report", + "next": "Next", + "back": "Back", + "close": "Close", + "bulkUploadNoticeLine1": "Uploading in bulk may take some time, click on", + "bulkUploadNoticeLine2": "to continue using Vidyadaan.", + "bulkUploadNoticeLine3": "Your upload will keep running in the background.", + "alreadyContentPresent": "Already present in this folder", + "loaderHeading": "Please wait", + "loaderMessage": "We are fetching details.", + "slidervalue": "Set the minimum and maximum values with which the slider would start and end respectively.", + "stepSizeInfo": "The step size would define the gap/jump between two values on the slider.", + "minSizeInfo": "The minimum slider value", + "maxSizeInfo": "The maximum slider value", + "questionsetAddFromLibraryItemLabel": "question", + "questionsetAddFromLibraryCollectionLabel": "question set", + "marks": "Marks", + "shuffleOnMessage": "Each question will carry equal weightage of 1 mark when using Shuffle. To provide different weightage to individual questions please turn off Shuffle.", + "editingConsentNote": "I confirm that I am allowing my reviewer to make edits to the content and metadata of the content. I consent my reviewer make changes, if any and publish the contain thereafter.", + "acceptBothConsentNote": "Agree to both conditions", + "totalScore": "Total Score", + "qualityReview": "If the score is less than or equal to 15 please re-evaluate the question and send back for corrections." + }, "err":{ "somethingWentWrong":"Something went wrong", "contentNotFoundonOrigin": "The content not found in consumption", From c0e88fd401f4e1b0ce118435175ed78bec4d3d10 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Sat, 22 Jul 2023 20:45:46 +0530 Subject: [PATCH 08/39] Issue #42 feat: Added support if questionInteraction type is match --- .../components/question/question.component.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.ts index cb1e50d5d..56103a3c5 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.ts @@ -2,6 +2,7 @@ import { Component, EventEmitter, Input, OnInit, Output, AfterViewInit, ViewEnca import * as _ from 'lodash-es'; import { v4 as uuidv4 } from 'uuid'; import { McqForm } from '../../interfaces/McqForm'; +import { MtfForm } from '../../interfaces/MtfForm'; import { ServerResponse } from '../../interfaces/serverResponse'; import { QuestionService } from '../../services/question/question.service'; import { PlayerService } from '../../services/player/player.service'; @@ -243,6 +244,31 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { this.editorState.responseDeclaration = _.get(this.questionMetaData, 'responseDeclaration'); } } + + if (this.questionInteractionType === 'match') { + const responseDeclaration = this.questionMetaData.responseDeclaration; + this.scoreMapping = _.get(responseDeclaration, 'response1.mapping'); + const templateId = this.questionMetaData.templateId; + const numberOfOptions = this.questionMetaData?.editorState?.options?.length || 0; + const maximumOptions = _.get(this.questionInput, 'config.maximumOptions'); + this.editorService.optionsLength = numberOfOptions; + // converting the options to the format required by the editor + const options = _.map(this.questionMetaData?.editorState?.options?.leftOption, (leftOption, index) => ({ + leftOption, + rightOption:this.questionMetaData?.editorState?.options?.rightOption?.[index] + })); + const question = this.questionMetaData?.editorState?.question; + const interactions = this.questionMetaData?.interactions; + this.editorState = new MtfForm({ + question, options, answer: _.get(responseDeclaration, 'response1.correctResponse.value') + }, { templateId, numberOfOptions, maximumOptions }); + this.editorState.solutions = this.questionMetaData?.editorState?.solutions; + this.editorState.interactions = interactions; + if (_.has(this.questionMetaData, 'responseDeclaration')) { + this.editorState.responseDeclaration = _.get(this.questionMetaData, 'responseDeclaration'); + } + } + if (_.has(this.questionMetaData, 'primaryCategory')) { this.editorState.primaryCategory = _.get(this.questionMetaData, 'primaryCategory'); } @@ -299,6 +325,9 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { else if (this.questionInteractionType === 'choice') { this.editorState = new McqForm({ question: '', options: [] }, { numberOfOptions: _.get(this.questionInput, 'config.numberOfOptions'), maximumOptions: _.get(this.questionInput, 'config.maximumOptions') }); } + else if (this.questionInteractionType === 'match') { + this.editorState = new MtfForm({ question: '', options: [] }, { numberOfOptions: _.get(this.questionInput, 'config.numberOfOptions'), maximumOptions: _.get(this.questionInput, 'config.maximumOptions') }); + } /** for observation and survey to show hint,tip,dependent question option. */ if(!_.isUndefined(this.editorService?.editorConfig?.config?.renderTaxonomy)){ this.subMenuConfig(); From ea38850c4a78abb752c2bb1519846157ec4ee73c Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Mon, 24 Jul 2023 14:51:54 +0530 Subject: [PATCH 09/39] Issue #42 feat: Modifications in metadata of Match The Following Type Questions --- .../components/options/options.component.ts | 103 +++++++++++++----- 1 file changed, 78 insertions(+), 25 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/options/options.component.ts b/projects/questionset-editor-library/src/lib/components/options/options.component.ts index c2a058124..60dd1b29e 100644 --- a/projects/questionset-editor-library/src/lib/components/options/options.component.ts +++ b/projects/questionset-editor-library/src/lib/components/options/options.component.ts @@ -191,36 +191,74 @@ export class OptionsComponent implements OnInit, OnChanges { } setMapping() { - if(!_.isEmpty(this.selectedOptions)) { - this.mapping = []; - const scoreForEachOption = _.round((this.maxScore/this.selectedOptions.length), 2); - _.forEach(this.selectedOptions, (value) => { - const optionMapping = { - value: value, - score: scoreForEachOption, - } - this.mapping.push(optionMapping) - }) + if (this.questionInteractionType === 'choice') { + if (!_.isEmpty(this.selectedOptions)) { + this.mapping = []; + const scoreForEachOption = _.round((this.maxScore/this.selectedOptions.length), 2); + _.forEach(this.selectedOptions, (value) => { + const optionMapping = { + value: value, + score: scoreForEachOption, + }; + this.mapping.push(optionMapping); + }); + } + } else if(this.questionInteractionType === 'match') { + if (!_.isEmpty(this.editorState.answer)) { + this.mapping = []; + const scoreForEachMatch = _.round( + this.maxScore / this.editorState.answer.length, + 2 + ); + _.forEach(this.editorState.answer, (value) => { + const optionMapping = { + value: value, + score: scoreForEachMatch, + }; + this.mapping.push(optionMapping); + }) + } } else { this.mapping = []; } } getInteractions(options) { - let index; - const interactOptions = _.map(options, (opt, key) => { - index = Number(key); - const hints = _.get(this.editorState, `interactions.response1.options[${index}].hints`) - return { label: opt.body, value: index, hints }; - }); - this.subMenuConfig(options); - const interactions = { - response1: { - type: 'choice', - options: interactOptions, - }, - }; - return interactions; + if (this.questionInteractionType === 'choice') { + let index; + const interactOptions = _.map(options, (opt, key) => { + index = Number(key); + const hints = _.get(this.editorState, `interactions.response1.options[${index}].hints`) + return { label: opt.body, value: index, hints }; + }); + this.subMenuConfig(options); + const interactions = { + response1: { + type: 'choice', + options: interactOptions, + }, + }; + return interactions; + } + else if (this.questionInteractionType === 'match') { + const optionSet = { + left: options.map((option) => ({ + label: option.leftOption, + value: option.leftOption.replace(/<\/?[^>]+(>|$)/g, ""), + })), + right: options.map((option) => ({ + label: option.rightOption, + value: option.rightOption.replace(/<\/?[^>]+(>|$)/g, ""), + })), + } + const interactions = { + response1: { + type: 'match', + optionSet: optionSet, + } + }; + return interactions; + } } setTemplete(template) { @@ -269,7 +307,22 @@ export class OptionsComponent implements OnInit, OnChanges { this.setMapping(); this.editorDataHandler(); } - + + onMatchCheck(event) { + if (event.target.checked) { + this.editorState.answer = this.editorState.options.map((option) => { + const obj = {}; + let leftOption = option.leftOption.replace(/<\/?[^>]+(>|$)/g, ""); + let rightOption = option.rightOption.replace(/<\/?[^>]+(>|$)/g, ""); + obj[leftOption] = rightOption; + return obj; + }); + } else { + this.editorState.answer = undefined; + } + this.setMapping(); + this.editorDataHandler(); + } setScore(value, scoreIndex) { const obj = { response: scoreIndex, From 35aa70ed66b30d14c668c5471b605d24d9224a7a Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Mon, 24 Jul 2023 17:27:20 +0530 Subject: [PATCH 10/39] Issue #42 feat: body and answer body in the form of HTML --- .../components/question/question.component.ts | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.ts index 56103a3c5..145d80e85 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.ts @@ -615,6 +615,29 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } } + //to handle when question type is match + if (this.questionInteractionType === 'match') { + const data = _.get(this.treeNodeData, 'data.metadata'); + if (_.get(this.editorState, 'interactionTypes[0]') === 'match' && + _.isEmpty(this.editorState?.responseDeclaration?.response1?.mapping) && + !_.isUndefined(this.editorService?.editorConfig?.config?.renderTaxonomy) && + _.get(data,'allowScoring') === 'Yes') { + this.toasterService.error(_.get(this.configService, 'labelConfig.messages.error.005')); + this.showFormError = true; + return; + } else { + this.showFormError = false; + } + const rightOptionValid = _.find(this.editorState.options, option => (option.rightOption === undefined || option.rightOption === '' || option.rightOption.length > this.setCharacterLimit)); + const leftOptionValid = _.find(this.editorState.options, option => (option.leftOption === undefined || option.leftOption === '' || option.leftOption.length > this.setCharacterLimit)); + if (rightOptionValid || leftOptionValid || (_.isUndefined(this.editorState.answer) && this.sourcingSettings?.enforceCorrectAnswer)) { + this.showFormError = true; + return; + } else { + this.showFormError = false; + } + } + if (this.questionInteractionType === 'slider') { const min = _.get(this.sliderDatas, 'validation.range.min'); const max = _.get(this.sliderDatas, 'validation.range.max'); @@ -800,7 +823,7 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { _.get(treeNodeData,'allowScoring') === 'Yes' ? '' : _.set(metadata,'responseDeclaration.response1.mapping',[]); } - if (this.questionInteractionType != 'choice') { + if (this.questionInteractionType != 'choice' && this.questionInteractionType != 'match') { if (!_.isUndefined(metadata.answer)) { const answerHtml = this.getAnswerHtml(metadata.answer); const finalAnswer = this.getAnswerWrapperHtml(answerHtml); @@ -823,10 +846,17 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { }) const finalAnswer = this.getAnswerWrapperHtml(concatenatedAnswers); metadata.answer = finalAnswer; - } else if (this.questionInteractionType != 'default' && this.questionInteractionType != 'choice') { + } else if (this.questionInteractionType != 'default' && this.questionInteractionType != 'choice' && this.questionInteractionType != 'match') { metadata.responseDeclaration = this.getResponseDeclaration(this.questionInteractionType); } + if (this.questionInteractionType === 'match') { + metadata.body = this.getMatchQuestionHtmlBody(this.editorState.question); + const leftOptions = metadata.interactions.response1.optionSet.left; + const rightOptions = metadata.interactions.response1.optionSet.right; + metadata.answer = this.getMatchAnswerContainerHtml(leftOptions, rightOptions); + } + if (!_.isUndefined(this.selectedSolutionType) && !_.isEmpty(this.selectedSolutionType)) { const solutionObj = this.getSolutionObj(this.solutionUUID, this.selectedSolutionType, this.editorState.solutions); metadata.editorState.solutions = [solutionObj]; @@ -856,6 +886,33 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { return optionHtml; } + + getMatchAnswerContainerHtml(leftOptions, rightOptions) { + const matchContainerTemplate = '
{leftOptions}{rightOptions}
'; + const leftOptionsHtml = this.getOptionWrapperHtml(leftOptions, 'left'); + const rightOptionsHtml = this.getOptionWrapperHtml(rightOptions, 'right'); + const matchContainer = matchContainerTemplate + .replace('{leftOptions}', leftOptionsHtml) + .replace('{rightOptions}', rightOptionsHtml); + return matchContainer; + } + + getOptionWrapperHtml(options, type) { + const wrapperTemplate = `
{options}
`; + let optionsHtml = ''; + options.forEach((option) => { + const optionHtml = this.getMatchAnswerHtml(option.label, option.value); + optionsHtml = optionsHtml.concat(optionHtml); + }); + const wrapper = wrapperTemplate.replace('{options}', optionsHtml); + return wrapper; + } + getMatchAnswerHtml(label, value) { + const answerHtml = '
{label}
'; + const optionHtml = answerHtml.replace('{label}', label).replace('{value}', value); + return optionHtml; + } + getAnswerWrapperHtml(concatenatedAnswers) { const answerTemplate = '
{answers}
'; const answer = answerTemplate.replace('{answers}', concatenatedAnswers); @@ -899,6 +956,15 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { return videoSolutionValue; } + getMatchQuestionHtmlBody(question) { + const matchTemplateConfig = { + // tslint:disable-next-line:max-line-length + matchBody: '
{question}
' + }; + const { matchBody } = matchTemplateConfig; + const questionBody = matchBody.replace('{question}', question); + return questionBody; + } getMcqQuestionHtmlBody(question, templateId) { const mcqTemplateConfig = { From 862b86d692c7770afc08474d2d99328c4b0991d6 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Tue, 25 Jul 2023 15:22:32 +0530 Subject: [PATCH 11/39] test foreditorDataHandler() --- .../components/options/options.component.spec.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts b/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts index 83113a3bc..8527b418c 100644 --- a/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts +++ b/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts @@ -139,6 +139,7 @@ describe('OptionsComponent', () => { it('#editorDataHandler() should emit option data when answer is single value', () => { spyOn(component, 'prepareMcqBody').and.callThrough(); spyOn(component.editorDataOutput, 'emit').and.callThrough(); + component.questionInteractionType = 'choice'; component.editorState = mockOptionData.editorOptionData; component.editorDataHandler(); component.questionPrimaryCategory='Multiselect Multiple Choice Question'; @@ -149,6 +150,7 @@ describe('OptionsComponent', () => { it('#editorDataHandler() should emit option data when answer is multiple value', () => { spyOn(component, 'prepareMcqBody').and.callThrough(); spyOn(component.editorDataOutput, 'emit').and.callThrough(); + component.questionInteractionType = 'choice'; component.editorState = mockOptionData.editorOptionData; component.editorState.answer = [0,1]; component.editorDataHandler(); @@ -157,6 +159,18 @@ describe('OptionsComponent', () => { expect(component.editorDataOutput.emit).toHaveBeenCalled(); }); + it('when questionInteractionType is match prepareMtfBody will be called', () => { + spyOn(component, 'prepareMtfBody').and.callFake(()=>{}); + spyOn(component.editorDataOutput, 'emit').and.callFake(()=>{}); + component.questionInteractionType = 'match'; + component.editorDataHandler(); + component.questionPrimaryCategory='Match The Following Question'; + expect(component.prepareMtfBody).toHaveBeenCalled(); + expect(component.editorDataOutput.emit).toHaveBeenCalled(); + }); + + + it('#prepareMcqBody() should return expected mcq option data for single select MCQ', () => { component.maxScore = 1; component.selectedOptions = [0]; From 296a02529a28f5babbcebbaf4cc3e5e4a9bb45f8 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Tue, 25 Jul 2023 15:51:15 +0530 Subject: [PATCH 12/39] modifiedtest for setMapping when interaction type is choice --- .../src/lib/components/options/options.component.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts b/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts index 8527b418c..2927fc75f 100644 --- a/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts +++ b/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts @@ -169,8 +169,6 @@ describe('OptionsComponent', () => { expect(component.editorDataOutput.emit).toHaveBeenCalled(); }); - - it('#prepareMcqBody() should return expected mcq option data for single select MCQ', () => { component.maxScore = 1; component.selectedOptions = [0]; @@ -192,6 +190,7 @@ describe('OptionsComponent', () => { }); it('#getResponseDeclaration() should return expected response declaration', () => { + component.questionInteractionType = 'choice'; component.mapping = [{ "value": 0, "score": 1 @@ -228,6 +227,7 @@ describe('OptionsComponent', () => { }); it('setMapping should set the mapping for single select MCQ', () => { + component.questionInteractionType = 'choice'; component.mapping = []; component.selectedOptions = [0]; component.maxScore = 1; @@ -237,6 +237,7 @@ describe('OptionsComponent', () => { }); it('setMapping should set the mapping for single select MCQ', () => { + component.questionInteractionType = 'choice'; component.mapping = []; component.selectedOptions = [0,1]; component.maxScore = 1; @@ -248,6 +249,7 @@ describe('OptionsComponent', () => { it('#getInteractions() should return expected response declaration', () => { spyOn(component,"getInteractions").and.callThrough(); + component.questionInteractionType = 'choice'; component.getInteractions(mockOptionData.editorOptionData.options); expect(component.getInteractions).toHaveBeenCalled(); // expect(mockOptionData.prepareMcqBody.interactions).toEqual(result); From 8b8deadae02446be3d457348c19677be44277ae1 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Tue, 1 Aug 2023 13:34:27 +0530 Subject: [PATCH 13/39] Issue #42 feat: new match component added --- .../lib/components/match/match.component.ts | 200 ++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 projects/questionset-editor-library/src/lib/components/match/match.component.ts diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.ts b/projects/questionset-editor-library/src/lib/components/match/match.component.ts new file mode 100644 index 000000000..920e556db --- /dev/null +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.ts @@ -0,0 +1,200 @@ +import { Component, Input, OnInit, OnChanges, Output, EventEmitter, SimpleChanges } from '@angular/core'; +import * as _ from 'lodash-es'; +import { EditorTelemetryService } from '../../services/telemetry/telemetry.service'; +import { ConfigService } from '../../services/config/config.service'; +import { SubMenu } from '../question-option-sub-menu/question-option-sub-menu.component'; +import { TreeService } from '../../services/tree/tree.service'; +import { EditorService } from '../../services/editor/editor.service'; + +@Component({ + selector: 'lib-match', + templateUrl: './match.component.html', + styleUrls: ['./match.component.scss'] +}) +export class MatchComponent implements OnInit, OnChanges { + @Input() editorState: any; + @Input() showFormError; + @Input() sourcingSettings; + @Input() questionPrimaryCategory; + @Input() mapping = []; + @Input() isReadOnlyMode; + @Input() maxScore; + @Output() editorDataOutput: EventEmitter = new EventEmitter(); + public setCharacterLimit = 160; + public setImageLimit = 1; + public templateType = 'default'; + subMenus: SubMenu[][]; + hints = []; + showSubMenu: boolean = false; + parentMeta: any; + selectedOptions = []; + constructor( + public telemetryService: EditorTelemetryService, + public configService: ConfigService, + public treeService: TreeService, + private editorService: EditorService + ) {} + + ngOnInit() { + if (!_.isUndefined(this.editorState.answer)) { + this.addSelectedOptions(); + } + if (!_.isUndefined(this.editorState.templateId)) { + this.templateType = this.editorState.templateId; + } + this.mapping = _.get(this.editorState, 'responseDeclaration.response1.mapping') || []; + this.editorDataHandler(); + if (!_.isUndefined(this.editorService.editorConfig.config.renderTaxonomy)) { + this.parentMeta = this.treeService.getFirstChild().data.metadata; + this.showSubMenu = true; + } + } + ngOnChanges(changes: SimpleChanges){ + if (!_.isUndefined(changes.maxScore.previousValue) && !_.isNaN(changes.maxScore.currentValue)) { + this.setMapping(); + this.editorDataHandler(); + } + } + addSelectedOptions() { + this.selectedOptions = this.editorState.answer; + } + + editorDataHandler(event?) { + const body = this.prepareMtfBody(this.editorState); + this.editorDataOutput.emit({ + body, + mediaobj: event ? event.mediaobj : undefined, + }); + } + prepareMtfBody(editorState) { + let metadata: any; + const correctAnswer = editorState.answer; + let options: any; + if (!_.isEmpty(correctAnswer)) { + options = { + leftOptions: editorState.options.map((option, index) => { + return { + value: { + body: option.leftOption, + value: index, + } + } + }), + rightOptions: editorState.options.map((option, index) => { + return { + value: { + body: option.rightOption, + value: index, + } + } + }) + } + } + metadata = { + templateId: this.templateType, + name: this.questionPrimaryCategory || "Match The Following Question", + responseDeclaration: this.getResponseDeclaration(editorState), + outcomeDeclaration: this.getOutcomeDeclaration(), + interactionTypes: ["match"], + interactions: this.getInteractions(editorState.options), + editorState: { + options, + }, + qType: "MTF", + primaryCategory: + this.questionPrimaryCategory || "Match The Following Question", + }; + return metadata; + } + getResponseDeclaration(editorState) { + const responseDeclaration = { + response1: { + cardinality: 'multiple', + type: 'map', + correctResponse: { + value: editorState.answer, + }, + mapping: this.mapping, + }, + }; + return responseDeclaration; + } + + getOutcomeDeclaration() { + const outcomeDeclaration = { + maxScore: { + cardinality: 'multiple', + type: 'integer', + defaultValue: this.maxScore + } + }; + return outcomeDeclaration; + } + + setMapping() { + if (!_.isEmpty(this.editorState.answer)) { + this.mapping = []; + const scoreForEachMatch = _.round( + this.maxScore / this.editorState.answer.length, + 2 + ); + _.forEach(this.editorState.answer, (value) => { + const optionMapping = { + value: value, + score: scoreForEachMatch, + }; + this.mapping.push(optionMapping); + }) + } else { + this.mapping = []; + } + } + + getInteractions(options) { + const optionSet = { + leftOptions: options.map((option,index) => ({ + label: option.leftOption, + value: index, + })), + rightOptions: options.map((option,index) => ({ + label: option.rightOption, + value: index, + })), + } + const interactions = { + response1: { + type: 'match', + options: optionSet, + } + }; + return interactions; + } + + onOptionChange(event,index) { + if(event.body!=='' && !_.includes(this.selectedOptions, index)) { + this.selectedOptions.push(index); + } else if(event.body === '') { + _.remove(this.selectedOptions, (n) => { + return n === index; + }); + } + if(this.selectedOptions === undefined){ + this.editorState.answer = undefined; + } + else { + this.editorState.answer = this.selectedOptions.map((value) => ({ [value]: value })); + } + this.setMapping(); + this.editorDataHandler(); + } + setScore(value, scoreIndex) { + const obj = { + response: scoreIndex, + outcomes: { + score: value, + }, + }; + this.mapping[scoreIndex] = obj; + this.editorDataHandler(); + } +} From b14cda94cca4123405d55a92c3545bc7718324ae Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Tue, 1 Aug 2023 13:37:52 +0530 Subject: [PATCH 14/39] Adding match component in question template --- .../src/lib/components/question/question.component.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.html b/projects/questionset-editor-library/src/lib/components/question/question.component.html index 5a26f2329..6bd2590d6 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.html +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.html @@ -64,6 +64,13 @@ (editorDataOutput)="editorDataHandler($event)" [sourcingSettings]="sourcingSettings" [mapping]="scoreMapping" [maxScore]="maxScore" [isReadOnlyMode]="isReadOnlyMode"> + + From 2c982fd390eab8a69c03f005fba673390efd47b3 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Tue, 1 Aug 2023 13:50:31 +0530 Subject: [PATCH 15/39] Adding match component in module --- .../src/lib/questionset-editor-library.module.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/projects/questionset-editor-library/src/lib/questionset-editor-library.module.ts b/projects/questionset-editor-library/src/lib/questionset-editor-library.module.ts index e2f5303eb..ccf245b11 100644 --- a/projects/questionset-editor-library/src/lib/questionset-editor-library.module.ts +++ b/projects/questionset-editor-library/src/lib/questionset-editor-library.module.ts @@ -36,6 +36,7 @@ import { PlainTreeComponent } from './components/plain-tree/plain-tree.component import { A11yModule } from '@angular/cdk/a11y'; import { ProgressStatusComponent } from './components/progress-status/progress-status.component'; import {TermAndConditionComponent} from './components/term-and-condition/term-and-condition.component'; +import { MatchComponent } from './components/match/match.component'; import { QualityParamsModalComponent } from './components/quality-params-modal/quality-params-modal.component'; @NgModule({ @@ -68,7 +69,8 @@ import { QualityParamsModalComponent } from './components/quality-params-modal/q PlainTreeComponent, ProgressStatusComponent, TermAndConditionComponent, - QualityParamsModalComponent + QualityParamsModalComponent, + MatchComponent ], imports: [CommonModule, FormsModule, ReactiveFormsModule.withConfig({callSetDisabledState: 'whenDisabledForLegacyCode'}), RouterModule.forChild([]), SuiModule, CommonFormElementsModule, InfiniteScrollModule, HttpClientModule, ResourceLibraryModule, A11yModule], From 1ea5d7ccaf68cc977789ed69c9f294eb62304f6e Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Tue, 1 Aug 2023 16:18:17 +0530 Subject: [PATCH 16/39] Revert "test foreditorDataHandler()" This reverts commit 862b86d692c7770afc08474d2d99328c4b0991d6. --- .../lib/components/options/options.component.spec.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts b/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts index 2927fc75f..7ba3e7510 100644 --- a/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts +++ b/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts @@ -139,7 +139,6 @@ describe('OptionsComponent', () => { it('#editorDataHandler() should emit option data when answer is single value', () => { spyOn(component, 'prepareMcqBody').and.callThrough(); spyOn(component.editorDataOutput, 'emit').and.callThrough(); - component.questionInteractionType = 'choice'; component.editorState = mockOptionData.editorOptionData; component.editorDataHandler(); component.questionPrimaryCategory='Multiselect Multiple Choice Question'; @@ -150,7 +149,6 @@ describe('OptionsComponent', () => { it('#editorDataHandler() should emit option data when answer is multiple value', () => { spyOn(component, 'prepareMcqBody').and.callThrough(); spyOn(component.editorDataOutput, 'emit').and.callThrough(); - component.questionInteractionType = 'choice'; component.editorState = mockOptionData.editorOptionData; component.editorState.answer = [0,1]; component.editorDataHandler(); @@ -159,16 +157,6 @@ describe('OptionsComponent', () => { expect(component.editorDataOutput.emit).toHaveBeenCalled(); }); - it('when questionInteractionType is match prepareMtfBody will be called', () => { - spyOn(component, 'prepareMtfBody').and.callFake(()=>{}); - spyOn(component.editorDataOutput, 'emit').and.callFake(()=>{}); - component.questionInteractionType = 'match'; - component.editorDataHandler(); - component.questionPrimaryCategory='Match The Following Question'; - expect(component.prepareMtfBody).toHaveBeenCalled(); - expect(component.editorDataOutput.emit).toHaveBeenCalled(); - }); - it('#prepareMcqBody() should return expected mcq option data for single select MCQ', () => { component.maxScore = 1; component.selectedOptions = [0]; From c233eae4b16dc0ce1179b8fc9d10f869c8c03b37 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Tue, 1 Aug 2023 16:32:12 +0530 Subject: [PATCH 17/39] Revert "modifiedtest for setMapping when interaction type is choice" This reverts commit 296a02529a28f5babbcebbaf4cc3e5e4a9bb45f8. --- .../src/lib/components/options/options.component.spec.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts b/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts index 7ba3e7510..83113a3bc 100644 --- a/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts +++ b/projects/questionset-editor-library/src/lib/components/options/options.component.spec.ts @@ -178,7 +178,6 @@ describe('OptionsComponent', () => { }); it('#getResponseDeclaration() should return expected response declaration', () => { - component.questionInteractionType = 'choice'; component.mapping = [{ "value": 0, "score": 1 @@ -215,7 +214,6 @@ describe('OptionsComponent', () => { }); it('setMapping should set the mapping for single select MCQ', () => { - component.questionInteractionType = 'choice'; component.mapping = []; component.selectedOptions = [0]; component.maxScore = 1; @@ -225,7 +223,6 @@ describe('OptionsComponent', () => { }); it('setMapping should set the mapping for single select MCQ', () => { - component.questionInteractionType = 'choice'; component.mapping = []; component.selectedOptions = [0,1]; component.maxScore = 1; @@ -237,7 +234,6 @@ describe('OptionsComponent', () => { it('#getInteractions() should return expected response declaration', () => { spyOn(component,"getInteractions").and.callThrough(); - component.questionInteractionType = 'choice'; component.getInteractions(mockOptionData.editorOptionData.options); expect(component.getInteractions).toHaveBeenCalled(); // expect(mockOptionData.prepareMcqBody.interactions).toEqual(result); From 23ddf3a0c24efce5403f66b7306fdfe1fb08c905 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Tue, 1 Aug 2023 16:35:18 +0530 Subject: [PATCH 18/39] Reverting option component file --- .../components/options/options.component.ts | 147 ++++-------------- 1 file changed, 28 insertions(+), 119 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/options/options.component.ts b/projects/questionset-editor-library/src/lib/components/options/options.component.ts index 60dd1b29e..d317eccf9 100644 --- a/projects/questionset-editor-library/src/lib/components/options/options.component.ts +++ b/projects/questionset-editor-library/src/lib/components/options/options.component.ts @@ -15,7 +15,6 @@ export class OptionsComponent implements OnInit, OnChanges { @Input() showFormError; @Input() sourcingSettings; @Input() questionPrimaryCategory; - @Input() questionInteractionType; @Input() mapping = []; @Input() isReadOnlyMode; @Input() maxScore; @@ -76,16 +75,8 @@ export class OptionsComponent implements OnInit, OnChanges { } editorDataHandler(event?) { - let body: any; - if (this.questionInteractionType === 'choice') { - body = this.prepareMcqBody(this.editorState); - } else if (this.questionInteractionType === 'match') { - body = this.prepareMtfBody(this.editorState); - } - this.editorDataOutput.emit({ - body, - mediaobj: event ? event.mediaobj : undefined, - }); + const body = this.prepareMcqBody(this.editorState); + this.editorDataOutput.emit({ body, mediaobj: event ? event.mediaobj : undefined }); } prepareMcqBody(editorState) { @@ -128,40 +119,11 @@ export class OptionsComponent implements OnInit, OnChanges { return metadata; } - prepareMtfBody(editorState) { - let metadata: any; - const correctAnswer = editorState.answer; - let options: any; - if (!_.isEmpty(correctAnswer)) { - options = _.reduce(this.editorState.options,function (acc, obj) { - acc.leftOption.push(obj.leftOption); - acc.rightOption.push(obj.rightOption); - return acc; - },{ leftOption: [], rightOption: [] } - ); - } - console.log(options); - console.log(editorState.answer); - metadata = { - templateId: this.templateType, - name: this.questionPrimaryCategory || 'Match The Following Question', - responseDeclaration: this.getResponseDeclaration(editorState), - outcomeDeclaration: this.getOutcomeDeclaration(), - interactionTypes: ['match'], - interactions: this.getInteractions(editorState.options), - editorState: { - options, - }, - qType: 'MTF', - primaryCategory: this.questionPrimaryCategory || "Match The Following Question", - }; - return metadata; - } getResponseDeclaration(editorState) { const responseDeclaration = { response1: { cardinality: this.getCardinality(), - type: this.questionInteractionType === 'choice' ? 'integer' : 'map', + type: 'integer', correctResponse: { value: editorState.answer, }, @@ -191,74 +153,36 @@ export class OptionsComponent implements OnInit, OnChanges { } setMapping() { - if (this.questionInteractionType === 'choice') { - if (!_.isEmpty(this.selectedOptions)) { - this.mapping = []; - const scoreForEachOption = _.round((this.maxScore/this.selectedOptions.length), 2); - _.forEach(this.selectedOptions, (value) => { - const optionMapping = { - value: value, - score: scoreForEachOption, - }; - this.mapping.push(optionMapping); - }); - } - } else if(this.questionInteractionType === 'match') { - if (!_.isEmpty(this.editorState.answer)) { - this.mapping = []; - const scoreForEachMatch = _.round( - this.maxScore / this.editorState.answer.length, - 2 - ); - _.forEach(this.editorState.answer, (value) => { - const optionMapping = { - value: value, - score: scoreForEachMatch, - }; - this.mapping.push(optionMapping); - }) - } + if(!_.isEmpty(this.selectedOptions)) { + this.mapping = []; + const scoreForEachOption = _.round((this.maxScore/this.selectedOptions.length), 2); + _.forEach(this.selectedOptions, (value) => { + const optionMapping = { + value: value, + score: scoreForEachOption, + } + this.mapping.push(optionMapping) + }) } else { this.mapping = []; } } getInteractions(options) { - if (this.questionInteractionType === 'choice') { - let index; - const interactOptions = _.map(options, (opt, key) => { - index = Number(key); - const hints = _.get(this.editorState, `interactions.response1.options[${index}].hints`) - return { label: opt.body, value: index, hints }; - }); - this.subMenuConfig(options); - const interactions = { - response1: { - type: 'choice', - options: interactOptions, - }, - }; - return interactions; - } - else if (this.questionInteractionType === 'match') { - const optionSet = { - left: options.map((option) => ({ - label: option.leftOption, - value: option.leftOption.replace(/<\/?[^>]+(>|$)/g, ""), - })), - right: options.map((option) => ({ - label: option.rightOption, - value: option.rightOption.replace(/<\/?[^>]+(>|$)/g, ""), - })), - } - const interactions = { - response1: { - type: 'match', - optionSet: optionSet, - } - }; - return interactions; - } + let index; + const interactOptions = _.map(options, (opt, key) => { + index = Number(key); + const hints = _.get(this.editorState, `interactions.response1.options[${index}].hints`) + return { label: opt.body, value: index, hints }; + }); + this.subMenuConfig(options); + const interactions = { + response1: { + type: 'choice', + options: interactOptions, + }, + }; + return interactions; } setTemplete(template) { @@ -307,22 +231,7 @@ export class OptionsComponent implements OnInit, OnChanges { this.setMapping(); this.editorDataHandler(); } - - onMatchCheck(event) { - if (event.target.checked) { - this.editorState.answer = this.editorState.options.map((option) => { - const obj = {}; - let leftOption = option.leftOption.replace(/<\/?[^>]+(>|$)/g, ""); - let rightOption = option.rightOption.replace(/<\/?[^>]+(>|$)/g, ""); - obj[leftOption] = rightOption; - return obj; - }); - } else { - this.editorState.answer = undefined; - } - this.setMapping(); - this.editorDataHandler(); - } + setScore(value, scoreIndex) { const obj = { response: scoreIndex, From 000c5c3bf83927dd7697244e6202baa00f38df06 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Tue, 1 Aug 2023 20:26:14 +0530 Subject: [PATCH 19/39] Refactoring of code in match component and question component --- .../src/styles.scss | 1 + .../lib/components/match/match.component.html | 52 ++++++++++++++++++ .../lib/components/match/match.component.scss | 0 .../components/match/match.component.spec.ts | 23 ++++++++ .../lib/components/match/match.component.ts | 55 +++++-------------- .../components/question/question.component.ts | 13 ++--- .../src/lib/interfaces/MtfForm.ts | 9 ++- 7 files changed, 101 insertions(+), 52 deletions(-) create mode 100644 projects/questionset-editor-library/src/lib/components/match/match.component.html create mode 100644 projects/questionset-editor-library/src/lib/components/match/match.component.scss create mode 100644 projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts diff --git a/projects/questionset-editor-library-wc/src/styles.scss b/projects/questionset-editor-library-wc/src/styles.scss index 73bf969da..13239b7e9 100644 --- a/projects/questionset-editor-library-wc/src/styles.scss +++ b/projects/questionset-editor-library-wc/src/styles.scss @@ -22,4 +22,5 @@ @import "./../../questionset-editor-library/src/lib/components/template/template.component.scss"; @import "./../../questionset-editor-library/src/lib/components/term-and-condition/term-and-condition.component.scss"; @import "./../../questionset-editor-library/src/lib/components/translations/translations.component.scss"; +@import "./../../questionset-editor-library/src/lib/components/match/match.component.scss"; diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.html b/projects/questionset-editor-library/src/lib/components/match/match.component.html new file mode 100644 index 000000000..a59dafdd5 --- /dev/null +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.html @@ -0,0 +1,52 @@ + +
+ + + +
+
+
+
+
+ + + + +
+
+ + + + +
+
+
+ +
+
+
+
+ +
+
diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.scss b/projects/questionset-editor-library/src/lib/components/match/match.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts new file mode 100644 index 000000000..932baeae4 --- /dev/null +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MatchComponent } from './match.component'; + +describe('MatchComponent', () => { + let component: MatchComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MatchComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(MatchComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.ts b/projects/questionset-editor-library/src/lib/components/match/match.component.ts index 920e556db..2c479610c 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.ts +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.ts @@ -2,7 +2,6 @@ import { Component, Input, OnInit, OnChanges, Output, EventEmitter, SimpleChange import * as _ from 'lodash-es'; import { EditorTelemetryService } from '../../services/telemetry/telemetry.service'; import { ConfigService } from '../../services/config/config.service'; -import { SubMenu } from '../question-option-sub-menu/question-option-sub-menu.component'; import { TreeService } from '../../services/tree/tree.service'; import { EditorService } from '../../services/editor/editor.service'; @@ -23,11 +22,7 @@ export class MatchComponent implements OnInit, OnChanges { public setCharacterLimit = 160; public setImageLimit = 1; public templateType = 'default'; - subMenus: SubMenu[][]; - hints = []; - showSubMenu: boolean = false; parentMeta: any; - selectedOptions = []; constructor( public telemetryService: EditorTelemetryService, public configService: ConfigService, @@ -36,18 +31,11 @@ export class MatchComponent implements OnInit, OnChanges { ) {} ngOnInit() { - if (!_.isUndefined(this.editorState.answer)) { - this.addSelectedOptions(); - } if (!_.isUndefined(this.editorState.templateId)) { this.templateType = this.editorState.templateId; } this.mapping = _.get(this.editorState, 'responseDeclaration.response1.mapping') || []; this.editorDataHandler(); - if (!_.isUndefined(this.editorService.editorConfig.config.renderTaxonomy)) { - this.parentMeta = this.treeService.getFirstChild().data.metadata; - this.showSubMenu = true; - } } ngOnChanges(changes: SimpleChanges){ if (!_.isUndefined(changes.maxScore.previousValue) && !_.isNaN(changes.maxScore.currentValue)) { @@ -55,9 +43,6 @@ export class MatchComponent implements OnInit, OnChanges { this.editorDataHandler(); } } - addSelectedOptions() { - this.selectedOptions = this.editorState.answer; - } editorDataHandler(event?) { const body = this.prepareMtfBody(this.editorState); @@ -68,9 +53,16 @@ export class MatchComponent implements OnInit, OnChanges { } prepareMtfBody(editorState) { let metadata: any; - const correctAnswer = editorState.answer; + if (_.isEmpty(editorState.correctMatchPair) && !_.isEmpty(editorState.options)) { + editorState.correctMatchPair = editorState.options.map((option, index) => { + const correctMatchPair = {}; + correctMatchPair[index.toString()] = index; + return correctMatchPair; + }); + } + this.setMapping(); let options: any; - if (!_.isEmpty(correctAnswer)) { + if (!_.isEmpty(editorState.correctMatchPair)) { options = { leftOptions: editorState.options.map((option, index) => { return { @@ -112,7 +104,7 @@ export class MatchComponent implements OnInit, OnChanges { cardinality: 'multiple', type: 'map', correctResponse: { - value: editorState.answer, + value: editorState.correctMatchPair, }, mapping: this.mapping, }, @@ -130,15 +122,15 @@ export class MatchComponent implements OnInit, OnChanges { }; return outcomeDeclaration; } - + setMapping() { - if (!_.isEmpty(this.editorState.answer)) { + if (!_.isEmpty(this.editorState.correctMatchPair)) { this.mapping = []; const scoreForEachMatch = _.round( - this.maxScore / this.editorState.answer.length, + this.maxScore / this.editorState.correctMatchPair.length, 2 ); - _.forEach(this.editorState.answer, (value) => { + _.forEach(this.editorState.correctMatchPair, (value) => { const optionMapping = { value: value, score: scoreForEachMatch, @@ -168,25 +160,8 @@ export class MatchComponent implements OnInit, OnChanges { } }; return interactions; - } - - onOptionChange(event,index) { - if(event.body!=='' && !_.includes(this.selectedOptions, index)) { - this.selectedOptions.push(index); - } else if(event.body === '') { - _.remove(this.selectedOptions, (n) => { - return n === index; - }); - } - if(this.selectedOptions === undefined){ - this.editorState.answer = undefined; - } - else { - this.editorState.answer = this.selectedOptions.map((value) => ({ [value]: value })); - } - this.setMapping(); - this.editorDataHandler(); } + setScore(value, scoreIndex) { const obj = { response: scoreIndex, diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.ts index 145d80e85..6ab00be78 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.ts @@ -260,7 +260,7 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { const question = this.questionMetaData?.editorState?.question; const interactions = this.questionMetaData?.interactions; this.editorState = new MtfForm({ - question, options, answer: _.get(responseDeclaration, 'response1.correctResponse.value') + question, options, correctMatchPair: _.get(responseDeclaration, 'response1.correctResponse.value') }, { templateId, numberOfOptions, maximumOptions }); this.editorState.solutions = this.questionMetaData?.editorState?.solutions; this.editorState.interactions = interactions; @@ -630,7 +630,7 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } const rightOptionValid = _.find(this.editorState.options, option => (option.rightOption === undefined || option.rightOption === '' || option.rightOption.length > this.setCharacterLimit)); const leftOptionValid = _.find(this.editorState.options, option => (option.leftOption === undefined || option.leftOption === '' || option.leftOption.length > this.setCharacterLimit)); - if (rightOptionValid || leftOptionValid || (_.isUndefined(this.editorState.answer) && this.sourcingSettings?.enforceCorrectAnswer)) { + if (rightOptionValid || leftOptionValid || (_.isUndefined(this.editorState.correctMatchPair) && this.sourcingSettings?.enforceCorrectAnswer)) { this.showFormError = true; return; } else { @@ -852,9 +852,9 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { if (this.questionInteractionType === 'match') { metadata.body = this.getMatchQuestionHtmlBody(this.editorState.question); - const leftOptions = metadata.interactions.response1.optionSet.left; - const rightOptions = metadata.interactions.response1.optionSet.right; - metadata.answer = this.getMatchAnswerContainerHtml(leftOptions, rightOptions); + const leftOptions = metadata.interactions.response1.options.leftOptions; + const rightOptions = metadata.interactions.response1.options.rightOptions; + metadata.correctMatchPair = this.getMatchAnswerContainerHtml(leftOptions, rightOptions); } if (!_.isUndefined(this.selectedSolutionType) && !_.isEmpty(this.selectedSolutionType)) { @@ -885,7 +885,6 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { const optionHtml = answerHtml.replace('{answer}', optionLabel); return optionHtml; } - getMatchAnswerContainerHtml(leftOptions, rightOptions) { const matchContainerTemplate = '
{leftOptions}{rightOptions}
'; @@ -895,7 +894,7 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { .replace('{leftOptions}', leftOptionsHtml) .replace('{rightOptions}', rightOptionsHtml); return matchContainer; - } + } getOptionWrapperHtml(options, type) { const wrapperTemplate = `
{options}
`; diff --git a/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts b/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts index 9634569bc..26077c4e4 100644 --- a/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts +++ b/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts @@ -7,7 +7,7 @@ export class MtfOption { export interface MtfData { question: string; options: Array; - answer?: string; + correctMatchPair?: string; learningOutcome?: string; complexityLevel?: string; maxScore?: number; @@ -17,25 +17,24 @@ export interface MtfConfig { templateId?: string; numberOfOptions?: number; maximumOptions?: number; - } export class MtfForm { public question: string; public options: Array; public templateId: string; - public answer: string; + public correctMatchPair: string; public learningOutcome?: string; public complexityLevel?: string; public maxScore?: number; public maximumOptions; public numberOfOptions; - constructor({question, options, answer, learningOutcome, complexityLevel, maxScore,}: MtfData,{ templateId, numberOfOptions, maximumOptions }: MtfConfig) { + constructor({question, options, correctMatchPair, learningOutcome, complexityLevel, maxScore,}: MtfData,{ templateId, numberOfOptions, maximumOptions }: MtfConfig) { this.question = question; this.options = options || []; this.templateId = templateId; - this.answer = answer; + this.correctMatchPair = correctMatchPair; this.learningOutcome = learningOutcome; this.complexityLevel = complexityLevel; this.numberOfOptions = numberOfOptions || 2; From c19a96e59b7806000432ff2bf71910d429ef8020 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Wed, 2 Aug 2023 00:47:00 +0530 Subject: [PATCH 20/39] Tests for match component --- .../components/match/match.component.spec.ts | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts index 932baeae4..294e61b1c 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts @@ -1,23 +1,35 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - +import { TelemetryInteractDirective } from '../../directives/telemetry-interact/telemetry-interact.directive'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { FormsModule } from '@angular/forms'; import { MatchComponent } from './match.component'; +import { CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; +import { ConfigService } from '../../services/config/config.service'; +import { SuiModule } from 'ng2-semantic-ui-v9'; +import { TreeService } from '../../services/tree/tree.service'; +import { EditorTelemetryService } from '../../services/telemetry/telemetry.service'; + -describe('MatchComponent', () => { +describe('OptionsComponent', () => { let component: MatchComponent; let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ MatchComponent ] + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, FormsModule, SuiModule], + declarations: [MatchComponent, TelemetryInteractDirective], + providers: [ConfigService, TreeService, EditorTelemetryService,], + schemas: [CUSTOM_ELEMENTS_SCHEMA] }) - .compileComponents(); + .compileComponents(); + })); + beforeEach(() => { fixture = TestBed.createComponent(MatchComponent); component = fixture.componentInstance; - fixture.detectChanges(); + // fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); -}); +}); \ No newline at end of file From f82c88418e08b43e5381f91560183c8ba820b8a6 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Mon, 7 Aug 2023 15:03:03 +0530 Subject: [PATCH 21/39] Issue #42 feat: New Match Type Question Implementation --- .../lib/components/match/match.component.html | 34 +- .../lib/components/match/match.component.scss | 25 ++ .../match/match.component.spec.data.ts | 114 ++++++ .../components/match/match.component.spec.ts | 119 +++++- .../lib/components/match/match.component.ts | 37 +- .../components/options/options.component.ts | 10 +- .../components/question/question.component.ts | 36 +- .../src/lib/interfaces/MtfForm.ts | 2 +- .../src/lib/services/config/label.config.json | 382 +++++++++--------- 9 files changed, 495 insertions(+), 264 deletions(-) create mode 100644 projects/questionset-editor-library/src/lib/components/match/match.component.spec.data.ts diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.html b/projects/questionset-editor-library/src/lib/components/match/match.component.html index a59dafdd5..ea938a44b 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.html +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.html @@ -5,37 +5,39 @@
-
-
-
+
+
+
+ [setCharacterLimit]="setCharacterLimit" (editorDataOutput)="option.left= $event.body; editorDataHandler($event);" + [editorDataInput]="option.left" class="ckeditor-tool__option mb-10" + [class.mb-5]="showFormError && option.left.length > setCharacterLimit"> -
-
+
-
-
-
diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.scss b/projects/questionset-editor-library/src/lib/components/match/match.component.scss index e69de29bb..b6b99bbbe 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.scss +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.scss @@ -0,0 +1,25 @@ +.b-0{ + border: 0 !important; +} + +.bg-none{ + background-color: transparent !important; + + &:hover, + &:focus { + background-color: transparent !important; + } +} + +.w-49{ + width: 49%; + max-width: 49%; +} + +.sb-line-height-24 { + line-height: 24px; +} + +.right{ + float: right; +} \ No newline at end of file diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.spec.data.ts b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.data.ts new file mode 100644 index 000000000..2552ed8fd --- /dev/null +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.data.ts @@ -0,0 +1,114 @@ +export const mockOptionData = { + editorOptionData: { + question: "

Match the following with appropriate answer?

", + options: [ + { + left: "

Lotus

", + right: "

Flower

", + }, + { + left: "

Mango

", + right: "

Fruit

", + }, + ], + templateId: "mtf-vertical", + correctMatchPair: [{ "0": 0 }, { "1": 1 }], + numberOfOptions: 4, + }, + prepareMtfBody: { + templateId: "default", + name: "Match The Following Question", + responseDeclaration: { + response1: { + cardinality: "multiple", + type: "map", + correctResponse: { + value: [ + { + "0": 0, + }, + { + "1": 1, + }, + ], + }, + mapping: [ + { + value: { + "0": 0, + }, + score: 2, + }, + { + value: { + "1": 1, + }, + score: 2, + }, + ], + }, + }, + interactionTypes: ["match"], + interactions: { + response1: { + type: "match", + options: { + left: [ + { + label: "

Lotus

", + value: 0, + }, + { + label: "

Mango

", + value: 1, + }, + ], + right: [ + { + label: "

Flower

", + value: 0, + }, + { + label: "

Fruit

", + value: 1, + }, + ], + }, + }, + }, + editorState: { + options: { + left: [ + { + value: { + body: "

Lotus

", + value: 0, + }, + }, + { + value: { + body: "

Mango

", + value: 1, + }, + }, + ], + right: [ + { + value: { + body: "

Flower

", + value: 0, + }, + }, + { + value: { + body: "

Fruit

", + value: 1, + }, + }, + ], + }, + }, + qType: "MTF", + primaryCategory: "Match The Following Question", + }, +}; diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts index 294e61b1c..79e1a79b7 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts @@ -1,23 +1,21 @@ import { TelemetryInteractDirective } from '../../directives/telemetry-interact/telemetry-interact.directive'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { HttpClientTestingModule } from '@angular/common/http/testing'; -import { FormsModule } from '@angular/forms'; import { MatchComponent } from './match.component'; -import { CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; +import { mockOptionData } from './match.component.spec.data'; +import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange} from '@angular/core'; import { ConfigService } from '../../services/config/config.service'; -import { SuiModule } from 'ng2-semantic-ui-v9'; -import { TreeService } from '../../services/tree/tree.service'; import { EditorTelemetryService } from '../../services/telemetry/telemetry.service'; -describe('OptionsComponent', () => { +describe('MatchComponent', () => { let component: MatchComponent; let fixture: ComponentFixture; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ - imports: [HttpClientTestingModule, FormsModule, SuiModule], + imports: [HttpClientTestingModule], declarations: [MatchComponent, TelemetryInteractDirective], - providers: [ConfigService, TreeService, EditorTelemetryService,], + providers: [ConfigService, EditorTelemetryService,], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) .compileComponents(); @@ -26,10 +24,117 @@ describe('OptionsComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(MatchComponent); component = fixture.componentInstance; + component.editorState = mockOptionData.editorOptionData; // fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); + + it("#ngOnInit() should call editorDataHandler on ngOnInit", () => { + component.editorState = mockOptionData.editorOptionData; + spyOn(component, "editorDataHandler"); + component.ngOnInit(); + expect(component.editorDataHandler).toHaveBeenCalled(); + }); + + it("should not set #templateType when creating new question", () => { + component.editorState = {}; + spyOn(component, "editorDataHandler"); + component.ngOnInit(); + expect(component.templateType).toEqual("mtf-horizontal"); + }); + + it("should set #templateType when updating an existing question", () => { + component.editorState = mockOptionData.editorOptionData; + spyOn(component, "editorDataHandler"); + component.ngOnInit(); + expect(component.templateType).toEqual("mtf-vertical"); + }); + + it("ngOnChanges should not call editorDataHandler", () => { + spyOn(component, "editorDataHandler").and.callFake(() => {}); + spyOn(component, "ngOnChanges").and.callThrough(); + component.ngOnChanges({ + maxScore: new SimpleChange(undefined, 4, true), + }); + expect(component.editorDataHandler).not.toHaveBeenCalled(); + }); + + it("ngOnChanges should call editorDataHandler", () => { + spyOn(component, "editorDataHandler").and.callFake(() => {}); + spyOn(component, "ngOnChanges").and.callThrough(); + component.ngOnChanges({ + maxScore: new SimpleChange(1, 2, false), + }); + expect(component.editorDataHandler).toHaveBeenCalled(); + }); + + it('#editorDataHandler() should emit option data', () => { + spyOn(component, 'prepareMtfBody').and.callThrough(); + spyOn(component.editorDataOutput, 'emit').and.callThrough(); + component.editorState = mockOptionData.editorOptionData; + component.editorState.correctMatchPair = [{ "0": 0 }, { "1": 1 }]; + component.editorDataHandler(); + expect(component.prepareMtfBody).toHaveBeenCalledWith(mockOptionData.editorOptionData); + expect(component.editorDataOutput.emit).toHaveBeenCalled(); + }); + + it("#prepareMtfBody() should return expected mtf option data for MTF", () => { + component.maxScore = 4; + spyOn(component, 'setMapping').and.callThrough(); + spyOn(component, "getResponseDeclaration").and.callThrough(); + spyOn(component, "getInteractions").and.callThrough(); + const result = component.prepareMtfBody(mockOptionData.editorOptionData); + expect(component.getResponseDeclaration).toHaveBeenCalledWith( + mockOptionData.editorOptionData + ); + expect(component.getInteractions).toHaveBeenCalledWith( + mockOptionData.editorOptionData.options + ); + }); + + it('#getInteractions should return expected interactions', () => { + spyOn(component, 'getInteractions').and.callThrough(); + const result = component.getInteractions(mockOptionData.editorOptionData.options); + expect(result).toEqual(mockOptionData.prepareMtfBody.interactions); + }) + + it('#setMapping should set mapping', () => { + spyOn(component, 'setMapping').and.callThrough(); + component.editorState = mockOptionData.editorOptionData; + component.editorState.correctMatchPair = mockOptionData.editorOptionData.correctMatchPair; + component.maxScore = 4; + component.setMapping(); + expect(component.mapping).toEqual(mockOptionData.prepareMtfBody.responseDeclaration.response1.mapping); + }) + + it('#getOutcomeDeclaration should return expected outcomeDeclaration', () => { + component.maxScore = 4; + spyOn(component, 'getOutcomeDeclaration').and.callThrough(); + const outcomeDeclaration = component.getOutcomeDeclaration(); + expect(outcomeDeclaration.maxScore.cardinality).toEqual('multiple'); + expect(outcomeDeclaration.maxScore.defaultValue).toEqual(4); + }) + + it('#getResponseDeclaration should return expected responseDeclaration', () => { + component.mapping = [ + { + value: { + "0": 0, + }, + score: 2, + }, + { + value: { + "1": 1, + }, + score: 2, + }, + ]; + spyOn(component, "getResponseDeclaration").and.callThrough(); + const responseDeclaration = component.getResponseDeclaration(mockOptionData.editorOptionData); + expect(responseDeclaration.response1.cardinality).toEqual('multiple'); + }) }); \ No newline at end of file diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.ts b/projects/questionset-editor-library/src/lib/components/match/match.component.ts index 2c479610c..1336531bc 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.ts +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.ts @@ -2,8 +2,6 @@ import { Component, Input, OnInit, OnChanges, Output, EventEmitter, SimpleChange import * as _ from 'lodash-es'; import { EditorTelemetryService } from '../../services/telemetry/telemetry.service'; import { ConfigService } from '../../services/config/config.service'; -import { TreeService } from '../../services/tree/tree.service'; -import { EditorService } from '../../services/editor/editor.service'; @Component({ selector: 'lib-match', @@ -21,13 +19,11 @@ export class MatchComponent implements OnInit, OnChanges { @Output() editorDataOutput: EventEmitter = new EventEmitter(); public setCharacterLimit = 160; public setImageLimit = 1; - public templateType = 'default'; - parentMeta: any; + public templateType = 'mtf-horizontal'; + constructor( public telemetryService: EditorTelemetryService, public configService: ConfigService, - public treeService: TreeService, - private editorService: EditorService ) {} ngOnInit() { @@ -53,7 +49,7 @@ export class MatchComponent implements OnInit, OnChanges { } prepareMtfBody(editorState) { let metadata: any; - if (_.isEmpty(editorState.correctMatchPair) && !_.isEmpty(editorState.options)) { + if (!_.isEmpty(editorState.options)) { editorState.correctMatchPair = editorState.options.map((option, index) => { const correctMatchPair = {}; correctMatchPair[index.toString()] = index; @@ -64,18 +60,18 @@ export class MatchComponent implements OnInit, OnChanges { let options: any; if (!_.isEmpty(editorState.correctMatchPair)) { options = { - leftOptions: editorState.options.map((option, index) => { + left: editorState.options.map((option, index) => { return { value: { - body: option.leftOption, + body: option.left, value: index, } } }), - rightOptions: editorState.options.map((option, index) => { + right: editorState.options.map((option, index) => { return { value: { - body: option.rightOption, + body: option.right, value: index, } } @@ -144,12 +140,12 @@ export class MatchComponent implements OnInit, OnChanges { getInteractions(options) { const optionSet = { - leftOptions: options.map((option,index) => ({ - label: option.leftOption, + left: options.map((option,index) => ({ + label: option.left, value: index, })), - rightOptions: options.map((option,index) => ({ - label: option.rightOption, + right: options.map((option,index) => ({ + label: option.right, value: index, })), } @@ -161,15 +157,4 @@ export class MatchComponent implements OnInit, OnChanges { }; return interactions; } - - setScore(value, scoreIndex) { - const obj = { - response: scoreIndex, - outcomes: { - score: value, - }, - }; - this.mapping[scoreIndex] = obj; - this.editorDataHandler(); - } } diff --git a/projects/questionset-editor-library/src/lib/components/options/options.component.ts b/projects/questionset-editor-library/src/lib/components/options/options.component.ts index d317eccf9..daf368fc5 100644 --- a/projects/questionset-editor-library/src/lib/components/options/options.component.ts +++ b/projects/questionset-editor-library/src/lib/components/options/options.component.ts @@ -162,7 +162,7 @@ export class OptionsComponent implements OnInit, OnChanges { score: scoreForEachOption, } this.mapping.push(optionMapping) - }) + }) } else { this.mapping = []; } @@ -171,9 +171,9 @@ export class OptionsComponent implements OnInit, OnChanges { getInteractions(options) { let index; const interactOptions = _.map(options, (opt, key) => { - index = Number(key); - const hints = _.get(this.editorState, `interactions.response1.options[${index}].hints`) - return { label: opt.body, value: index, hints }; + index = Number(key); + const hints = _.get(this.editorState, `interactions.response1.options[${index}].hints`) + return { label: opt.body, value: index, hints }; }); this.subMenuConfig(options); const interactions = { @@ -181,7 +181,7 @@ export class OptionsComponent implements OnInit, OnChanges { type: 'choice', options: interactOptions, }, - }; + }; return interactions; } diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.ts index 6ab00be78..07ab2b238 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.ts @@ -253,9 +253,9 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { const maximumOptions = _.get(this.questionInput, 'config.maximumOptions'); this.editorService.optionsLength = numberOfOptions; // converting the options to the format required by the editor - const options = _.map(this.questionMetaData?.editorState?.options?.leftOption, (leftOption, index) => ({ - leftOption, - rightOption:this.questionMetaData?.editorState?.options?.rightOption?.[index] + const options = _.map(this.questionMetaData?.editorState?.options?.left, (left, index) => ({ + left, + right:this.questionMetaData?.editorState?.options?.right?.[index] })); const question = this.questionMetaData?.editorState?.question; const interactions = this.questionMetaData?.interactions; @@ -628,8 +628,8 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } else { this.showFormError = false; } - const rightOptionValid = _.find(this.editorState.options, option => (option.rightOption === undefined || option.rightOption === '' || option.rightOption.length > this.setCharacterLimit)); - const leftOptionValid = _.find(this.editorState.options, option => (option.leftOption === undefined || option.leftOption === '' || option.leftOption.length > this.setCharacterLimit)); + const rightOptionValid = _.find(this.editorState.options, option => (option.right === undefined || option.right === '' || option.right.length > this.setCharacterLimit)); + const leftOptionValid = _.find(this.editorState.options, option => (option.left === undefined || option.left === '' || option.left.length > this.setCharacterLimit)); if (rightOptionValid || leftOptionValid || (_.isUndefined(this.editorState.correctMatchPair) && this.sourcingSettings?.enforceCorrectAnswer)) { this.showFormError = true; return; @@ -851,10 +851,10 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } if (this.questionInteractionType === 'match') { - metadata.body = this.getMatchQuestionHtmlBody(this.editorState.question); - const leftOptions = metadata.interactions.response1.options.leftOptions; - const rightOptions = metadata.interactions.response1.options.rightOptions; - metadata.correctMatchPair = this.getMatchAnswerContainerHtml(leftOptions, rightOptions); + metadata.body = this.getMtfQuestionHtmlBody(this.editorState.question, this.editorState.templateId); + const leftOptions = metadata.interactions.response1.options.left; + const rightOptions = metadata.interactions.response1.options.right; + metadata.correctMatchPair = this.getMtfAnswerContainerHtml(leftOptions, rightOptions); } if (!_.isUndefined(this.selectedSolutionType) && !_.isEmpty(this.selectedSolutionType)) { @@ -886,7 +886,7 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { return optionHtml; } - getMatchAnswerContainerHtml(leftOptions, rightOptions) { + getMtfAnswerContainerHtml(leftOptions, rightOptions) { const matchContainerTemplate = '
{leftOptions}{rightOptions}
'; const leftOptionsHtml = this.getOptionWrapperHtml(leftOptions, 'left'); const rightOptionsHtml = this.getOptionWrapperHtml(rightOptions, 'right'); @@ -897,18 +897,18 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } getOptionWrapperHtml(options, type) { - const wrapperTemplate = `
{options}
`; + const wrapperTemplate = `
{options}
`; let optionsHtml = ''; options.forEach((option) => { - const optionHtml = this.getMatchAnswerHtml(option.label, option.value); + const optionHtml = this.getMtfAnswerHtml(option.label, type); optionsHtml = optionsHtml.concat(optionHtml); }); const wrapper = wrapperTemplate.replace('{options}', optionsHtml); return wrapper; } - getMatchAnswerHtml(label, value) { - const answerHtml = '
{label}
'; - const optionHtml = answerHtml.replace('{label}', label).replace('{value}', value); + getMtfAnswerHtml(label, type) { + const answerHtml = `
{label}
`; + const optionHtml = answerHtml.replace('{label}', label); return optionHtml; } @@ -955,13 +955,13 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { return videoSolutionValue; } - getMatchQuestionHtmlBody(question) { + getMtfQuestionHtmlBody(question, templateId) { const matchTemplateConfig = { // tslint:disable-next-line:max-line-length - matchBody: '
{question}
' + matchBody: '
{question}
' }; const { matchBody } = matchTemplateConfig; - const questionBody = matchBody.replace('{question}', question); + const questionBody = matchBody.replace('{templateClass}', templateId).replace('{question}', question); return questionBody; } diff --git a/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts b/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts index 26077c4e4..67713877e 100644 --- a/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts +++ b/projects/questionset-editor-library/src/lib/interfaces/MtfForm.ts @@ -1,7 +1,7 @@ import * as _ from "lodash-es"; export class MtfOption { - constructor(public leftOption: string, public rightOption: string) {} + constructor(public left: string, public right: string) {} } export interface MtfData { diff --git a/projects/questionset-editor-library/src/lib/services/config/label.config.json b/projects/questionset-editor-library/src/lib/services/config/label.config.json index 6ecd46581..ef7a67d23 100644 --- a/projects/questionset-editor-library/src/lib/services/config/label.config.json +++ b/projects/questionset-editor-library/src/lib/services/config/label.config.json @@ -47,199 +47,199 @@ "remove_btn_label":"Remove", "done_btn_label":"Done", "add_translation_label":"Add Translation", - "add_page_numbers_to_questions_btn_label": "Pagination" + "add_page_numbers_to_questions_btn_label": "Pagination", + "delete_pair_btn_label":"Delete Pair" }, "lbl":{ - "search": "Search", - "subject": "Subject", - "medium": "Medium", - "gradeLevel": "Class", - "contentType": "Content Type", - "reset": "Reset", - "apply": "apply", - "filterText": "Change Filters", - "Questiondetails": "Question details", - "selectContent": "Select content", - "noMatchingContent": "Sorry there is no matching content", - "changeFilterMessage": "Changing filter helps you find more content", - "changeFilter": "Change filters", - "whereDoYouWantToAddThisContent": "Where do you want to add this content?", - "selectContentToAdd": "Use search and filters above to find more content", - "addedToCollection": "Added to collection", - "changingFilters": "Changing filters make you find more content", - "ChangeFilters": "Change filters", - "addFromLibrary": "Add from Library", - "showContentAddedToCollection": "Show content added to collection", - "addContent": "Add content", - "sortBy": "Sort By", - "sortlabel": "A - Z", - "viewOnOrigin": "View Content on consumption", - "answers": "Answers", - "answersRequired": "Answer is required", - "answersPopupText": "Please provide an answer for the question. Check preview to understand how it would look.", - "selectImage": "Select Image", - "myImages": "My Images", - "allImage": "All Image", - "uploadAndUse": "Upload and Use", - "chooseOrDragImage": "Choose or drag and drop your image here", - "chooseOrDragVideo": "Choose or drag and drop your video here", - "uploadFromComputer": "Upload from Computer", - "upload": "Upload", - "maxFileSize": "Max File size:", - "allowedFileTypes": "Allowed file types are:", - "maximumAllowedFileSize": "Maximum allowed file size:", - "copyRightsAndLicense": "Copyright & License", - "dropChooseFile": "Drop or choose file to upload before entering the details", - "charactersLeft": "Characters left:", - "myVideos": "My Video(s)", - "allVideos": "All Video(s)", - "selectVideo": "Select Video", - "searchPlaceholder": "Search...", - "addAnImage": "Add an image", - "name": "Name", - "copyRightsAndYear": "Copyright & Yrar", - "license": "License", - "author": "Author", - "grade": "Grade", - "board": "Board", - "audience": "Audience", - "copyRight": "Copyright", - "licensedBy": "Licensed by", - "attributions": "Attributions", - "requestForQrCode": "Request for QR Codes", - "confirmDeleteContent": "Confirm Delete Content", - "confirmDeleteNode": "Are you sure want to delete the selected Node?", - "comments": "Comments", - "reviewComments": "Review Comments", - "questionSetPreview": "Question Set Preview", - "numberToolarge": "This number is too large for the request", - "acceptTerms": "Accepting Terms & Conditions", - "iAgreeSubmit": "I agree that by submitting / publishing this Content,", - "iconfirmContent": "I confirm that this Content complies with prescribed guidelines, including the Terms of Use and Content Policy and that I consent to publish it under the", - "createCommonFramework": "Creative Commons Framework in", - "accordance": "accordance with the", - "contentPolicy": "Content Policy.", - "privacyRights": "I have made sure that I do not violate others’ copyright or privacy rights.", - "viewComments": "View Comments", - "addReviewComments": "Add Review Comments", - "enterYourComments": "Enter your comments", - "publishCollection": "Publish ${objectType}", - "confirmPublishCollection": "Are you sure you want to publish this ${objectType}?", - "fillComments": "Fill comments", - "searchLibrary": "Search Library", - "options": "Options", - "optionsPopupText": "Please Preview to check how layout looks. Layout is responsive to the resolution of your device", - "selectOneAns": "Select one correct answer", - "fillThisOption": "Fill this option", - "reduceSize": "Please reduce the size", - "correctAns": "Correct answer", - "correctMatch": "Check if all match pairs are correct", - "addOption": "Add option", - "addPair": "Add pair", - "question": "Question", - "pageNumber": "Page No", - "setAnswers": "Set your answers", - "addQuestionAnswerPairText": "Add question-answer pairs to your question. Answers will be shuffled automatically", - "confirmQuestionNotSaved": "This question will not be saved, are you sure you want to go back to questionset?", - "video": "Video", - "textImage": "Text+Image", - "chooseType": "Choose type", - "solution": "Solution", - "optional": "(Optional)", - "questionRequired": "Question is required", - "addingTo": "Adding To", - "selectTemplate": "Select a template to get started", - "createNew": "Create new", - "selectLayout": "Select Layout", - "vertical": "Vertical", - "grid": "Grid", - "horizontal": "Horizontal", - "folders": "Folders", - "createHierarchyCsv": "Create folders using csv file", - "downloadFoldersInCSV": "Download folders as csv file", - "uploadUpdateCSV": "Update folder metadata using csv file", - "downloadSampleHierarchyCSv": "Upload csv file as per the given sample to create folders", - "makeSureFile": "Make sure that:", - "allColumnsAreAvailable": "The file has all the required columns", - "hasAllMandatoryColumn": "The file has all the required values filled in as per the format", - "noDuplicateRow": "There are no duplicate rows (with exactly same folder levels) in the file", - "downloadSampleCSV": "Download sample csv file", - "dragAndDropCSV": "Drag and Drop files to upload", - "selectFileToUpload": "Select file to upload", - "uploadEntries": "File accepted - csv", - "Or": "Or", - "pleaseWait": "Please wait", - "validateCsvFile": "Validating CSV file", - "hierarchyValidationError": "Error in processing the file", - "followingErrors": "Following errors are found in the file. Please correct and upload again", - "reUploadCSV": "Upload file again", - "hierarchyValidation": "Hierarchy Validation", - "hierarchyAdded": "Folders have been successfully created. Please close the dialog", - "hierarchyUpdated": "Folder metadata has been successfully updated. Please close the dialog", - "successful": "Successful !", - "csvDownloadInstruction": "Please make sure that this is the file downloaded using the “Download folders as csv file” option and changes are made to it", - "collaborators": "Collaborators", - "addCollaborators": "Add Collaborators", - "manageCollaborators": "Manage Collaborators", - "sliderValue": "Slider Value", - "left": "Left", - "stepSize": "Step size", - "right": "Right", - "translation": "Translation", - "publishchecklistTitle": "Please confirm that ALL the following items are verified (by ticking the check-boxes) before you can publish:", - "bulkUploadErrorMessage": "The metadata file has following errors. Please check and upload again", - "bulkUploadQuestion": "Bulk Upload Question", - "lastUploaded": "Last uploaded", - "bulkInProgress": "Bulk upload in progress", - "viewDetails": "View Details", - "downloadSampleMetadataCsvFileAndCreate": "Download sample metadata CSV file and create your own metadata file using the format", - "makeSureYourFile": "Make sure your file", - "allColumnsAreAvailableShownFormat": "All columns are available as shown in format.", - "hasAllMandatoryColumnsFilledAsMarkedInFormat": "Has all mandatory columns filled, as marked in the format", - "hasNoDuplicateUrlsFilepathColumn": "Has no duplicate URLs in the filepath column", - "downloadSampleMetadataCsv": "Download sample metadata CSV", - "processingDroppedFiles": "Processing dropped files...", - "retry": "retry", - "dragAndDrop": "Drag and Drop file", - "or": "or", - "selectFile": "Select file", - "uploadCSVXlEntries": "Upload csv upto 300 entries", - "no": "No", - "yes": "Yes", - "cancel": "Cancel", - "ok": "OK", - "validatingCSVFile": "Validating CSV file", - "metadataFileValidationFailed": "Metadata file validation failed.", - "metadataFollowingError": "The metadata file has following errors. Please check and upload again", - "uploadingYourContentFromCSV": "Uploading your question from CSV", - "uploadFail": "Upload failed", - "uploadSuccessful": "Upload successful", - "uploadRemaining": "Upload remaining", - "bulkUploadComplete": "Bulk upload complete!", - "contentUploaded": "Question uploaded", - "downloadReport": "Download report", - "next": "Next", - "back": "Back", - "close": "Close", - "bulkUploadNoticeLine1": "Uploading in bulk may take some time, click on", - "bulkUploadNoticeLine2": "to continue using Vidyadaan.", - "bulkUploadNoticeLine3": "Your upload will keep running in the background.", - "alreadyContentPresent": "Already present in this folder", - "loaderHeading": "Please wait", - "loaderMessage": "We are fetching details.", - "slidervalue": "Set the minimum and maximum values with which the slider would start and end respectively.", - "stepSizeInfo": "The step size would define the gap/jump between two values on the slider.", - "minSizeInfo": "The minimum slider value", - "maxSizeInfo": "The maximum slider value", - "questionsetAddFromLibraryItemLabel": "question", - "questionsetAddFromLibraryCollectionLabel": "question set", - "marks": "Marks", - "shuffleOnMessage": "Each question will carry equal weightage of 1 mark when using Shuffle. To provide different weightage to individual questions please turn off Shuffle.", - "editingConsentNote": "I confirm that I am allowing my reviewer to make edits to the content and metadata of the content. I consent my reviewer make changes, if any and publish the contain thereafter.", - "acceptBothConsentNote": "Agree to both conditions", - "totalScore": "Total Score", - "qualityReview": "If the score is less than or equal to 15 please re-evaluate the question and send back for corrections." - }, + "search":"Search", + "subject":"Subject", + "medium":"Medium", + "gradeLevel":"Class", + "contentType":"Content Type", + "reset":"Reset", + "apply":"apply", + "filterText":"Change Filters", + "Questiondetails":"Question details", + "selectContent":"Select content", + "noMatchingContent":"Sorry there is no matching content", + "changeFilterMessage":"Changing filter helps you find more content", + "changeFilter":"Change filters", + "whereDoYouWantToAddThisContent":"Where do you want to add this content?", + "selectContentToAdd":"Use search and filters above to find more content", + "addedToCollection":"Added to collection", + "changingFilters":"Changing filters make you find more content", + "ChangeFilters":"Change filters", + "addFromLibrary":"Add from Library", + "showContentAddedToCollection":"Show content added to collection", + "addContent":"Add content", + "sortBy":"Sort By", + "sortlabel":"A - Z", + "viewOnOrigin":"View Content on consumption", + "answers":"Answers", + "answersRequired":"Answer is required", + "answersPopupText":"Please provide an answer for the question. Check preview to understand how it would look.", + "selectImage":"Select Image", + "myImages":"My Images", + "allImage":"All Image", + "uploadAndUse":"Upload and Use", + "chooseOrDragImage":"Choose or drag and drop your image here", + "chooseOrDragVideo":"Choose or drag and drop your video here", + "uploadFromComputer":"Upload from Computer", + "upload":"Upload", + "maxFileSize":"Max File size:", + "allowedFileTypes":"Allowed file types are:", + "maximumAllowedFileSize":"Maximum allowed file size:", + "copyRightsAndLicense":"Copyright & License", + "dropChooseFile":"Drop or choose file to upload before entering the details", + "charactersLeft":"Characters left:", + "myVideos":"My Video(s)", + "allVideos":"All Video(s)", + "selectVideo":"Select Video", + "searchPlaceholder":"Search...", + "addAnImage":"Add an image", + "name":"Name", + "copyRightsAndYear":"Copyright & Yrar", + "license":"License", + "author":"Author", + "grade":"Grade", + "board":"Board", + "audience":"Audience", + "copyRight":"Copyright", + "licensedBy":"Licensed by", + "attributions":"Attributions", + "requestForQrCode":"Request for QR Codes", + "confirmDeleteContent":"Confirm Delete Content", + "confirmDeleteNode":"Are you sure want to delete the selected Node?", + "comments":"Comments", + "reviewComments":"Review Comments", + "questionSetPreview":"Question Set Preview", + "numberToolarge": "This number is too large for the request", + "acceptTerms":"Accepting Terms & Conditions", + "iAgreeSubmit":"I agree that by submitting / publishing this Content,", + "iconfirmContent":"I confirm that this Content complies with prescribed guidelines, including the Terms of Use and Content Policy and that I consent to publish it under the", + "createCommonFramework":"Creative Commons Framework in", + "accordance":"accordance with the", + "contentPolicy":"Content Policy.", + "privacyRights":"I have made sure that I do not violate others’ copyright or privacy rights." , + "viewComments":"View Comments", + "addReviewComments":"Add Review Comments", + "enterYourComments":"Enter your comments", + "publishCollection":"Publish ${objectType}", + "confirmPublishCollection":"Are you sure you want to publish this ${objectType}?", + "fillComments":"Fill comments", + "searchLibrary":"Search Library", + "options":"Options", + "optionsPopupText":"Please Preview to check how layout looks. Layout is responsive to the resolution of your device", + "selectOneAns":"Select one correct answer", + "fillThisOption":"Fill this option", + "reduceSize":"Please reduce the size", + "correctAns":"Correct answer", + "addOption":"Add option", + "question":"Question", + "pageNumber":"Page No", + "confirmQuestionNotSaved":"This question will not be saved, are you sure you want to go back to questionset?", + "video":"Video", + "textImage":"Text+Image", + "chooseType":"Choose type", + "solution":"Solution", + "optional":"(Optional)", + "questionRequired":"Question is required", + "addingTo":"Adding To", + "selectTemplate":"Select a template to get started", + "createNew":"Create new", + "selectLayout":"Select Layout", + "vertical":"Vertical", + "grid":"Grid", + "horizontal":"Horizontal", + "folders":"Folders", + "createHierarchyCsv":"Create folders using csv file", + "downloadFoldersInCSV":"Download folders as csv file", + "uploadUpdateCSV":"Update folder metadata using csv file", + "downloadSampleHierarchyCSv":"Upload csv file as per the given sample to create folders", + "makeSureFile":"Make sure that:", + "allColumnsAreAvailable":"The file has all the required columns", + "hasAllMandatoryColumn":"The file has all the required values filled in as per the format", + "noDuplicateRow":"There are no duplicate rows (with exactly same folder levels) in the file", + "downloadSampleCSV":"Download sample csv file", + "dragAndDropCSV":"Drag and Drop files to upload", + "selectFileToUpload":"Select file to upload", + "uploadEntries":"File accepted - csv", + "Or":"Or", + "pleaseWait":"Please wait", + "validateCsvFile":"Validating CSV file", + "hierarchyValidationError":"Error in processing the file", + "followingErrors":"Following errors are found in the file. Please correct and upload again", + "reUploadCSV":"Upload file again", + "hierarchyValidation":"Hierarchy Validation", + "hierarchyAdded":"Folders have been successfully created. Please close the dialog", + "hierarchyUpdated":"Folder metadata has been successfully updated. Please close the dialog", + "successful":"Successful !", + "csvDownloadInstruction":"Please make sure that this is the file downloaded using the “Download folders as csv file” option and changes are made to it", + "collaborators": "Collaborators", + "addCollaborators": "Add Collaborators", + "manageCollaborators": "Manage Collaborators", + "sliderValue":"Slider Value", + "left":"Left", + "stepSize":"Step size", + "right":"Right", + "translation":"Translation", + "publishchecklistTitle": "Please confirm that ALL the following items are verified (by ticking the check-boxes) before you can publish:", + "bulkUploadErrorMessage" : "The metadata file has following errors. Please check and upload again", + "bulkUploadQuestion": "Bulk Upload Question", + "lastUploaded": "Last uploaded", + "bulkInProgress": "Bulk upload in progress", + "viewDetails": "View Details", + "downloadSampleMetadataCsvFileAndCreate": "Download sample metadata CSV file and create your own metadata file using the format", + "makeSureYourFile": "Make sure your file", + "allColumnsAreAvailableShownFormat": "All columns are available as shown in format.", + "hasAllMandatoryColumnsFilledAsMarkedInFormat": "Has all mandatory columns filled, as marked in the format", + "hasNoDuplicateUrlsFilepathColumn": "Has no duplicate URLs in the filepath column", + "downloadSampleMetadataCsv": "Download sample metadata CSV", + "processingDroppedFiles" : "Processing dropped files...", + "retry": "retry", + "dragAndDrop": "Drag and Drop file", + "or" : "or", + "selectFile": "Select file", + "uploadCSVXlEntries": "Upload csv upto 300 entries", + "no": "No", + "yes": "Yes", + "cancel": "Cancel", + "ok": "OK", + "validatingCSVFile": "Validating CSV file", + "metadataFileValidationFailed": "Metadata file validation failed.", + "metadataFollowingError": "The metadata file has following errors. Please check and upload again", + "uploadingYourContentFromCSV": "Uploading your question from CSV", + "uploadFail" : "Upload failed", + "uploadSuccessful" : "Upload successful", + "uploadRemaining" : "Upload remaining", + "bulkUploadComplete" : "Bulk upload complete!", + "contentUploaded" : "Question uploaded", + "downloadReport": "Download report", + "next": "Next", + "back": "Back", + "close": "Close", + "bulkUploadNoticeLine1": "Uploading in bulk may take some time, click on", + "bulkUploadNoticeLine2": "to continue using Vidyadaan.", + "bulkUploadNoticeLine3": "Your upload will keep running in the background.", + "alreadyContentPresent": "Already present in this folder", + "loaderHeading": "Please wait", + "loaderMessage": "We are fetching details.", + "slidervalue": "Set the minimum and maximum values with which the slider would start and end respectively.", + "stepSizeInfo": "The step size would define the gap/jump between two values on the slider.", + "minSizeInfo": "The minimum slider value", + "maxSizeInfo": "The maximum slider value", + "questionsetAddFromLibraryItemLabel": "question", + "questionsetAddFromLibraryCollectionLabel": "question set", + "marks": "Marks", + "shuffleOnMessage": "Each question will carry equal weightage of 1 mark when using Shuffle. To provide different weightage to individual questions please turn off Shuffle.", + "editingConsentNote": "I confirm that I am allowing my reviewer to make edits to the content and metadata of the content. I consent my reviewer make changes, if any and publish the contain thereafter.", + "acceptBothConsentNote": "Agree to both conditions", + "totalScore": "Total Score", + "qualityReview": "If the score is less than or equal to 15 please re-evaluate the question and send back for corrections.", + "addPair": "Add pair", + "setAnswers": "Set your answers", + "addQuestionAnswerPairText": "Add question-answer pairs to your question. Answers will be shuffled automatically" + }, "err":{ "somethingWentWrong":"Something went wrong", "contentNotFoundonOrigin": "The content not found in consumption", From 1cda982b9ca5a1c31a37b13dd616cdd1a707bf4a Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Tue, 8 Aug 2023 17:21:25 +0530 Subject: [PATCH 22/39] Added interfaces in sonar exclusion --- sonar-project.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index 9f8441c8f..0c80de60d 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,7 +1,7 @@ sonar.projectName=sunbird-questionset-editor sonar.language=ts sonar.sources=projects/questionset-editor-library/src -sonar.exclusions=projects/questionset-editor-library/src/lib/assets/**,projects/questionset-editor-library/src/lib/**/*.spec.ts,projects/questionset-editor-library/src/lib/**/*.spec.data.ts,projects/questionset-editor-library/src/lib/**/*.module.ts +sonar.exclusions=projects/questionset-editor-library/src/lib/assets/**,projects/questionset-editor-library/src/lib/**/*.spec.ts,projects/questionset-editor-library/src/lib/**/*.spec.data.ts,projects/questionset-editor-library/src/lib/**/*.module.ts,projects/questionset-editor-library/src/lib/interfaces/* sonar.javascript.lcov.reportPaths=projects/questionset-editor-library/coverage/lcov.info sonar.projectKey=Sunbird-inQuiry_editor sonar.host.url=https://sonarcloud.io From d068f4f277bc2b86a39ff113718f22f018bb3757 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Wed, 9 Aug 2023 16:00:00 +0530 Subject: [PATCH 23/39] Added tests for new functions in question component --- .../question/question.component.spec.ts | 182 +++++++++++++++++- 1 file changed, 180 insertions(+), 2 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts index 84b477943..592f7855d 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts @@ -25,6 +25,7 @@ import { BranchingLogic, mockEditorCursor, interactionChoiceEditorState, + interactionMatchEditorState, RubricData, videoSolutionObject, mediaVideoArray @@ -292,7 +293,7 @@ describe("QuestionComponent", () => { component.previewFormData(true); expect(component.initialize).toHaveBeenCalled(); }); - + it("#initialize should call when question page for question mcq api fail", () => { spyOn(component, "initialize").and.callThrough(); component.questionId = "do_11330103476396851218"; @@ -313,7 +314,44 @@ describe("QuestionComponent", () => { component.initialize(); expect(component.initialize).toHaveBeenCalled(); }); - + + it("#initialize should call when question page for question mtf", () => { + component.initialLeafFormConfig = leafFormConfigMock; + component.leafFormConfig = leafFormConfigMock; + component.questionFormConfig=leafFormConfigMock; + spyOn(component, "initialize").and.callThrough(); + component.questionId = "do_11330103476396851218"; + editorService.parentIdentifier = undefined; + component.questionPrimaryCategory = undefined; + spyOn(editorService, "getToolbarConfig").and.returnValue({ + title: "abcd", + showDialcode: "No", + showPreview: "false", + }); + component.toolbarConfig.showPreview = false; + spyOn(editorService, "fetchCollectionHierarchy").and.callFake(() => { + return of(collectionHierarchyMock); + }); + component.questionId = "do_127"; + component.questionSetHierarchy = collectionHierarchyMock.result.questionset; + spyOn(questionService, "readQuestion").and.returnValue( + of(mockData.mtfQuestionMetaData) + ); + component.questionMetaData = mockData.mtfQuestionMetaData.result.question; + component.questionInteractionType = "match"; + component.scoreMapping = + mockData.mcqQuestionMetaData.result.question.responseDeclaration.response1.mapping; + component.sourcingSettings = sourcingSettingsMock; + component.questionInput.setChildQuestion = false; + component.editorState.solutions = [{ + id: '1', + type: 'vedio' + }] + component.initialize(); + component.previewFormData(true); + expect(component.initialize).toHaveBeenCalled(); + }); + it("#initialize should call when question page for question slider", () => { spyOn(component, "initialize").and.callThrough(); component.questionId = "do_11330103476396851218"; @@ -479,6 +517,54 @@ describe("QuestionComponent", () => { expect(component.setQuestionTitle).toHaveBeenCalled(); }); + it("#initialize should call when question page for question mtf with interactionTypes", () => { + component.questionSetId = "do_1278"; + spyOn(editorService, "fetchCollectionHierarchy").and.callFake(() => { + return of(collectionHierarchyMock); + }); + editorService.parentIdentifier = undefined; + component.questionId = "do_11330103476396851218"; + component.leafFormConfig = leafFormConfigMock; + spyOn(questionService, "readQuestion").and.returnValue( + of(mockData.mtfQuestionMetaData) + ); + spyOn(component, 'setQuestionTitle').and.callFake(() => {}); + spyOn(component, 'populateFormData').and.callFake(() => {}); + component.leafFormConfig = leafFormConfigMock; + spyOn(component, "initialize").and.callThrough(); + component.initialize(); + expect(component.initialize).toHaveBeenCalled(); + expect(component.questionPrimaryCategory).toBeDefined(); + expect(component.questionInteractionType).toBeDefined(); + expect(component.populateFormData).toHaveBeenCalled(); + expect(component.setQuestionTitle).toHaveBeenCalled(); + }); + + it("#initialize should call when question page for question mtf without interactionTypes", () => { + let questionMetadata = mockData.mtfQuestionMetaData.result.question; + questionMetadata = _.omit(questionMetadata, ['interactionTypes', 'primaryCategory']) + component.questionSetId = "do_1278"; + spyOn(editorService, "fetchCollectionHierarchy").and.callFake(() => { + return of(collectionHierarchyMock); + }); + editorService.parentIdentifier = undefined; + component.questionId = "do_11330103476396851218"; + component.leafFormConfig = leafFormConfigMock; + spyOn(questionService, "readQuestion").and.returnValue( + of({result: {question: {questionMetadata}}}) + ); + spyOn(component, 'setQuestionTitle').and.callFake(() => {}); + spyOn(component, 'populateFormData').and.callFake(() => {}); + component.leafFormConfig = leafFormConfigMock; + spyOn(component, "initialize").and.callThrough(); + component.initialize(); + expect(component.initialize).toHaveBeenCalled(); + expect(component.questionPrimaryCategory).toBeUndefined(); + expect(component.questionInteractionType).toEqual("default"); + expect(component.populateFormData).toHaveBeenCalled(); + expect(component.setQuestionTitle).toHaveBeenCalled(); + }); + it("#initialize should call when question page for question slider", () => { spyOn(component, "initialize").and.callThrough(); component.initialLeafFormConfig = leafFormConfigMock; @@ -781,6 +867,12 @@ describe("QuestionComponent", () => { const templateId = "mcq-vertical"; component.getMcqQuestionHtmlBody(question, templateId); }); + + it("call #getMtfQuestionHtmlBody() to verify questionBody", () => { + const question = '
{question}
'; + const templateId = "mtf-horizontal"; + component.getMtfQuestionHtmlBody(question, templateId); + }); it("Unit test for #sendForReview", () => { spyOn(component, "upsertQuestion"); @@ -1169,6 +1261,30 @@ describe("QuestionComponent", () => { expect(metadata['outcomeDeclaration'].maxScore.defaultValue).toEqual(1); }); + it('#getQuestionMetadata() should return question metata when interactionType is match', () => { + component.mediaArr = []; + component.editorState = interactionMatchEditorState; + component.selectedSolutionType = 'video'; + component.creationContext = undefined; + component.questionInteractionType = 'match'; + component.childFormData = { + name: 'MTF', + bloomsLevel: null, + board: 'CBSE', + maxScore: 1 + }; + component.maxScore = 4; + spyOn(component, 'getDefaultSessionContext').and.returnValue({ + creator: 'Vaibahv Bhuva', + createdBy: '5a587cc1-e018-4859-a0a8-e842650b9d64' + } + ); + spyOn(component, 'getQuestionSolution').and.returnValue({}); + spyOn(component, 'getQuestionMetadata').and.callThrough(); + const metadata = component.getQuestionMetadata(); + expect(metadata['outcomeDeclaration'].maxScore.defaultValue).toEqual(4); + }); + it('#getAnswerHtml() should return answer html', () => { spyOn(component, 'getAnswerHtml').and.callThrough(); const answerHtml = component.getAnswerHtml('

Sample Answer

'); @@ -1181,6 +1297,54 @@ describe("QuestionComponent", () => { expect(answerWrappedHtml).toBe('

Sample Answer

'); }); + it('#getMtfAnswerContainerHtml() should return answer html', () => { + spyOn(component, 'getMtfAnswerContainerHtml').and.callThrough(); + const leftOptions = [ + { + label: "

a

", + value: "0", + }, + { + label: "

b

", + value: "1", + }, + ]; + const rightOptions = [ + { + label: "

c

", + value: "0", + }, + { + label: "

d

", + value: "1", + }, + ]; + const matchContainer = component.getMtfAnswerContainerHtml(leftOptions, rightOptions); + expect(matchContainer).toBe('

a

b

c

d

') + }) + + it('#getOptionWrapperHtml() should return wrapper html', () => { + spyOn(component, 'getOptionWrapperHtml').and.callThrough(); + const leftOptions = [ + { + label: "

a

", + value: "0", + }, + { + label: "

b

", + value: "1", + }, + ]; + const wrapperHtml = component.getOptionWrapperHtml(leftOptions, 'left'); + expect(wrapperHtml).toBe('

a

b

'); + }) + + it('#getMtfAnswerHtml() should return answer html', () => { + spyOn(component, 'getMtfAnswerHtml').and.callThrough(); + const answerHtml = component.getMtfAnswerHtml('

Sample Answer

', 'left'); + expect(answerHtml).toBe('

Sample Answer

'); + }) + it('#getInteractionValues() should return correct answer object', () => { spyOn(component, 'getInteractionValues').and.callThrough(); const correctAnswersData = component.getInteractionValues([0], interactionChoiceEditorState.interactions); @@ -1400,6 +1564,20 @@ describe("QuestionComponent", () => { expect(component.showFormError).toBeFalsy(); }); + it("#validateQuestionData() should call validateQuestionData and questionInteractionType is mtf", () => { + component.sourcingSettings = sourcingSettingsMock; + spyOn(treeService, "getFirstChild").and.callFake(() => { + return { data: { metadata: { identifier: "0123",allowScoring:'Yes' } } }; + }); + component.editorState = mockData.mtfQuestionMetaData.result.question; + editorService = TestBed.inject(EditorService); + editorService.editorConfig.renderTaxonomy=false; + component.editorState.question = "

Hi how are you

"; + component.editorState.correctMatchPair = ""; + component.questionInteractionType = "match"; + component.validateQuestionData(); + }); + it("#validateQuestionData() should call validateQuestionData and questionInteractionType is text", () => { component.sourcingSettings = sourcingSettingsMock; component.editorState = mockData.textQuestionNetaData.result.question; From 01707b2b7c90cdc966638d51aca70bc47aa1de49 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Wed, 9 Aug 2023 16:01:29 +0530 Subject: [PATCH 24/39] Mock data for tests --- .../question/question.component.spec.data.ts | 307 ++++++++++++++++++ 1 file changed, 307 insertions(+) diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.spec.data.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.spec.data.ts index bddc88aef..7faf96b97 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.spec.data.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.spec.data.ts @@ -233,6 +233,206 @@ export const mockData = { }, }, }, + mtfQuestionMetaData: { + id: "api.question.read", + ver: "3.0", + ts: "2022-01-31T04:38:30ZZ", + params: { + resmsgid: "597b1b63-7007-435a-8b9d-68127f3c6fa8", + msgid: null, + err: null, + status: "successful", + errmsg: null, + }, + responseCode: "OK", + result: { + question: { + mimeType: "application/vnd.sunbird.question", + media: [], + editorState: { + options: { + left: [ + { + value: { + body: "

LeftOption1

", + value: 0, + }, + }, + { + value: { + body: "

LeftOption2

", + value: 1, + }, + }, + { + value: { + body: "

LeftOption3

", + value: 2, + }, + }, + { + value: { + body: "

LeftOption4

", + value: 3, + }, + }, + ], + right: [ + { + value: { + body: "

RightOption1

", + value: 0, + }, + }, + { + value: { + body: "

RightOption2

", + value: 1, + }, + }, + { + value: { + body: "

RightOption3

", + value: 2, + }, + }, + { + value: { + body: "

RightOption4

", + value: 3, + }, + }, + ], + }, + question: "

MTF Question

", + }, + templateId: "mtf-horizontal", + solutions: {}, + interactions: { + response1: { + type: "match", + options: { + left: [ + { + label: "

LeftOption1

", + value: 0, + }, + { + label: "

LeftOption2

", + value: 1, + }, + { + label: "

LeftOption3

", + value: 2, + }, + { + label: "

LeftOption4

", + value: 3, + }, + ], + right: [ + { + label: "

RightOption1

", + value: 0, + }, + { + label: "

RightOption2

", + value: 1, + }, + { + label: "

RightOption3

", + value: 2, + }, + { + label: "

RightOption4

", + value: 3, + }, + ], + }, + validation: { + required: "Yes", + }, + }, + }, + name: "MTF Question", + responseDeclaration: { + response1: { + cardinality: "multiple", + type: "map", + correctResponse: { + value: [ + { + "0": 0, + }, + { + "1": 1, + }, + { + "2": 2, + }, + { + "3": 3, + }, + ], + }, + mapping: [ + { + value: { + "0": 0, + }, + score: 1, + }, + { + value: { + "1": 1, + }, + score: 1, + }, + { + value: { + "2": 2, + }, + score: 1, + }, + { + value: { + "3": 3, + }, + score: 1, + }, + ], + }, + }, + outcomeDeclaration: { + maxScore: { + cardinality: "multiple", + type: "integer", + defaultValue: 4, + }, + }, + remarks: { + maxLength: 100, + }, + interactionTypes: ["match"], + qType: "MTF", + primaryCategory: "Match The Following Question", + body: "

MTF Question

", + creator: "Arpan Gupta", + createdBy: "5a587cc1-e018-4859-a0a8-e842650b9d64", + board: "CBSE", + medium: ["English"], + gradeLevel: ["Grade 1"], + subject: ["English"], + topic: ["Forest"], + author: "check1@yopmail.com", + channel: "01309282781705830427", + framework: "nit_k-12", + license: "CC BY 4.0", + maxScore: "4", + identifier: "", + }, + }, + }, sliderQuestionMetaData: { id: "api.question.read", ver: "3.0", @@ -3124,6 +3324,113 @@ export const interactionChoiceEditorState = { primaryCategory: 'Multiple Choice Question' }; +export const interactionMatchEditorState = { + question: "

q

", + options: [ + { + left: "

a

", + right: "

b

", + }, + { + left: "

c

", + right: "

d

", + }, + ], + templateId: "mtf-horizontal", + corectMatchPair: [{ "0": 0 }, { "1": 1 }], + numberOfOptions: 2, + interactions: { + response1: { + type: "match", + options: { + left: [ + { + label: "

a

", + value: 0, + }, + { + label: "

b

", + value: 1, + }, + ], + right: [ + { + label: "

c

", + value: 0, + }, + { + label: "

d

", + value: 1, + }, + ], + }, + }, + validation: { + required: "Yes", + }, + }, + name: "Match The Following Question", + responseDeclaration: { + response1: { + cardinality: "multiple", + type: "integer", + correctResponse: { + value: [{ "0": 0 }, { "1": 1 }], + }, + mapping: [ + { + value: { + "0": 0, + }, + score: 2, + }, + { + value: { + "1": 1, + }, + score: 2, + }, + ], + }, + }, + interactionTypes: ["match"], + editorState: { + options: { + left: [ + { + value: { + body: "

a

", + value: 0, + }, + }, + { + value: { + body: "

b

", + value: 1, + }, + }, + ], + right: [ + { + value: { + body: "

c

", + value: 0, + }, + }, + { + value: { + body: "

d

", + value: 1, + }, + }, + ], + }, + question: "

q

", + }, + qType: "MTF", + primaryCategory: "Match The Following Question", +}; + export const RubricData = [ { parent: "do_1134357224765685761203", From 52f53ec4baa1a0ccfccb4dc1b56542214564a7b9 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Wed, 9 Aug 2023 16:03:39 +0530 Subject: [PATCH 25/39] Refactored getQuestionMetadata function --- .../components/question/question.component.ts | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.ts index 07ab2b238..e408a32a1 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.ts @@ -820,7 +820,10 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { metadata.body = metadata.question; if (!_.isUndefined(this.editorService?.editorConfig?.config?.renderTaxonomy)) { const treeNodeData = _.get(this.treeNodeData, 'data.metadata'); - _.get(treeNodeData,'allowScoring') === 'Yes' ? '' : _.set(metadata,'responseDeclaration.response1.mapping',[]); + const allowScoring = _.get(treeNodeData, 'allowScoring'); + if (allowScoring !== 'Yes') { + _.set(metadata, "responseDeclaration.response1.mapping", []); + } } if (this.questionInteractionType != 'choice' && this.questionInteractionType != 'match') { @@ -846,17 +849,15 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { }) const finalAnswer = this.getAnswerWrapperHtml(concatenatedAnswers); metadata.answer = finalAnswer; - } else if (this.questionInteractionType != 'default' && this.questionInteractionType != 'choice' && this.questionInteractionType != 'match') { + } else if (this.questionInteractionType === 'match') { + const { question, templateId } = this.editorState; + const { left, right } = this.editorState.interactions.response1.options; + metadata.body = this.getMtfQuestionHtmlBody(question, templateId); + metadata.correctMatchPair = this.getMtfAnswerContainerHtml(left, right); + } else if (this.questionInteractionType !== 'default') { metadata.responseDeclaration = this.getResponseDeclaration(this.questionInteractionType); } - - if (this.questionInteractionType === 'match') { - metadata.body = this.getMtfQuestionHtmlBody(this.editorState.question, this.editorState.templateId); - const leftOptions = metadata.interactions.response1.options.left; - const rightOptions = metadata.interactions.response1.options.right; - metadata.correctMatchPair = this.getMtfAnswerContainerHtml(leftOptions, rightOptions); - } - + if (!_.isUndefined(this.selectedSolutionType) && !_.isEmpty(this.selectedSolutionType)) { const solutionObj = this.getSolutionObj(this.solutionUUID, this.selectedSolutionType, this.editorState.solutions); metadata.editorState.solutions = [solutionObj]; @@ -870,12 +871,10 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { metadata.programId = _.get(this.editorService, 'editorConfig.context.programId'); metadata.collectionId = _.get(this.editorService, 'editorConfig.context.collectionIdentifier'); metadata.organisationId = _.get(this.editorService, 'editorConfig.context.contributionOrgId'); + metadata.isReviewModificationAllowed = !!_.get(this.questionMetaData, 'isReviewModificationAllowed'); } metadata['outcomeDeclaration'] = this.getOutcomeDeclaration(metadata); metadata = _.merge(metadata, _.pickBy(this.childFormData, _.identity)); - if (_.get(this.creationContext, 'objectType') === 'question') { - metadata.isReviewModificationAllowed = !!_.get(this.questionMetaData, 'isReviewModificationAllowed'); - } // tslint:disable-next-line:max-line-length return _.omit(metadata, ['question', 'numberOfOptions', 'options', 'allowMultiSelect', 'showEvidence', 'evidenceMimeType', 'showRemarks', 'markAsNotMandatory', 'leftAnchor', 'rightAnchor', 'step', 'numberOnly', 'characterLimit', 'dateFormat', 'autoCapture', 'remarksLimit', 'maximumOptions']); } From dba6334d912c8774ea88f70ea680a42a73205628 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Thu, 10 Aug 2023 20:39:53 +0530 Subject: [PATCH 26/39] UI refactored and tests added --- .../lib/components/match/match.component.html | 9 ++++-- .../lib/components/match/match.component.scss | 31 +++++++++++++++++++ .../components/match/match.component.spec.ts | 19 ++++++++++-- .../lib/components/match/match.component.ts | 5 +++ 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.html b/projects/questionset-editor-library/src/lib/components/match/match.component.html index ea938a44b..6530a556f 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.html +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.html @@ -1,9 +1,13 @@
- + + + +
+
@@ -49,6 +53,7 @@ libTelemetryInteract [telemetryInteractEdata]="telemetryService.getTelemetryInteractEdata('add_option','click','submit',telemetryService.telemetryPageId)" > {{configService.labelConfig?.lbl?.addPair}} - + +
diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.scss b/projects/questionset-editor-library/src/lib/components/match/match.component.scss index b6b99bbbe..f4233a529 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.scss +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.scss @@ -1,3 +1,34 @@ +.q-sb-layout-single{ + &:before{ + content: url("/assets/images/layoutoneicon.svg"); + } + &.active, + &:hover + { + border-color: var(--primary-400); + background-color: #ffffff; + color: var(--primary-400); + &:before{ + content: url("/assets/images/layoutoneicon_blue.svg"); + } + } +} +.q-sb-layout-two{ + &:before{ + content: url("/assets/images/layouttwoicon.svg"); + } + &.active, + &:hover + { + border-color: var(--primary-400); + background-color: #ffffff; + color: var(--primary-400); + &:before{ + content: url("/assets/images/layouttwoicon_blue.svg"); + } + } +} + .b-0{ border: 0 !important; } diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts index 79e1a79b7..39a321684 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.ts @@ -25,7 +25,6 @@ describe('MatchComponent', () => { fixture = TestBed.createComponent(MatchComponent); component = fixture.componentInstance; component.editorState = mockOptionData.editorOptionData; - // fixture.detectChanges(); }); it('should create', () => { @@ -86,7 +85,7 @@ describe('MatchComponent', () => { spyOn(component, 'setMapping').and.callThrough(); spyOn(component, "getResponseDeclaration").and.callThrough(); spyOn(component, "getInteractions").and.callThrough(); - const result = component.prepareMtfBody(mockOptionData.editorOptionData); + component.prepareMtfBody(mockOptionData.editorOptionData); expect(component.getResponseDeclaration).toHaveBeenCalledWith( mockOptionData.editorOptionData ); @@ -110,6 +109,13 @@ describe('MatchComponent', () => { expect(component.mapping).toEqual(mockOptionData.prepareMtfBody.responseDeclaration.response1.mapping); }) + it('#setMapping should set mapping with empty array when correctMatchPair is empty', () => { + spyOn(component, 'setMapping').and.callThrough(); + component.editorState.correctMatchPair = []; + component.setMapping(); + expect(component.mapping).toEqual([]); + }); + it('#getOutcomeDeclaration should return expected outcomeDeclaration', () => { component.maxScore = 4; spyOn(component, 'getOutcomeDeclaration').and.callThrough(); @@ -137,4 +143,13 @@ describe('MatchComponent', () => { const responseDeclaration = component.getResponseDeclaration(mockOptionData.editorOptionData); expect(responseDeclaration.response1.cardinality).toEqual('multiple'); }) + + it('#setTemplate() should set #templateType to "mtf-vertical"', () => { + spyOn(component, "editorDataHandler").and.callThrough(); + const templateType = "mtf-vertical"; + component.editorState = mockOptionData.editorOptionData; + component.setTemplate(templateType); + expect(component.templateType).toEqual(templateType); + expect(component.editorDataHandler).toHaveBeenCalled(); + }); }); \ No newline at end of file diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.ts b/projects/questionset-editor-library/src/lib/components/match/match.component.ts index 1336531bc..a8d043e36 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.ts +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.ts @@ -157,4 +157,9 @@ export class MatchComponent implements OnInit, OnChanges { }; return interactions; } + + setTemplate(template) { + this.templateType = template; + this.editorDataHandler(); + } } From 957cb3dd873f2e819bea4667f5efd49d71b83f2a Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Thu, 10 Aug 2023 20:47:25 +0530 Subject: [PATCH 27/39] Bug resolved --- .../src/lib/components/match/match.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.html b/projects/questionset-editor-library/src/lib/components/match/match.component.html index 6530a556f..b12923f0a 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.html +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.html @@ -4,8 +4,8 @@ - - + +
From 73eb19d410a7964f0bc85cd6bb7bd0b069051998 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Fri, 11 Aug 2023 21:58:32 +0530 Subject: [PATCH 28/39] Refactored question component code and tests added --- .../question/question.component.spec.ts | 35 +++++++++++++- .../components/question/question.component.ts | 46 ++++++++++--------- 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts index 4098e6add..9ddb4bd9e 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts @@ -1244,7 +1244,7 @@ describe("QuestionComponent", () => { expect(metadata['outcomeDeclaration'].maxScore.defaultValue).toEqual(1); }); - it('#getQuestionMetadata() should return question metata when interactionType is match', () => { + it('#getQuestionMetadata() should return question metadata when interactionType is match', () => { component.mediaArr = []; component.editorState = interactionMatchEditorState; component.selectedSolutionType = 'video'; @@ -1567,6 +1567,39 @@ describe("QuestionComponent", () => { expect(component.showFormError).toBeFalsy(); }); + it("#validateMatchQuestionData() should validate and set showFormError to true", () => { + component.sourcingSettings = sourcingSettingsMock; + component.treeNodeData = { data: { metadata: { allowScoring: "Yes" } } }; + component.editorState = mockData.mtfQuestionMetaData.result.question; + component.editorState.responseDeclaration.response1.mapping = []; + editorService = TestBed.inject(EditorService); + editorService.editorConfig.renderTaxonomy = false; + component.editorState.question = "

Match each object with its correct type

"; + component.editorState.correctMatchPair = ""; + component.questionInteractionType = "match"; + const toasterService = TestBed.inject(ToasterService); + spyOn(toasterService, "error").and.callFake(() => {}); + spyOn(component, "validateMatchQuestionData").and.callThrough(); + component.validateMatchQuestionData(); + expect(component.showFormError).toBeTruthy(); + }); + + it("#validateMatchQuestionData() should validate and set showFormError to false when allowScoring is No", () => { + component.sourcingSettings = sourcingSettingsMock; + component.treeNodeData = { data: { metadata: { allowScoring: "No" } } }; + component.editorState = mockData.mtfQuestionMetaData.result.question; + editorService = TestBed.inject(EditorService); + editorService.editorConfig.renderTaxonomy = false; + component.editorState.question = "

Match each object with its correct type

"; + component.editorState.correctMatchPair = ""; + component.questionInteractionType = "match"; + const toasterService = TestBed.inject(ToasterService); + spyOn(toasterService, "error").and.callFake(() => {}); + spyOn(component, "validateMatchQuestionData").and.callThrough(); + component.validateMatchQuestionData(); + expect(component.showFormError).toBeFalsy(); + }); + it("#validateQuestionData() should call validateQuestionData when questionInteractionType is text", () => { component.sourcingSettings = sourcingSettingsMock; component.editorState = mockData.textQuestionNetaData.result.question; diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.ts index f3e1e3ae2..c6f48bcf1 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.ts @@ -589,27 +589,9 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { this.validateChoiceQuestionData(); } - //to handle when question type is match + //to handle when question type is mtf if (this.questionInteractionType === 'match') { - const data = _.get(this.treeNodeData, 'data.metadata'); - if (_.get(this.editorState, 'interactionTypes[0]') === 'match' && - _.isEmpty(this.editorState?.responseDeclaration?.response1?.mapping) && - !_.isUndefined(this.editorService?.editorConfig?.config?.renderTaxonomy) && - _.get(data,'allowScoring') === 'Yes') { - this.toasterService.error(_.get(this.configService, 'labelConfig.messages.error.005')); - this.showFormError = true; - return; - } else { - this.showFormError = false; - } - const rightOptionValid = _.find(this.editorState.options, option => (option.right === undefined || option.right === '' || option.right.length > this.setCharacterLimit)); - const leftOptionValid = _.find(this.editorState.options, option => (option.left === undefined || option.left === '' || option.left.length > this.setCharacterLimit)); - if (rightOptionValid || leftOptionValid || (_.isUndefined(this.editorState.correctMatchPair) && this.sourcingSettings?.enforceCorrectAnswer)) { - this.showFormError = true; - return; - } else { - this.showFormError = false; - } + this.validateMatchQuestionData(); } if (this.questionInteractionType === 'slider') { @@ -649,6 +631,28 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } } + validateMatchQuestionData() { + const data = _.get(this.treeNodeData, 'data.metadata'); + if (_.get(this.editorState, 'interactionTypes[0]') === 'match' && + _.isEmpty(this.editorState?.responseDeclaration?.response1?.mapping) && + !_.isUndefined(this.editorService?.editorConfig?.config?.renderTaxonomy) && + _.get(data,'allowScoring') === 'Yes') { + this.toasterService.error(_.get(this.configService, 'labelConfig.messages.error.005')); + this.showFormError = true; + return; + } else { + this.showFormError = false; + } + const rightOptionValid = _.find(this.editorState.options, option => (option.right === undefined || option.right === '' || option.right.length > this.setCharacterLimit)); + const leftOptionValid = _.find(this.editorState.options, option => (option.left === undefined || option.left === '' || option.left.length > this.setCharacterLimit)); + if (rightOptionValid || leftOptionValid || (_.isUndefined(this.editorState.correctMatchPair) && this.sourcingSettings?.enforceCorrectAnswer)) { + this.showFormError = true; + return; //NOSONAR + } else { + this.showFormError = false; + } + } + validateSliderQuestionData() { const min = _.get(this.sliderDatas, 'validation.range.min'); const max = _.get(this.sliderDatas, 'validation.range.max'); @@ -817,7 +821,7 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } setQuestionProperties(metadata) { - if (this.questionInteractionType != 'choice') { + if (this.questionInteractionType != 'choice' && this.questionInteractionType != 'match') { if (!_.isUndefined(metadata.answer)) { const answerHtml = this.getAnswerHtml(metadata.answer); const finalAnswer = this.getAnswerWrapperHtml(answerHtml); From 4ff8fcafb15af30584184bbb1fe178e61c591e57 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Wed, 16 Aug 2023 23:19:21 +0530 Subject: [PATCH 29/39] Code refactored --- .../src/lib/components/match/match.component.spec.data.ts | 2 +- .../src/lib/components/match/match.component.ts | 2 -- .../src/lib/components/question/question.component.html | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.spec.data.ts b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.data.ts index 2552ed8fd..7d1a30bf2 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.spec.data.ts +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.spec.data.ts @@ -16,7 +16,7 @@ export const mockOptionData = { numberOfOptions: 4, }, prepareMtfBody: { - templateId: "default", + templateId: "mtf-horizontal", name: "Match The Following Question", responseDeclaration: { response1: { diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.ts b/projects/questionset-editor-library/src/lib/components/match/match.component.ts index a8d043e36..0695010b4 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.ts +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.ts @@ -11,14 +11,12 @@ import { ConfigService } from '../../services/config/config.service'; export class MatchComponent implements OnInit, OnChanges { @Input() editorState: any; @Input() showFormError; - @Input() sourcingSettings; @Input() questionPrimaryCategory; @Input() mapping = []; @Input() isReadOnlyMode; @Input() maxScore; @Output() editorDataOutput: EventEmitter = new EventEmitter(); public setCharacterLimit = 160; - public setImageLimit = 1; public templateType = 'mtf-horizontal'; constructor( diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.html b/projects/questionset-editor-library/src/lib/components/question/question.component.html index 62063550e..e1806aef2 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.html +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.html @@ -69,7 +69,7 @@ [questionPrimaryCategory]="questionPrimaryCategory" [editorState]="editorState" [showFormError]="showFormError" (editorDataOutput)="editorDataHandler($event)" - [sourcingSettings]="sourcingSettings" [mapping]="scoreMapping" [maxScore]="maxScore" + [mapping]="scoreMapping" [maxScore]="maxScore" [isReadOnlyMode]="isReadOnlyMode"> Date: Thu, 17 Aug 2023 15:06:29 +0530 Subject: [PATCH 30/39] Match template refactored --- .../lib/components/match/match.component.html | 70 ++++++++++++------- 1 file changed, 46 insertions(+), 24 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.html b/projects/questionset-editor-library/src/lib/components/match/match.component.html index b12923f0a..149b98883 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.html +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.html @@ -1,11 +1,26 @@
- - + + - - + + + + + +
@@ -17,42 +32,49 @@ [editorDataInput]="option.left" class="ckeditor-tool__option mb-10" [class.mb-5]="showFormError && option.left.length > setCharacterLimit"> - - + +
- - + +
From cd046e4e952bbe2ec30d56f605481ef0bcb1c8f5 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Thu, 17 Aug 2023 15:24:49 +0530 Subject: [PATCH 31/39] Question component refactored --- .../components/question/question.component.ts | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.ts index c6f48bcf1..d4e6d5a8d 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.ts @@ -225,45 +225,32 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } } - if (this.questionInteractionType === 'choice') { + if (this.questionInteractionType === 'choice' || this.questionInteractionType === 'match') { const responseDeclaration = this.questionMetaData.responseDeclaration; this.scoreMapping = _.get(responseDeclaration, 'response1.mapping'); const templateId = this.questionMetaData.templateId; const numberOfOptions = this.questionMetaData?.editorState?.options?.length || 0; const maximumOptions = _.get(this.questionInput, 'config.maximumOptions'); - this.editorService.optionsLength = numberOfOptions; - const options = _.map(this.questionMetaData?.editorState?.options, option => ({ body: option.value.body })); + this.editorService.optionsLength = numberOfOptions; const question = this.questionMetaData?.editorState?.question; const interactions = this.questionMetaData?.interactions; - this.editorState = new McqForm({ - question, options, answer: _.get(responseDeclaration, 'response1.correctResponse.value') - }, { templateId, numberOfOptions,maximumOptions }); - this.editorState.solutions = this.questionMetaData?.editorState?.solutions; this.editorState.interactions = interactions; - if (_.has(this.questionMetaData, 'responseDeclaration')) { - this.editorState.responseDeclaration = _.get(this.questionMetaData, 'responseDeclaration'); + if (this.questionInteractionType === 'choice') { + const options = _.map(this.questionMetaData?.editorState?.options, option => ({ body: option.value.body })); + this.editorState = new McqForm({ + question, options, answer: _.get(responseDeclaration, 'response1.correctResponse.value') + }, { templateId, numberOfOptions, maximumOptions }); + } + else if (this.questionInteractionType === 'match') { + const options = _.map(this.questionMetaData?.editorState?.options?.left, (left, index) => ({ + left, + right:this.questionMetaData?.editorState?.options?.right?.[index] + })); + this.editorState = new MtfForm({ + question, options, correctMatchPair: _.get(responseDeclaration, 'response1.correctResponse.value') + }, { templateId, numberOfOptions, maximumOptions }); } - } - - if (this.questionInteractionType === 'match') { - const responseDeclaration = this.questionMetaData.responseDeclaration; - this.scoreMapping = _.get(responseDeclaration, 'response1.mapping'); - const templateId = this.questionMetaData.templateId; - const numberOfOptions = this.questionMetaData?.editorState?.options?.length || 0; - const maximumOptions = _.get(this.questionInput, 'config.maximumOptions'); - this.editorService.optionsLength = numberOfOptions; - // converting the options to the format required by the editor - const options = _.map(this.questionMetaData?.editorState?.options?.left, (left, index) => ({ - left, - right:this.questionMetaData?.editorState?.options?.right?.[index] - })); - const question = this.questionMetaData?.editorState?.question; - const interactions = this.questionMetaData?.interactions; - this.editorState = new MtfForm({ - question, options, correctMatchPair: _.get(responseDeclaration, 'response1.correctResponse.value') - }, { templateId, numberOfOptions, maximumOptions }); this.editorState.solutions = this.questionMetaData?.editorState?.solutions; - this.editorState.interactions = interactions; if (_.has(this.questionMetaData, 'responseDeclaration')) { this.editorState.responseDeclaration = _.get(this.questionMetaData, 'responseDeclaration'); } From 5cbf70cf0673105bf370f10b0a0202eb5e36e816 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Fri, 18 Aug 2023 21:48:18 +0530 Subject: [PATCH 32/39] Refactored Code --- .../question/question.component.spec.ts | 54 ++++++++---------- .../components/question/question.component.ts | 57 +++++++++---------- sonar-project.properties | 2 +- 3 files changed, 52 insertions(+), 61 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts index 9ddb4bd9e..918f3a9a0 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.spec.ts @@ -1534,41 +1534,38 @@ describe("QuestionComponent", () => { expect(component.validateChoiceQuestionData).toHaveBeenCalled(); }); - it('#validateChoiceQuestionData() should validate and set showFormError to true', () => { + it('#validateChoiceQuestionData() should validate choice question data when all options are valid and set showFormError to false', () => { component.sourcingSettings = sourcingSettingsMock; - component.treeNodeData = {data: {metadata: {allowScoring: 'Yes'}}} - component.editorState = mockData.mcqQuestionMetaData.result.question; - component.editorState.responseDeclaration.response1.mapping = []; - editorService = TestBed.inject(EditorService); - editorService.editorConfig.renderTaxonomy = false; component.editorState.question = "

Hi how are you

"; + component.editorState.options = [ + { body: "

1

" }, + { body: "

2

" }, + ] component.editorState.answer = ""; component.questionInteractionType = "choice"; - const toasterService = TestBed.inject(ToasterService); - spyOn(toasterService, 'error').and.callFake(() => {}); spyOn(component, 'validateChoiceQuestionData').and.callThrough(); component.validateChoiceQuestionData(); - expect(component.showFormError).toBeTruthy(); + expect(component.showFormError).toBeFalsy(); }); - it('#validateChoiceQuestionData() should validate and set showFormError to false when allowScoring is No', () => { + it("#validateMatchQuestionData() should validate match question data when all options have valid left and right values and set showFormError to false", () => { component.sourcingSettings = sourcingSettingsMock; - component.treeNodeData = {data: {metadata: {allowScoring: 'No'}}} - component.editorState = mockData.mcqQuestionMetaData.result.question; - editorService = TestBed.inject(EditorService); - editorService.editorConfig.renderTaxonomy = false; - component.editorState.question = "

Hi how are you

"; - component.editorState.answer = ""; - component.questionInteractionType = "choice"; - const toasterService = TestBed.inject(ToasterService); - spyOn(toasterService, 'error').and.callFake(() => {}); - spyOn(component, 'validateChoiceQuestionData').and.callThrough(); - component.validateChoiceQuestionData(); + component.editorState.question = "

Match each object with its correct type

"; + component.editorState.options = [ + { left: "

1

", right: "

a

" }, + { left: "

2

", right: "

b

"}, + ] + component.editorState.correctMatchPair = [ + { "0": "0" }, + { "1": "1"}, + ]; + component.questionInteractionType = "match"; + spyOn(component, "validateMatchQuestionData").and.callThrough(); + component.validateMatchQuestionData(); expect(component.showFormError).toBeFalsy(); }); - it("#validateMatchQuestionData() should validate and set showFormError to true", () => { - component.sourcingSettings = sourcingSettingsMock; + it("#validateData() should validate and set showFormError to true when allowScoring is Yes", () => { component.treeNodeData = { data: { metadata: { allowScoring: "Yes" } } }; component.editorState = mockData.mtfQuestionMetaData.result.question; component.editorState.responseDeclaration.response1.mapping = []; @@ -1579,13 +1576,12 @@ describe("QuestionComponent", () => { component.questionInteractionType = "match"; const toasterService = TestBed.inject(ToasterService); spyOn(toasterService, "error").and.callFake(() => {}); - spyOn(component, "validateMatchQuestionData").and.callThrough(); - component.validateMatchQuestionData(); + spyOn(component, "validateData").and.callThrough(); + component.validateData(component.questionInteractionType); expect(component.showFormError).toBeTruthy(); }); - it("#validateMatchQuestionData() should validate and set showFormError to false when allowScoring is No", () => { - component.sourcingSettings = sourcingSettingsMock; + it("#validateData() should validate and set showFormError to false when allowScoring is No", () => { component.treeNodeData = { data: { metadata: { allowScoring: "No" } } }; component.editorState = mockData.mtfQuestionMetaData.result.question; editorService = TestBed.inject(EditorService); @@ -1595,8 +1591,8 @@ describe("QuestionComponent", () => { component.questionInteractionType = "match"; const toasterService = TestBed.inject(ToasterService); spyOn(toasterService, "error").and.callFake(() => {}); - spyOn(component, "validateMatchQuestionData").and.callThrough(); - component.validateMatchQuestionData(); + spyOn(component, "validateData").and.callThrough(); + component.validateData(component.questionInteractionType); expect(component.showFormError).toBeFalsy(); }); diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.ts index d4e6d5a8d..673635656 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.ts @@ -597,17 +597,7 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } validateChoiceQuestionData() { - const data = _.get(this.treeNodeData, 'data.metadata'); - if (_.get(this.editorState, 'interactionTypes[0]') === 'choice' && - _.isEmpty(this.editorState?.responseDeclaration?.response1?.mapping) && - !_.isUndefined(this.editorService?.editorConfig?.config?.renderTaxonomy) && - _.get(data,'allowScoring') === 'Yes') { - this.toasterService.error(_.get(this.configService, 'labelConfig.messages.error.005')); - this.showFormError = true; - return; - } else { - this.showFormError = false; - } + this.validateData('choice'); const optionValid = _.find(this.editorState.options, option => (option.body === undefined || option.body === '' || option.length > this.setCharacterLimit)); if (optionValid || (_.isUndefined(this.editorState.answer) && this.sourcingSettings?.enforceCorrectAnswer)) { @@ -619,27 +609,18 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } validateMatchQuestionData() { - const data = _.get(this.treeNodeData, 'data.metadata'); - if (_.get(this.editorState, 'interactionTypes[0]') === 'match' && - _.isEmpty(this.editorState?.responseDeclaration?.response1?.mapping) && - !_.isUndefined(this.editorService?.editorConfig?.config?.renderTaxonomy) && - _.get(data,'allowScoring') === 'Yes') { - this.toasterService.error(_.get(this.configService, 'labelConfig.messages.error.005')); - this.showFormError = true; - return; - } else { - this.showFormError = false; - } - const rightOptionValid = _.find(this.editorState.options, option => (option.right === undefined || option.right === '' || option.right.length > this.setCharacterLimit)); - const leftOptionValid = _.find(this.editorState.options, option => (option.left === undefined || option.left === '' || option.left.length > this.setCharacterLimit)); - if (rightOptionValid || leftOptionValid || (_.isUndefined(this.editorState.correctMatchPair) && this.sourcingSettings?.enforceCorrectAnswer)) { - this.showFormError = true; - return; //NOSONAR - } else { - this.showFormError = false; - } + this.validateData('match'); + const rightOptionValid = _.find(this.editorState.options, option => (option.right === undefined || option.right === '' || option.right.length > this.setCharacterLimit)); + const leftOptionValid = _.find(this.editorState.options, option => (option.left === undefined || option.left === '' || option.left.length > this.setCharacterLimit)); + if (rightOptionValid || leftOptionValid || (_.isUndefined(this.editorState.correctMatchPair) && this.sourcingSettings?.enforceCorrectAnswer)) { + this.showFormError = true; + return; //NOSONAR + } else { + this.showFormError = false; + } } + validateSliderQuestionData() { const min = _.get(this.sliderDatas, 'validation.range.min'); const max = _.get(this.sliderDatas, 'validation.range.max'); @@ -651,7 +632,21 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { this.showFormError = false; } } - + + validateData(interactionType) { + const data = _.get(this.treeNodeData, 'data.metadata'); + if (_.get(this.editorState, 'interactionTypes[0]') === interactionType && + _.isEmpty(this.editorState?.responseDeclaration?.response1?.mapping) && + !_.isUndefined(this.editorService?.editorConfig?.config?.renderTaxonomy) && + _.get(data,'allowScoring') === 'Yes') { + this.toasterService.error(_.get(this.configService, 'labelConfig.messages.error.005')); + this.showFormError = true; + return; //NOSONAR + } else { + this.showFormError = false; + } + } + redirectToQuestionset() { this.showConfirmPopup = false; this.treeService.clearTreeCache(); diff --git a/sonar-project.properties b/sonar-project.properties index 0c80de60d..10f91e1bf 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,7 +1,7 @@ sonar.projectName=sunbird-questionset-editor sonar.language=ts sonar.sources=projects/questionset-editor-library/src -sonar.exclusions=projects/questionset-editor-library/src/lib/assets/**,projects/questionset-editor-library/src/lib/**/*.spec.ts,projects/questionset-editor-library/src/lib/**/*.spec.data.ts,projects/questionset-editor-library/src/lib/**/*.module.ts,projects/questionset-editor-library/src/lib/interfaces/* +sonar.exclusions=projects/questionset-editor-library/src/lib/assets/**,projects/questionset-editor-library/src/lib/**/*.spec.ts,projects/questionset-editor-library/src/lib/**/*.spec.data.ts,projects/questionset-editor-library/src/lib/**/*.module.ts,projects/questionset-editor-library/src/lib/interfaces/*,projects/questionset-editor-library/src/lib/services/config/label.config.json sonar.javascript.lcov.reportPaths=projects/questionset-editor-library/coverage/lcov.info sonar.projectKey=Sunbird-inQuiry_editor sonar.host.url=https://sonarcloud.io From e3a703825331e07afeaf0c6de7fa7be269ebfa7f Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Sat, 19 Aug 2023 14:56:18 +0530 Subject: [PATCH 33/39] Canaged correctMatchPair property in metadata --- .../src/lib/components/question/question.component.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/question/question.component.ts b/projects/questionset-editor-library/src/lib/components/question/question.component.ts index 673635656..3c73f944b 100644 --- a/projects/questionset-editor-library/src/lib/components/question/question.component.ts +++ b/projects/questionset-editor-library/src/lib/components/question/question.component.ts @@ -243,8 +243,8 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { } else if (this.questionInteractionType === 'match') { const options = _.map(this.questionMetaData?.editorState?.options?.left, (left, index) => ({ - left, - right:this.questionMetaData?.editorState?.options?.right?.[index] + left: left.value.body, + right:this.questionMetaData?.editorState?.options?.right?.[index].value.body })); this.editorState = new MtfForm({ question, options, correctMatchPair: _.get(responseDeclaration, 'response1.correctResponse.value') @@ -830,7 +830,9 @@ export class QuestionComponent implements OnInit, AfterViewInit, OnDestroy { const { question, templateId } = this.editorState; const { left, right } = this.editorState.interactions.response1.options; metadata.body = this.getMtfQuestionHtmlBody(question, templateId); - metadata.correctMatchPair = this.getMtfAnswerContainerHtml(left, right); + metadata['answer'] = metadata['correctMatchPair']; + delete metadata['correctMatchPair']; + metadata.answer = this.getMtfAnswerContainerHtml(left, right); } else if (this.questionInteractionType !== 'default') { metadata.responseDeclaration = this.getResponseDeclaration(this.questionInteractionType); } From 7eb285a122328c58a123c771bfeb6c6c9eae5638 Mon Sep 17 00:00:00 2001 From: Arpan Gupta Date: Wed, 23 Aug 2023 17:49:57 +0530 Subject: [PATCH 34/39] Resolved fill this option error bug --- .../src/lib/components/match/match.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/questionset-editor-library/src/lib/components/match/match.component.html b/projects/questionset-editor-library/src/lib/components/match/match.component.html index 149b98883..ce10ec67b 100644 --- a/projects/questionset-editor-library/src/lib/components/match/match.component.html +++ b/projects/questionset-editor-library/src/lib/components/match/match.component.html @@ -30,7 +30,7 @@ + [class.mb-5]="showFormError && ([undefined, ''].includes(option.left.body) || option.left.length > setCharacterLimit)">
"),g=w.children()[0];return L("body").append(w),O=g.offsetWidth,w.css("overflow","scroll"),O===(g=g.offsetWidth)&&(g=w[0].clientWidth),w.remove(),u=O-g},getScrollInfo:function(O){var g=O.isWindow||O.isDocument?"":O.element.css("overflow-x"),w=O.isWindow||O.isDocument?"":O.element.css("overflow-y");return g="scroll"===g||"auto"===g&&O.widthn(o(Ne),o($e))?"horizontal":"vertical",O.using.call(this,he,G)}),Ee.offset(L.extend(ge,{using:ee}))})},L.ui.position={fit:{left:function(O,w){var g=w.within,S=g.isWindow?g.scrollLeft:g.offset.left,I=g.width,h=O.left-w.collisionPosition.marginLeft,E=S-h,x=h+w.collisionWidth-I-S;w.collisionWidth>I?0I?0"'/]/g,o=/[<>"'/]/g,i="$recursive_request",a="$request_target_invalid",p={"&":"&","<":"<",">":">",'"':""","'":"'","/":"/"},c={16:!0,17:!0,18:!0},f={8:"backspace",9:"tab",10:"return",13:"return",19:"pause",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"insert",46:"del",59:";",61:"=",96:"0",97:"1",98:"2",99:"3",100:"4",101:"5",102:"6",103:"7",104:"8",105:"9",106:"*",107:"+",109:"-",110:".",111:"/",112:"f1",113:"f2",114:"f3",115:"f4",116:"f5",117:"f6",118:"f7",119:"f8",120:"f9",121:"f10",122:"f11",123:"f12",144:"numlock",145:"scroll",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},d={16:"shift",17:"ctrl",18:"alt",91:"meta",93:"meta"},m={0:"",1:"left",2:"middle",3:"right"},y="active expanded focus folder lazy radiogroup selected unselectable unselectableIgnore".split(" "),v={},b="columns types".split(" "),T="checkbox expanded extraClasses folder icon iconTooltip key lazy partsel radiogroup refKey selected statusNodeType title tooltip type unselectable unselectableIgnore unselectableStatus".split(" "),M={},O={},w={active:!0,children:!0,data:!0,focus:!0},g=0;gQ.getIndexHier(".",5)},isChildOf:function(Q){return this.parent&&this.parent===Q},isDescendantOf:function(Q){if(!Q||Q.tree!==this.tree)return!1;for(var j=this.parent;j;){if(j===Q)return!0;j===j.parent&&t.error("Recursive parent link: "+j),j=j.parent}return!1},isExpanded:function(){return!!this.expanded},isFirstSibling:function(){var Q=this.parent;return!Q||Q.children[0]===this},isFolder:function(){return!!this.folder},isLastSibling:function(){var Q=this.parent;return!Q||Q.children[Q.children.length-1]===this},isLazy:function(){return!!this.lazy},isLoaded:function(){return!this.lazy||void 0!==this.hasChildren()},isLoading:function(){return!!this._isLoading},isRoot:function(){return this.isRootNode()},isPartsel:function(){return!this.selected&&!!this.partsel},isPartload:function(){return!!this.partload},isRootNode:function(){return this.tree.rootNode===this},isSelected:function(){return!!this.selected},isStatusNode:function(){return!!this.statusNodeType},isPagingNode:function(){return"paging"===this.statusNodeType},isTopLevel:function(){return this.tree.rootNode===this.parent},isUndefined:function(){return void 0===this.hasChildren()},isVisible:function(){var Q,j,F=this.tree.enableFilter,q=this.getParentList(!1,!1);if(F&&!this.match&&!this.subMatchCount)return!1;for(Q=0,j=q.length;QDate.now()?j.value:(delete this._tempCache[Q],null)},_usesExtension:function(Q){return 0<=t.inArray(Q,this.options.extensions)},_requireExtension:function(Q,j,F,q){null!=F&&(F=!!F);var we,ge=this._local.name,he=t.inArray(Q,we=this.options.extensions)",{type:"checkbox",name:q,value:Ne.key,checked:!0}))}he.length?he.empty():he=t("
",{id:we}).hide().insertAfter(this.$container),!1!==j&&this.activeNode&&he.append(t("",{type:"radio",name:ge,value:this.activeNode.key,checked:!0})),F.filter?this.visit(function(Ne){var $e=F.filter(Ne);if("skip"===$e)return $e;!1!==$e&&Ke(Ne)}):!1!==Q&&(xe=this.getSelectedNodes(xe),t.each(xe,function(Ne,$e){Ke($e)}))},getActiveNode:function(){return this.activeNode},getFirstChild:function(){return this.rootNode.getFirstChild()},getFocusNode:function(){return this.focusNode},getOption:function(Q){return this.widget.option(Q)},getNodeByKey:function(Q,j){var F,q;return!j&&(F=document.getElementById(this.options.idPrefix+Q))?F.ftnode||null:(Q=""+Q,(j=j||this.rootNode).visit(function(ge){if(ge.key===Q)return q=ge,!1},!(q=null)),q)},getRootNode:function(){return this.rootNode},getSelectedNodes:function(Q){return this.rootNode.getSelectedNodes(Q)},hasFocus:function(){return!!this._hasFocus},info:function(Q){3<=this.options.debugLevel&&(Array.prototype.unshift.call(arguments,this.toString()),A("info",arguments))},isLoading:function(){var Q=!1;return this.rootNode.visit(function(j){if(j._isLoading||j._requestId)return!(Q=!0)},!0),Q},loadKeyPath:function(Q,j){var F,q,ge,we=this,he=new t.Deferred,xe=this.getRootNode(),Ke=this.options.keyPathSeparator,Ne=[],$e=t.extend({},j);for("function"==typeof j?F=j:j&&j.callback&&(F=j.callback),$e.callback=function(G,k,se){F&&F.call(G,k,se),he.notifyWith(G,[{node:k,status:se}])},null==$e.matchKey&&($e.matchKey=function(G,k){return G.key===k}),S(Q)||(Q=[Q]),q=0;qG)ge.rejectWith(this,[i]);else if(null!==Ke.parent||null===Ne){if(Q.options.postProcess){try{(tt=xe._triggerNodeEvent("postProcess",Q,Q.originalEvent,{response:k,error:null,dataType:j.dataType})).error&&xe.warn("postProcess returned error:",tt)}catch(Re){tt={error:Re,message:""+Re,details:"postProcess failed"}}if(tt.error)return Se=t.isPlainObject(tt.error)?tt.error:{message:tt.error},Se=xe._makeHookContext(Ke,null,Se),void ge.rejectWith(this,[Se]);(S(tt)||t.isPlainObject(tt)&&S(tt.children))&&(k=tt)}else k&&h(k,"d")&&Q.options.enableAspx&&(42===Q.options.enableAspx&&xe.warn("The default for enableAspx will change to `false` in the fututure. Pass `enableAspx: true` or implement postProcess to silence this warning."),k="string"==typeof k.d?t.parseJSON(k.d):k.d);ge.resolveWith(this,[k])}else ge.rejectWith(this,[a])},function(k,se,ne){ne=xe._makeHookContext(Ke,null,{error:k,args:Array.prototype.slice.call(arguments),message:ne,details:k.status+": "+ne}),ge.rejectWith(this,[ne])}),ge.done(function(k){var se,ne,Se;xe.nodeSetStatus(Q,"ok"),t.isPlainObject(k)?(I(Ke.isRootNode(),"source may only be an object for root nodes (expecting an array of child objects otherwise)"),I(S(k.children),"if an object is passed as source, it must contain a 'children' array (all other properties are added to 'tree.data')"),se=(ne=k).children,delete ne.children,t.each(b,function(tt,Re){void 0!==ne[Re]&&(xe[Re]=ne[Re],delete ne[Re])}),t.extend(xe.data,ne)):se=k,I(S(se),"expected array of children"),Ke._setChildren(se),xe.options.nodata&&0===se.length&&(E(xe.options.nodata)?Se=xe.options.nodata.call(xe,{type:"nodata"},Q):!0===xe.options.nodata&&Ke.isRootNode()?Se=xe.options.strings.noData:"string"==typeof xe.options.nodata&&Ke.isRootNode()&&(Se=xe.options.nodata),Se&&Ke.setStatus("nodata",Se)),xe._triggerNodeEvent("loadChildren",Ke)}).fail(function(k){var se;k!==i?k!==a?(k.node&&k.error&&k.message?se=k:"[object Object]"===(se=xe._makeHookContext(Ke,null,{error:k,args:Array.prototype.slice.call(arguments),message:k?k.message||k.toString():""})).message&&(se.message=""),Ke.warn("Load children failed ("+se.message+")",se),!1!==xe._triggerNodeEvent("loadError",se,null)&&xe.nodeSetStatus(Q,"error",se.message,se.details)):Ke.warn("Lazy parent node was removed while loading: discarding response."):Ke.warn("Ignored response for obsolete load request #"+G+" (expected #"+Ke._requestId+")")}).always(function(){Ke._requestId=null,he&&xe.debugTimeEnd($e)}),ge.promise()},nodeLoadKeyPath:function(Q,j){},nodeRemoveChild:function(Q,j){var F=Q.node,q=t.extend({},Q,{node:j}),ge=F.children;if(1===ge.length)return I(j===ge[0],"invalid single child"),this.nodeRemoveChildren(Q);this.activeNode&&(j===this.activeNode||this.activeNode.isDescendantOf(j))&&this.activeNode.setActive(!1),this.focusNode&&(j===this.focusNode||this.focusNode.isDescendantOf(j))&&(this.focusNode=null),this.nodeRemoveMarkup(q),this.nodeRemoveChildren(q),I(0<=(q=t.inArray(j,ge)),"invalid child"),F.triggerModifyChild("remove",j),j.visit(function(we){we.parent=null},!0),this._callHook("treeRegisterNode",this,!1,j),ge.splice(q,1)},nodeRemoveChildMarkup:function(Q){(Q=Q.node).ul&&(Q.isRootNode()?t(Q.ul).empty():(t(Q.ul).remove(),Q.ul=null),Q.visit(function(j){j.li=j.ul=null}))},nodeRemoveChildren:function(Q){var j=Q.tree,F=Q.node;F.children&&(this.activeNode&&this.activeNode.isDescendantOf(F)&&this.activeNode.setActive(!1),this.focusNode&&this.focusNode.isDescendantOf(F)&&(this.focusNode=null),this.nodeRemoveChildMarkup(Q),F.triggerModifyChild("remove",null),F.visit(function(q){q.parent=null,j._callHook("treeRegisterNode",j,!1,q)}),F.children=F.lazy?[]:null,F.isRootNode()||(F.expanded=!1),this.nodeRenderStatus(Q))},nodeRemoveMarkup:function(Q){var j=Q.node;j.li&&(t(j.li).remove(),j.li=null),this.nodeRemoveChildMarkup(Q)},nodeRender:function(Q,j,F,q,ge){var we,he,xe,Ke,Ne,$e,G,k=Q.node,se=Q.tree,ne=Q.options,Se=ne.aria,tt=!1,Re=k.parent,pe=!Re,ve=k.children,Y=null;if(!1!==se._enableUpdate&&(pe||Re.ul)){if(I(pe||Re.ul,"parent UL must exist"),pe||(k.li&&(j||k.li.parentNode!==k.parent.ul)&&(k.li.parentNode===k.parent.ul?Y=k.li.nextSibling:this.debug("Unlinking "+k+" (must be child of "+k.parent+")"),this.nodeRemoveMarkup(Q)),k.li?this.nodeRenderStatus(Q):(tt=!0,k.li=document.createElement("li"),(k.li.ftnode=k).key&&ne.generateIds&&(k.li.id=ne.idPrefix+k.key),k.span=document.createElement("span"),k.span.className="fancytree-node",Se&&!k.tr&&t(k.li).attr("role","treeitem"),k.li.appendChild(k.span),this.nodeRenderTitle(Q),ne.createNode&&ne.createNode.call(se,{type:"createNode"},Q)),ne.renderNode&&ne.renderNode.call(se,{type:"renderNode"},Q)),ve){if(pe||k.expanded||!0===F){for(k.ul||(k.ul=document.createElement("ul"),(!0!==q||ge)&&k.expanded||(k.ul.style.display="none"),Se&&t(k.ul).attr("role","group"),k.li?k.li.appendChild(k.ul):k.tree.$div.append(k.ul)),Ke=0,Ne=ve.length;Ke")):Ne.push(""),(Ke=r.evalOption("checkbox",ge,ge,he,!1))&&!ge.isStatusNode()&&(F="fancytree-checkbox",("radio"===Ke||ge.parent&&ge.parent.radiogroup)&&(F+=" fancytree-radio"),Ne.push("")),void 0!==ge.data.iconClass&&(ge.icon?t.error("'iconClass' node option is deprecated since v2.14.0: use 'icon' only instead"):(ge.warn("'iconClass' node option is deprecated since v2.14.0: use 'icon' instead"),ge.icon=ge.data.iconClass)),!1!==(F=r.evalOption("icon",ge,ge,he,!0))&&(j=xe?" role='presentation'":"",q=(q=r.evalOption("iconTooltip",ge,ge,he,null))?" title='"+ee(q)+"'":"","string"==typeof F?u.test(F)?(F="/"===F.charAt(0)?F:(he.imagePath||"")+F,Ne.push("")):Ne.push(""):Ne.push(F.text?""+r.escapeHtml(F.text)+"":F.html?""+F.html+"":"")),j="",j=(j=he.renderTitle?he.renderTitle.call(we,{type:"renderTitle"},Q)||"":j)||""+(he.escapeTitles?r.escapeHtml(ge.title):ge.title)+"",Ne.push(j),ge.span.innerHTML=Ne.join(""),this.nodeRenderStatus(Q),he.enhanceTitle&&(Q.$title=t(">span.fancytree-title",ge.span),j=he.enhanceTitle.call(we,{type:"enhanceTitle"},Q)||""))},nodeRenderStatus:function($e){var j,F=$e.node,q=$e.tree,ge=$e.options,we=F.hasChildren(),he=F.isLastSibling(),xe=ge.aria,Ke=ge._classNames,Ne=[];($e=F[q.statusClassPropName])&&!1!==q._enableUpdate&&(xe&&(j=t(F.tr||F.li)),Ne.push(Ke.node),q.activeNode===F&&Ne.push(Ke.active),q.focusNode===F&&Ne.push(Ke.focused),F.expanded&&Ne.push(Ke.expanded),xe&&(!1===we?j.removeAttr("aria-expanded"):j.attr("aria-expanded",Boolean(F.expanded))),F.folder&&Ne.push(Ke.folder),!1!==we&&Ne.push(Ke.hasChildren),he&&Ne.push(Ke.lastsib),F.lazy&&null==F.children&&Ne.push(Ke.lazy),F.partload&&Ne.push(Ke.partload),F.partsel&&Ne.push(Ke.partsel),r.evalOption("unselectable",F,F,ge,!1)&&Ne.push(Ke.unselectable),F._isLoading&&Ne.push(Ke.loading),F._error&&Ne.push(Ke.error),F.statusNodeType&&Ne.push(Ke.statusNodePrefix+F.statusNodeType),F.selected?(Ne.push(Ke.selected),xe&&j.attr("aria-selected",!0)):xe&&j.attr("aria-selected",!1),F.extraClasses&&Ne.push(F.extraClasses),Ne.push(!1===we?Ke.combinedExpanderPrefix+"n"+(he?"l":""):Ke.combinedExpanderPrefix+(F.expanded?"e":"c")+(F.lazy&&null==F.children?"d":"")+(he?"l":"")),Ne.push(Ke.combinedIconPrefix+(F.expanded?"e":"c")+(F.folder?"f":"")),$e.className=Ne.join(" "),F.li&&t(F.li).toggleClass(Ke.lastsib,he))},nodeSetActive:function(Q,j,Ke){var q=Q.node,ge=Q.tree,we=Q.options,he=!0===(Ke=Ke||{}).noEvents,xe=!0===Ke.noFocus;return Ke=!1!==Ke.scrollIntoView,q===ge.activeNode==(j=!1!==j)?B(q):(Ke&&Q.originalEvent&&t(Q.originalEvent.target).is("a,:checkbox")&&(q.info("Not scrolling while clicking an embedded link."),Ke=!1),j&&!he&&!1===this._triggerNodeEvent("beforeActivate",q,Q.originalEvent)?N(q,["rejected"]):(j?(ge.activeNode&&(I(ge.activeNode!==q,"node was active (inconsistency)"),j=t.extend({},Q,{node:ge.activeNode}),ge.nodeSetActive(j,!1),I(null===ge.activeNode,"deactivate was out of sync?")),we.activeVisible&&q.makeVisible({scrollIntoView:Ke}),ge.activeNode=q,ge.nodeRenderStatus(Q),xe||ge.nodeSetFocus(Q),he||ge._triggerNodeEvent("activate",q,Q.originalEvent)):(I(ge.activeNode===q,"node was not active (inconsistency)"),ge.activeNode=null,this.nodeRenderStatus(Q),he||Q.tree._triggerNodeEvent("deactivate",q,Q.originalEvent)),B(q)))},nodeSetExpanded:function(Q,j,F){var q,ge,we,he,xe,Ke,Ne=Q.node,$e=Q.tree,G=Q.options,k=!0===(F=F||{}).noAnimation,se=!0===F.noEvents;if(j=!1!==j,t(Ne.li).hasClass(G._classNames.animating))return Ne.warn("setExpanded("+j+") while animating: ignored."),N(Ne,["recursion"]);if(Ne.expanded&&j||!Ne.expanded&&!j||j&&!Ne.lazy&&!Ne.hasChildren())return B(Ne);if(!j&&Ne.getLevel()ul.fancytree-container").empty(),j.rootNode.children=null,j._callHook("treeStructureChanged",Q,"clear")},treeCreate:function(Q){},treeDestroy:function(Q){this.$div.find(">ul.fancytree-container").remove(),this.$source&&this.$source.removeClass("fancytree-helper-hidden")},treeInit:function(Q){var j=Q.tree,F=j.options;j.$container.attr("tabindex",F.tabindex),t.each(b,function(q,ge){void 0!==F[ge]&&(j.info("Move option "+ge+" to tree"),j[ge]=F[ge],delete F[ge])}),F.checkboxAutoHide&&j.$container.addClass("fancytree-checkbox-auto-hide"),F.rtl?j.$container.attr("DIR","RTL").addClass("fancytree-rtl"):j.$container.removeAttr("DIR").removeClass("fancytree-rtl"),F.aria&&(j.$container.attr("role","tree"),1!==F.selectMode&&j.$container.attr("aria-multiselectable",!0)),this.treeLoad(Q)},treeLoad:function(Q,j){var F,q,ge,we=Q.tree,he=Q.widget.element,xe=t.extend({},Q,{node:this.rootNode});if(we.rootNode.children&&this.treeClear(Q),j=j||this.options.source)"string"==typeof j&&t.error("Not implemented");else switch(q=he.data("type")||"html"){case"html":(ge=he.find(">ul").not(".fancytree-container").first()).length?(ge.addClass("ui-fancytree-source fancytree-helper-hidden"),j=t.ui.fancytree.parseHtml(ge),this.data=t.extend(this.data,Z(ge))):(r.warn("No `source` option was passed and container does not contain `
    `: assuming `source: []`."),j=[]);break;case"json":j=t.parseJSON(he.text()),he.contents().filter(function(){return 3===this.nodeType}).remove(),t.isPlainObject(j)&&(I(S(j.children),"if an object is passed as source, it must contain a 'children' array (all other properties are added to 'tree.data')"),j=(F=j).children,delete F.children,t.each(b,function(Ke,Ne){void 0!==F[Ne]&&(we[Ne]=F[Ne],delete F[Ne])}),t.extend(we.data,F));break;default:t.error("Invalid data-type: "+q)}return we._triggerTreeEvent("preInit",null),this.nodeLoadChildren(xe,j).done(function(){we._callHook("treeStructureChanged",Q,"loadChildren"),we.render(),3===Q.options.selectMode&&we.rootNode.fixSelection3FromEndNodes(),we.activeNode&&we.options.activeVisible&&we.activeNode.makeVisible(),we._triggerTreeEvent("init",null,{status:!0})}).fail(function(){we.render(),we._triggerTreeEvent("init",null,{status:!1})})},treeRegisterNode:function(Q,j,F){Q.tree._callHook("treeStructureChanged",Q,j?"addNode":"removeNode")},treeSetFocus:function(Q,j,F){var q;(j=!1!==j)!==this.hasFocus()&&(!(this._hasFocus=j)&&this.focusNode?this.focusNode.setFocus(!1):!j||F&&F.calledByNode||t(this.$container).focus(),this.$container.toggleClass("fancytree-treefocus",j),this._triggerTreeEvent(j?"focusTree":"blurTree"),j&&!this.activeNode&&(q=this._lastMousedownNode||this.getFirstChild())&&q.setFocus())},treeSetOption:function(Q,j,F){var q=Q.tree,ge=!0,we=!1,he=!1;switch(j){case"aria":case"checkbox":case"icon":case"minExpandLevel":case"tabindex":he=we=!0;break;case"checkboxAutoHide":q.$container.toggleClass("fancytree-checkbox-auto-hide",!!F);break;case"escapeTitles":case"tooltip":he=!0;break;case"rtl":!1===F?q.$container.removeAttr("DIR").removeClass("fancytree-rtl"):q.$container.attr("DIR","RTL").addClass("fancytree-rtl"),he=!0;break;case"source":ge=!1,q._callHook("treeLoad",q,F),he=!0}q.debug("set option "+j+"="+F+" <"+typeof F+">"),ge&&(this.widget._super||t.Widget.prototype._setOption).call(this.widget,j,F),we&&q._callHook("treeCreate",q),he&&q.render(!0,!1)},treeStructureChanged:function(Q,j){}}),t.widget("ui.fancytree",{options:{activeVisible:!0,ajax:{type:"GET",cache:!1,dataType:"json"},aria:!0,autoActivate:!0,autoCollapse:!1,autoScroll:!1,checkbox:!1,clickFolderMode:4,copyFunctionsToData:!1,debugLevel:null,disabled:!1,enableAspx:42,escapeTitles:!1,extensions:[],focusOnSelect:!1,generateIds:!1,icon:!0,idPrefix:"ft_",keyboard:!0,keyPathSeparator:"/",minExpandLevel:1,nodata:!0,quicksearch:!1,rtl:!1,scrollOfs:{top:0,bottom:0},scrollParent:null,selectMode:2,strings:{loading:"Loading...",loadError:"Load error!",moreData:"More...",noData:"No data."},tabindex:"0",titlesTabbable:!1,toggleEffect:{effect:"slideToggle",duration:200},tooltip:!1,treeId:null,_classNames:{active:"fancytree-active",animating:"fancytree-animating",combinedExpanderPrefix:"fancytree-exp-",combinedIconPrefix:"fancytree-ico-",error:"fancytree-error",expanded:"fancytree-expanded",focused:"fancytree-focused",folder:"fancytree-folder",hasChildren:"fancytree-has-children",lastsib:"fancytree-lastsib",lazy:"fancytree-lazy",loading:"fancytree-loading",node:"fancytree-node",partload:"fancytree-partload",partsel:"fancytree-partsel",radio:"fancytree-radio",selected:"fancytree-selected",statusNodePrefix:"fancytree-statusnode-",unselectable:"fancytree-unselectable"},lazyLoad:null,postProcess:null},_deprecationWarning:function(Q){var j=this.tree;j&&3<=j.options.debugLevel&&j.warn("$().fancytree('"+Q+"') is deprecated (see https://wwwendt.de/tech/fancytree/doc/jsdoc/Fancytree_Widget.html")},_create:function(){this.tree=new Le(this),this.$source=this.source||"json"===this.element.data("type")?this.element:this.element.find(">ul").first();for(var Q,j,F=this.options,q=F.extensions,ge=0;ge element.");else{if(he){if(F._getExpiringValue("focusin"))return void F.debug("Ignored double focusin.");F._setExpiringValue("focusin",!0,50),we||(we=F._getExpiringValue("mouseDownNode"))&&F.debug("Reconstruct mouse target for focusin from recent event.")}we?F._callHook("nodeSetFocus",F._makeHookContext(we,ge),he):F.tbody&&t(ge.target).parents("table.fancytree-container > thead").length?F.debug("Ignore focus event outside table body.",ge):F._callHook("treeSetFocus",F,he)}}).on("selectstart"+q,"span.fancytree-title",function(ge){ge.preventDefault()}).on("keydown"+q,function(ge){if(j.disabled||!1===j.keyboard)return!0;var we,he=F.focusNode,xe=F._makeHookContext(he||F,ge),Ke=F.phase;try{return F.phase="userEvent","preventNav"===(we=he?F._triggerNodeEvent("keydown",he,ge):F._triggerTreeEvent("keydown",ge))?we=!0:!1!==we&&(we=F._callHook("nodeKeydown",xe)),we}finally{F.phase=Ke}}).on("mousedown"+q,function(ge){ge=r.getEventTarget(ge),F._lastMousedownNode=ge?ge.node:null,F._setExpiringValue("mouseDownNode",F._lastMousedownNode)}).on("click"+q+" dblclick"+q,function(ge){if(j.disabled)return!0;var we,he=r.getEventTarget(ge),xe=he.node,Ke=Q.tree,Ne=Ke.phase;if(!xe)return!0;we=Ke._makeHookContext(xe,ge);try{switch(Ke.phase="userEvent",ge.type){case"click":return we.targetType=he.type,xe.isPagingNode()?!0===Ke._triggerNodeEvent("clickPaging",we,ge):!1!==Ke._triggerNodeEvent("click",we,ge)&&Ke._callHook("nodeClick",we);case"dblclick":return we.targetType=he.type,!1!==Ke._triggerNodeEvent("dblclick",we,ge)&&Ke._callHook("nodeDblclick",we)}}finally{Ke.phase=Ne}})},getActiveNode:function(){return this._deprecationWarning("getActiveNode"),this.tree.activeNode},getNodeByKey:function(Q){return this._deprecationWarning("getNodeByKey"),this.tree.getNodeByKey(Q)},getRootNode:function(){return this._deprecationWarning("getRootNode"),this.tree.rootNode},getTree:function(){return this._deprecationWarning("getTree"),this.tree}}),r=t.ui.fancytree,t.extend(t.ui.fancytree,{version:"2.38.3",buildType:"production",debugLevel:3,_nextId:1,_nextNodeKey:1,_extensions:{},_FancytreeClass:Le,_FancytreeNodeClass:K,jquerySupports:{positionMyOfs:function(Q){for(var j,F,q=t.map(x(Q).split("."),function(he){return parseInt(he,10)}),ge=t.map(Array.prototype.slice.call(arguments,1),function(he){return parseInt(he,10)}),we=0;weli"),$e=[];return Ne.each(function(){var G,k,se=t(this),ne=se.find(">span",this).first(),Se=ne.length?null:se.find(">a").first(),tt={tooltip:null,data:{}};for(ne.length?tt.title=ne.html():Se&&Se.length?(tt.title=Se.html(),tt.data.href=Se.attr("href"),tt.data.target=Se.attr("target"),tt.tooltip=Se.attr("title")):(tt.title=se.html(),0<=(we=tt.title.search(/
      ul").first()).length?t.ui.fancytree.parseHtml(Q):tt.lazy?void 0:null,$e.push(tt)}),$e},registerExtension:function(Q){I(null!=Q.name,"extensions must have a `name` property."),I(null!=Q.version,"extensions must have a `version` property."),t.ui.fancytree._extensions[Q.name]=Q},trim:x,unescapeHtml:function(Q){var j=document.createElement("div");return j.innerHTML=Q,0===j.childNodes.length?"":j.childNodes[0].nodeValue},warn:function(Q){2<=t.ui.fancytree.debugLevel&&A("warn",arguments)}}),t.ui.fancytree}function I(Q,j){Q||(t.ui.fancytree.error(j="Fancytree assertion failed"+(j=j?": "+j:"")),t.error(j))}function h(Q,j){return Object.prototype.hasOwnProperty.call(Q,j)}function E(Q){return"function"==typeof Q}function x(Q){return null==Q?"":Q.trim()}function A(ge,j){var F,q;if(ge=window.console?window.console[ge]:null)try{ge.apply(window.console,j)}catch{for(q="",F=0;Ful.fancytree-container").remove(),this.rootNode=new K({tree:this},{title:"root",key:"root_"+this._id,children:null,expanded:!0}),this.rootNode.parent=null,Q=t("
        ",{id:"ft-id-"+this._id,class:"ui-fancytree fancytree-container fancytree-plain"}).appendTo(this.$div),this.$container=Q,this.rootNode.ul=Q[0],null==this.options.debugLevel&&(this.options.debugLevel=r.debugLevel)}t.ui.fancytree.warn("Fancytree: ignored duplicate include")},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree.ui-deps"],C):"object"==typeof module&&module.exports?(require("./jquery.fancytree.ui-deps"),module.exports=C(require("jquery"))):C(jQuery),C=function(t){"use strict";return t.ui.fancytree._FancytreeClass.prototype.countSelected=function(e){return this.getSelectedNodes(e).length},t.ui.fancytree._FancytreeNodeClass.prototype.updateCounters=function(){var e=this,r=t("span.fancytree-childcounter",e.span),u=e.tree.options.childcounter,n=e.countChildren(u.deep);!(e.data.childCounter=n)&&u.hideZeros||e.isExpanded()&&u.hideExpanded?r.remove():(r=r.length?r:t("").appendTo(t("span.fancytree-icon,span.fancytree-custom-icon",e.span))).text(n),!u.deep||e.isTopLevel()||e.isRootNode()||e.parent.updateCounters()},t.ui.fancytree.prototype.widgetMethod1=function(e){return e},t.ui.fancytree.registerExtension({name:"childcounter",version:"2.38.3",options:{deep:!0,hideZeros:!0,hideExpanded:!1},foo:42,_appendCounter:function(e){},treeInit:function(e){this._superApply(arguments),this.$container.addClass("fancytree-ext-childcounter")},treeDestroy:function(e){this._superApply(arguments)},nodeRenderTitle:function(e,r){var u=e.node,n=e.options.childcounter,o=null==u.data.childCounter?u.countChildren(n.deep):+u.data.childCounter;this._super(e,r),!o&&n.hideZeros||u.isExpanded()&&n.hideExpanded||t("span.fancytree-icon,span.fancytree-custom-icon",u.span).append(t("").text(o))},nodeSetExpanded:function(e,r,u){var n=e.tree;return this._superApply(arguments).always(function(){n.nodeRenderTitle(e)})}}),t.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],C):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=C(require("jquery"))):C(jQuery),C=function(t){"use strict";var e=t.ui.fancytree.assert;function r(u,n,o){for(var i,a,p=3&u.length,c=u.length-p,f=o,d=3432918353,m=461845907,y=0;y>>16)*d&65535)<<16)&4294967295)<<15|a>>>17))*m+(((a>>>16)*m&65535)<<16)&4294967295)<<13|f>>>19))+((5*(f>>>16)&65535)<<16)&4294967295))+((58964+(i>>>16)&65535)<<16);switch(a=0,p){case 3:a^=(255&u.charCodeAt(y+2))<<16;case 2:a^=(255&u.charCodeAt(y+1))<<8;case 1:f^=a=(65535&(a=(a=(65535&(a^=255&u.charCodeAt(y)))*d+(((a>>>16)*d&65535)<<16)&4294967295)<<15|a>>>17))*m+(((a>>>16)*m&65535)<<16)&4294967295}return f^=u.length,f=2246822507*(65535&(f^=f>>>16))+((2246822507*(f>>>16)&65535)<<16)&4294967295,f=3266489909*(65535&(f^=f>>>13))+((3266489909*(f>>>16)&65535)<<16)&4294967295,f^=f>>>16,n?("0000000"+(f>>>0).toString(16)).substr(-8):f>>>0}return t.ui.fancytree._FancytreeNodeClass.prototype.getCloneList=function(u){var n,o=this.tree,i=o.refMap[this.refKey]||null,a=o.keyMap;return i&&(n=this.key,u?i=t.map(i,function(p){return a[p]}):(i=t.map(i,function(p){return p===n?null:a[p]})).length<1&&(i=null)),i},t.ui.fancytree._FancytreeNodeClass.prototype.isClone=function(){var u;return!!((u=(u=this.refKey||null)&&this.tree.refMap[u]||null)&&1 "+a.getPath(!0),p.error(a),t.error(a)),c[d]=o,m&&((i=f[m])?(i.push(d),2===i.length&&u.options.clones.highlightClones&&c[i[0]].renderStatus()):f[m]=[d])):(null==c[d]&&t.error("clones.treeRegisterNode: node.key not registered: "+o.key),delete c[d],m&&(i=f[m])&&((a=i.length)<=1?(e(1===a),e(i[0]===d),delete f[m]):(function(y,v){for(var b=y.length-1;0<=b;b--)if(y[b]===v)return y.splice(b,1)}(i,d),2===a&&u.options.clones.highlightClones&&c[i[0]].renderStatus())))),this._super(u,n,o)},nodeRenderStatus:function(u){var n,o=u.node,i=this._super(u);return u.options.clones.highlightClones&&(n=t(o[u.tree.statusClassPropName])).length&&o.isClone()&&n.addClass("fancytree-clone"),i},nodeSetActive:function(u,n,o){var i=u.tree.statusClassPropName,a=u.node,p=this._superApply(arguments);return u.options.clones.highlightActiveClones&&a.isClone()&&t.each(a.getCloneList(!0),function(c,f){t(f[i]).toggleClass("fancytree-active-clone",!1!==n)}),p}}),t.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],C):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=C(require("jquery"))):C(jQuery),C=function(t){"use strict";var e,r,u=t.ui.fancytree,n=/Mac/.test(navigator.platform),o="fancytree-drag-source",i="fancytree-drag-remove",a="fancytree-drop-accept",p="fancytree-drop-after",c="fancytree-drop-before",f="fancytree-drop-over",d="fancytree-drop-reject",m="fancytree-drop-target",y="application/x-fancytree-node",v=null,b=null,T=null,M=null,O=null,w=null,g=null,S=null,I=null,h=null;function E(){T=b=w=S=g=h=O=null,M&&M.removeClass(o+" "+i),M=null,v&&v.hide(),r&&(r.remove(),r=null)}function x(U){return 0===U?"":0 "+Ee),w=Ee),Z.isMove="move"===Z.dropEffect,Z.files=K.files||[]}function D(U,Z,ee){var Ee=Z.tree,K=Z.dataTransfer;return"dragstart"!==U.type&&S!==Z.effectAllowed&&Ee.warn("effectAllowed should only be changed in dragstart event: "+U.type+": data.effectAllowed changed from "+S+" -> "+Z.effectAllowed),!1===ee&&(Ee.info("applyDropEffectCallback: allowDrop === false"),Z.effectAllowed="none",Z.dropEffect="none"),Z.isMove="move"===Z.dropEffect,"dragstart"===U.type&&(S=Z.effectAllowed,g=Z.dropEffect),K.effectAllowed=S,K.dropEffect=g}function N(U){var Z,ee=this,Ee=ee.options.dnd5,K=null,Le=u.getNode(U),Q=U.dataTransfer||U.originalEvent.dataTransfer,j={tree:ee,node:Le,options:ee.options,originalEvent:U.originalEvent,widget:ee.widget,hitMode:O,dataTransfer:Q,otherNode:b||null,otherNodeList:T||null,otherNodeData:null,useDefaultImage:!0,dropEffect:void 0,dropEffectSuggested:void 0,effectAllowed:void 0,files:null,isCancelled:void 0,isMove:void 0};switch(U.type){case"dragenter":if(h=null,!Le){ee.debug("Ignore non-node "+U.type+": "+U.target.tagName+"."+U.target.className),O=!1;break}if(t(Le.span).addClass(f).removeClass(a+" "+d),Z=0<=t.inArray(y,Q.types),Ee.preventNonNodes&&!Z){Le.debug("Reject dropping a non-node."),O=!1;break}if(Ee.preventForeignNodes&&(!b||b.tree!==Le.tree)){Le.debug("Reject dropping a foreign node."),O=!1;break}if(Ee.preventSameParent&&j.otherNode&&j.otherNode.tree===Le.tree&&Le.parent===j.otherNode.parent){Le.debug("Reject dropping as sibling (same parent)."),O=!1;break}if(Ee.preventRecursion&&j.otherNode&&j.otherNode.tree===Le.tree&&Le.isDescendantOf(j.otherNode)){Le.debug("Reject dropping below own ancestor."),O=!1;break}if(Ee.preventLazyParents&&!Le.isLoaded()){Le.warn("Drop over unloaded target node prevented."),O=!1;break}v.show(),A(U,j),Z=!!(Z=Ee.dragEnter(Le,j))&&(Z=t.isPlainObject(Z)?{over:!!Z.over,before:!!Z.before,after:!!Z.after}:Array.isArray(Z)?{over:0<=t.inArray("over",Z),before:0<=t.inArray("before",Z),after:0<=t.inArray("after",Z)}:{over:!0===Z||"over"===Z,before:!0===Z||"before"===Z,after:!0===Z||"after"===Z},0!==Object.keys(Z).length&&Z),D(U,j,K=(O=Z)&&(Z.over||Z.before||Z.after));break;case"dragover":if(!Le){ee.debug("Ignore non-node "+U.type+": "+U.target.tagName+"."+U.target.className);break}A(U,j),K=!!(I=function B(U,Z){if(Z.options.dnd5.scroll&&(Q=U,F=(K=(he=Z.tree).options.dnd5).scrollSensitivity,we=K.scrollSpeed,Ee=0,(Le=he.$scrollParent[0])!==document&&"HTML"!==Le.tagName?(K=he.$scrollParent.offset(),q=Le.scrollTop,K.top+Le.offsetHeight-Q.pageYEe.autoExpandMS)||Le.isLoading()||Ee.dragExpand&&!1===Ee.dragExpand(Le,j)||Le.setExpanded():h=Date.now();break;case"dragleave":if(!Le){ee.debug("Ignore non-node "+U.type+": "+U.target.tagName+"."+U.target.className);break}if(!t(Le.span).hasClass(f)){Le.debug("Ignore dragleave (multi).");break}t(Le.span).removeClass(f+" "+a+" "+d),Le.scheduleAction("cancel"),Ee.dragLeave(Le,j),v.hide();break;case"drop":if(0<=t.inArray(y,Q.types)&&(q=Q.getData(y),ee.info(U.type+": getData('application/x-fancytree-node'): '"+q+"'")),q||(q=Q.getData("text"),ee.info(U.type+": getData('text'): '"+q+"'")),q)try{void 0!==(F=JSON.parse(q)).title&&(j.otherNodeData=F)}catch{}ee.debug(U.type+": nodeData: '"+q+"', otherNodeData: ",j.otherNodeData),t(Le.span).removeClass(f+" "+a+" "+d),j.hitMode=I,A(U,j),j.isCancelled=!I;var F=b&&b.span,q=b&&b.tree;Ee.dragDrop(Le,j),U.preventDefault(),F&&!document.body.contains(F)&&(q===ee?(ee.debug("Drop handler removed source element: generating dragEnd."),Ee.dragEnd(b,j)):ee.warn("Drop handler removed source element: dragend event may be lost.")),E()}if(K)return U.preventDefault(),!1}return t.ui.fancytree.getDragNodeList=function(){return T||[]},t.ui.fancytree.getDragNode=function(){return b},t.ui.fancytree.registerExtension({name:"dnd5",version:"2.38.3",options:{autoExpandMS:1500,dropMarkerInsertOffsetX:-16,dropMarkerOffsetX:-24,dropMarkerParent:"body",multiSource:!1,effectAllowed:"all",dropEffectDefault:"move",preventForeignNodes:!1,preventLazyParents:!0,preventNonNodes:!1,preventRecursion:!0,preventSameParent:!1,preventVoidMoves:!0,scroll:!0,scrollSensitivity:20,scrollSpeed:5,setTextTypeJson:!1,sourceCopyHook:null,dragStart:null,dragDrag:t.noop,dragEnd:t.noop,dragEnter:null,dragOver:t.noop,dragExpand:t.noop,dragDrop:t.noop,dragLeave:t.noop},treeInit:function(U){var Z=U.tree,ee=U.options,Ee=ee.glyph||null,K=ee.dnd5;0<=t.inArray("dnd",ee.extensions)&&t.error("Extensions 'dnd' and 'dnd5' are mutually exclusive."),K.dragStop&&t.error("dragStop is not used by ext-dnd5. Use dragEnd instead."),null!=K.preventRecursiveMoves&&t.error("preventRecursiveMoves was renamed to preventRecursion."),K.dragStart&&u.overrideMethod(U.options,"createNode",function(Le,Q){this._super.apply(this,arguments),Q.node.span?Q.node.span.draggable=!0:Q.node.warn("Cannot add `draggable`: no span tag")}),this._superApply(arguments),this.$container.addClass("fancytree-ext-dnd5"),U=t("").appendTo(this.$container),this.$scrollParent=U.scrollParent(),U.remove(),(v=t("#fancytree-drop-marker")).length||(v=t("
        ").hide().css({"z-index":1e3,"pointer-events":"none"}).prependTo(K.dropMarkerParent),Ee&&u.setSpanIcon(v[0],Ee.map._addClass,Ee.map.dropMarker)),v.toggleClass("fancytree-rtl",!!ee.rtl),K.dragStart&&Z.$container.on("dragstart drag dragend",function(Le){var Q=this,j=Q.options.dnd5,F=u.getNode(Le),q=Le.dataTransfer||Le.originalEvent.dataTransfer,ge={tree:Q,node:F,options:Q.options,originalEvent:Le.originalEvent,widget:Q.widget,dataTransfer:q,useDefaultImage:!0,dropEffect:void 0,dropEffectSuggested:void 0,effectAllowed:void 0,files:void 0,isCancelled:void 0,isMove:void 0};switch(Le.type){case"dragstart":if(!F)return Q.info("Ignored dragstart on a non-node."),!1;b=F,T=!1===j.multiSource?[F]:!0===j.multiSource?F.isSelected()?Q.getSelectedNodes():[F]:j.multiSource(F,ge),(M=t(t.map(T,function(he){return he.span}))).addClass(o);var we=F.toDict(!0,j.sourceCopyHook);we.treeId=F.tree._id,we=JSON.stringify(we);try{q.setData(y,we),q.setData("text/html",t(F.span).html()),q.setData("text/plain",F.title)}catch(he){Q.warn("Could not set data (IE only accepts 'text') - "+he)}return q.setData("text",j.setTextTypeJson?we:F.title),A(Le,ge),!1===j.dragStart(F,ge)?(E(),!1):(D(Le,ge),r=null,ge.useDefaultImage&&(e=t(F.span).find(".fancytree-title"),T&&1").text("+"+(T.length-1)).appendTo(e)),q.setDragImage&&q.setDragImage(e[0],-10,-10)),!0);case"drag":A(Le,ge),j.dragDrag(F,ge),D(Le,ge),M.toggleClass(i,ge.isMove);break;case"dragend":A(Le,ge),E(),ge.isCancelled=!I,j.dragEnd(F,ge,!I)}}.bind(Z)),K.dragEnter&&Z.$container.on("dragenter dragover dragleave drop",N.bind(Z))}}),t.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],C):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=C(require("jquery"))):C(jQuery),C=function(t){"use strict";var e=/Mac/.test(navigator.platform),r=t.ui.fancytree.escapeHtml,u=t.ui.fancytree.trim,n=t.ui.fancytree.unescapeHtml;return t.ui.fancytree._FancytreeNodeClass.prototype.editStart=function(){var o,i=this,a=this.tree,p=a.ext.edit,c=a.options.edit,f=t(".fancytree-title",i.span),d={node:i,tree:a,options:a.options,isNew:t(i[a.statusClassPropName]).hasClass("fancytree-edit-new"),orgTitle:i.title,input:null,dirty:!1};if(!1===c.beforeEdit.call(i,{type:"beforeEdit"},d))return!1;t.ui.fancytree.assert(!p.currentNode,"recursive edit"),p.currentNode=this,p.eventData=d,a.widget._unbind(),p.lastDraggableAttrValue=i.span.draggable,p.lastDraggableAttrValue&&(i.span.draggable=!1),t(document).on("mousedown.fancytree-edit",function(m){t(m.target).hasClass("fancytree-edit-input")||i.editEnd(!0,m)}),o=t("",{class:"fancytree-edit-input",type:"text",value:a.options.escapeTitles?d.orgTitle:n(d.orgTitle)}),p.eventData.input=o,null!=c.adjustWidthOfs&&o.width(f.width()+c.adjustWidthOfs),null!=c.inputCss&&o.css(c.inputCss),f.html(o),o.focus().change(function(m){o.addClass("fancytree-edit-dirty")}).on("keydown",function(m){switch(m.which){case t.ui.keyCode.ESCAPE:i.editEnd(!1,m);break;case t.ui.keyCode.ENTER:return i.editEnd(!0,m),!1}m.stopPropagation()}).blur(function(m){return i.editEnd(!0,m)}),c.edit.call(i,{type:"edit"},d)},t.ui.fancytree._FancytreeNodeClass.prototype.editEnd=function(o,i){var a,p=this,c=this.tree,f=c.ext.edit,d=f.eventData,m=c.options.edit,y=t(".fancytree-title",p.span).find("input.fancytree-edit-input");return m.trim&&y.val(u(y.val())),a=y.val(),d.dirty=a!==p.title,d.originalEvent=i,d.save=!1!==o&&(d.isNew||d.dirty)&&""!==a,!(!1===m.beforeClose.call(p,{type:"beforeClose"},d)||d.save&&!1===m.save.call(p,{type:"save"},d)||(y.removeClass("fancytree-edit-dirty").off(),t(document).off(".fancytree-edit"),d.save?(p.setTitle(c.options.escapeTitles?a:r(a)),p.setFocus()):d.isNew?(p.remove(),p=d.node=null,f.relatedNode.setFocus()):(p.renderTitle(),p.setFocus()),f.eventData=null,f.currentNode=null,f.relatedNode=null,c.widget._bind(),p&&f.lastDraggableAttrValue&&(p.span.draggable=!0),c.$container.get(0).focus({preventScroll:!0}),d.input=null,m.close.call(p,{type:"close"},d),0))},t.ui.fancytree._FancytreeNodeClass.prototype.editCreateNode=function(o,i){var a,p=this.tree,c=this;o=o||"child",null==i?i={title:""}:"string"==typeof i?i={title:i}:t.ui.fancytree.assert(t.isPlainObject(i)),"child"!==o||this.isExpanded()||!1===this.hasChildren()?((a=this.addNode(i,o)).match=!0,t(a[p.statusClassPropName]).removeClass("fancytree-hide").addClass("fancytree-match"),a.makeVisible().done(function(){t(a[p.statusClassPropName]).addClass("fancytree-edit-new"),c.tree.ext.edit.relatedNode=c,a.editStart()})):this.setExpanded().done(function(){c.editCreateNode(o,i)})},t.ui.fancytree._FancytreeClass.prototype.isEditing=function(){return this.ext.edit?this.ext.edit.currentNode:null},t.ui.fancytree._FancytreeNodeClass.prototype.isEditing=function(){return!!this.tree.ext.edit&&this.tree.ext.edit.currentNode===this},t.ui.fancytree.registerExtension({name:"edit",version:"2.38.3",options:{adjustWidthOfs:4,allowEmpty:!1,inputCss:{minWidth:"3em"},triggerStart:["f2","mac+enter","shift+click"],trim:!0,beforeClose:t.noop,beforeEdit:t.noop,close:t.noop,edit:t.noop,save:t.noop},currentNode:null,treeInit:function(o){var i=o.tree;this._superApply(arguments),this.$container.addClass("fancytree-ext-edit").on("fancytreebeforeupdateviewport",function(a,p){var c=i.isEditing();c&&(c.info("Cancel edit due to scroll event."),c.editEnd(!1,a))})},nodeClick:function(o){var i=t.ui.fancytree.eventToString(o.originalEvent),a=o.options.edit.triggerStart;return"shift+click"===i&&0<=t.inArray("shift+click",a)&&o.originalEvent.shiftKey||"click"===i&&0<=t.inArray("clickActive",a)&&o.node.isActive()&&!o.node.isEditing()&&t(o.originalEvent.target).hasClass("fancytree-title")?(o.node.editStart(),!1):this._superApply(arguments)},nodeDblclick:function(o){return 0<=t.inArray("dblclick",o.options.edit.triggerStart)?(o.node.editStart(),!1):this._superApply(arguments)},nodeKeydown:function(o){switch(o.originalEvent.which){case 113:if(0<=t.inArray("f2",o.options.edit.triggerStart))return o.node.editStart(),!1;break;case t.ui.keyCode.ENTER:if(0<=t.inArray("mac+enter",o.options.edit.triggerStart)&&e)return o.node.editStart(),!1}return this._superApply(arguments)}}),t.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],C):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=C(require("jquery"))):C(jQuery),C=function(t){"use strict";var e="__not_found__",r=t.ui.fancytree.escapeHtml;function u(o){return(o+"").replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}function n(o,i,a){for(var p=[],c=1;c"}),d.join("")}return t.ui.fancytree._FancytreeClass.prototype._applyFilterImpl=function(o,i,a){var p,c,f,d,m,y,v=0,b=this.options,T=b.escapeTitles,M=b.autoCollapse,O=t.extend({},b.filter,a),w="hide"===O.mode,g=!!O.leavesOnly&&!i;if("string"==typeof o){if(""===o)return this.warn("Fancytree passing an empty string as a filter is handled as clearFilter()."),void this.clearFilter();p=O.fuzzy?o.split("").map(u).reduce(function(S,I){return S+"([^"+I+"]*)"+I},""):u(o),c=new RegExp(p,"i"),f=new RegExp(u(o),"gi"),T&&(d=new RegExp(u("\ufff7"),"g"),m=new RegExp(u("\ufff8"),"g")),o=function(S){if(!S.title)return!1;var h,I=T?S.title:0<=(h=S.title).indexOf(">")?t("
        ").html(h).text():h;return(h=I.match(c))&&O.highlight&&(T?(y=O.fuzzy?n(I,h,T):I.replace(f,function(E){return"\ufff7"+E+"\ufff8"}),S.titleWithHighlight=r(y).replace(d,"").replace(m,"")):S.titleWithHighlight=O.fuzzy?n(I,h):I.replace(f,function(E){return""+E+""})),!!h}}return this.enableFilter=!0,this.lastFilterArgs=arguments,a=this.enableUpdate(!1),this.$div.addClass("fancytree-ext-filter"),this.$div.addClass(w?"fancytree-ext-filter-hide":"fancytree-ext-filter-dimm"),this.$div.toggleClass("fancytree-ext-filter-hide-expanders",!!O.hideExpanders),this.rootNode.subMatchCount=0,this.visit(function(S){delete S.match,delete S.titleWithHighlight,S.subMatchCount=0}),(p=this.getRootNode()._findDirectChild(e))&&p.remove(),b.autoCollapse=!1,this.visit(function(S){if(!g||null==S.children){var I=o(S),h=!1;if("skip"===I)return S.visit(function(E){E.match=!1},!0),"skip";I||!i&&"branch"!==I||!S.parent.match||(h=I=!0),I&&(v++,S.match=!0,S.visitParents(function(E){E!==S&&(E.subMatchCount+=1),!O.autoExpand||h||E.expanded||(E.setExpanded(!0,{noAnimation:!0,noEvents:!0,scrollIntoView:!1}),E._filterAutoExpanded=!0)},!0))}}),b.autoCollapse=M,0===v&&O.nodata&&w&&(!0===(p="function"==typeof(p=O.nodata)?p():p)?p={}:"string"==typeof p&&(p={title:p}),p=t.extend({statusNodeType:"nodata",key:e,title:this.options.strings.noData},p),this.getRootNode().addNode(p).match=!0),this._callHook("treeStructureChanged",this,"applyFilter"),this.enableUpdate(a),v},t.ui.fancytree._FancytreeClass.prototype.filterNodes=function(o,i){return"boolean"==typeof i&&(i={leavesOnly:i},this.warn("Fancytree.filterNodes() leavesOnly option is deprecated since 2.9.0 / 2015-04-19. Use opts.leavesOnly instead.")),this._applyFilterImpl(o,!1,i)},t.ui.fancytree._FancytreeClass.prototype.filterBranches=function(o,i){return this._applyFilterImpl(o,!0,i)},t.ui.fancytree._FancytreeClass.prototype.updateFilter=function(){this.enableFilter&&this.lastFilterArgs&&this.options.filter.autoApply?this._applyFilterImpl.apply(this,this.lastFilterArgs):this.warn("updateFilter(): no filter active.")},t.ui.fancytree._FancytreeClass.prototype.clearFilter=function(){var o,i=this.getRootNode()._findDirectChild(e),a=this.options.escapeTitles,p=this.options.enhanceTitle,c=this.enableUpdate(!1);i&&i.remove(),delete this.rootNode.match,delete this.rootNode.subMatchCount,this.visit(function(f){f.match&&f.span&&(o=t(f.span).find(">span.fancytree-title"),a?o.text(f.title):o.html(f.title),p&&p({type:"enhanceTitle"},{node:f,$title:o})),delete f.match,delete f.subMatchCount,delete f.titleWithHighlight,f.$subMatchBadge&&(f.$subMatchBadge.remove(),delete f.$subMatchBadge),f._filterAutoExpanded&&f.expanded&&f.setExpanded(!1,{noAnimation:!0,noEvents:!0,scrollIntoView:!1}),delete f._filterAutoExpanded}),this.enableFilter=!1,this.lastFilterArgs=null,this.$div.removeClass("fancytree-ext-filter fancytree-ext-filter-dimm fancytree-ext-filter-hide"),this._callHook("treeStructureChanged",this,"clearFilter"),this.enableUpdate(c)},t.ui.fancytree._FancytreeClass.prototype.isFilterActive=function(){return!!this.enableFilter},t.ui.fancytree._FancytreeNodeClass.prototype.isMatched=function(){return!(this.tree.enableFilter&&!this.match)},t.ui.fancytree.registerExtension({name:"filter",version:"2.38.3",options:{autoApply:!0,autoExpand:!1,counter:!0,fuzzy:!1,hideExpandedCounter:!0,hideExpanders:!1,highlight:!0,leavesOnly:!1,nodata:!0,mode:"dimm"},nodeLoadChildren:function(o,i){var a=o.tree;return this._superApply(arguments).done(function(){a.enableFilter&&a.lastFilterArgs&&o.options.filter.autoApply&&a._applyFilterImpl.apply(a,a.lastFilterArgs)})},nodeSetExpanded:function(o,i,a){var p=o.node;return delete p._filterAutoExpanded,!i&&o.options.filter.hideExpandedCounter&&p.$subMatchBadge&&p.$subMatchBadge.show(),this._superApply(arguments)},nodeRenderStatus:function(y){var i=y.node,a=y.tree,p=y.options.filter,c=t(i.span).find("span.fancytree-title"),f=t(i[a.statusClassPropName]),d=y.options.enhanceTitle,m=y.options.escapeTitles;return y=this._super(y),f.length&&a.enableFilter&&(f.toggleClass("fancytree-match",!!i.match).toggleClass("fancytree-submatch",!!i.subMatchCount).toggleClass("fancytree-hide",!(i.match||i.subMatchCount)),!p.counter||!i.subMatchCount||i.isExpanded()&&p.hideExpandedCounter?i.$subMatchBadge&&i.$subMatchBadge.hide():(i.$subMatchBadge||(i.$subMatchBadge=t(""),t("span.fancytree-icon, span.fancytree-custom-icon",i.span).append(i.$subMatchBadge)),i.$subMatchBadge.show().text(i.subMatchCount)),!i.span||i.isEditing&&i.isEditing.call(i)||(i.titleWithHighlight?c.html(i.titleWithHighlight):m?c.text(i.title):c.html(i.title),d&&d({type:"enhanceTitle"},{node:i,$title:c}))),y}}),t.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],C):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=C(require("jquery"))):C(jQuery),C=function(t){"use strict";var e=t.ui.fancytree,r={awesome3:{_addClass:"",checkbox:"icon-check-empty",checkboxSelected:"icon-check",checkboxUnknown:"icon-check icon-muted",dragHelper:"icon-caret-right",dropMarker:"icon-caret-right",error:"icon-exclamation-sign",expanderClosed:"icon-caret-right",expanderLazy:"icon-angle-right",expanderOpen:"icon-caret-down",loading:"icon-refresh icon-spin",nodata:"icon-meh",noExpander:"",radio:"icon-circle-blank",radioSelected:"icon-circle",doc:"icon-file-alt",docOpen:"icon-file-alt",folder:"icon-folder-close-alt",folderOpen:"icon-folder-open-alt"},awesome4:{_addClass:"fa",checkbox:"fa-square-o",checkboxSelected:"fa-check-square-o",checkboxUnknown:"fa-square fancytree-helper-indeterminate-cb",dragHelper:"fa-arrow-right",dropMarker:"fa-long-arrow-right",error:"fa-warning",expanderClosed:"fa-caret-right",expanderLazy:"fa-angle-right",expanderOpen:"fa-caret-down",loading:{html:""},nodata:"fa-meh-o",noExpander:"",radio:"fa-circle-thin",radioSelected:"fa-circle",doc:"fa-file-o",docOpen:"fa-file-o",folder:"fa-folder-o",folderOpen:"fa-folder-open-o"},awesome5:{_addClass:"",checkbox:"far fa-square",checkboxSelected:"far fa-check-square",checkboxUnknown:"fas fa-square fancytree-helper-indeterminate-cb",radio:"far fa-circle",radioSelected:"fas fa-circle",radioUnknown:"far fa-dot-circle",dragHelper:"fas fa-arrow-right",dropMarker:"fas fa-long-arrow-alt-right",error:"fas fa-exclamation-triangle",expanderClosed:"fas fa-caret-right",expanderLazy:"fas fa-angle-right",expanderOpen:"fas fa-caret-down",loading:"fas fa-spinner fa-pulse",nodata:"far fa-meh",noExpander:"",doc:"far fa-file",docOpen:"far fa-file",folder:"far fa-folder",folderOpen:"far fa-folder-open"},bootstrap3:{_addClass:"glyphicon",checkbox:"glyphicon-unchecked",checkboxSelected:"glyphicon-check",checkboxUnknown:"glyphicon-expand fancytree-helper-indeterminate-cb",dragHelper:"glyphicon-play",dropMarker:"glyphicon-arrow-right",error:"glyphicon-warning-sign",expanderClosed:"glyphicon-menu-right",expanderLazy:"glyphicon-menu-right",expanderOpen:"glyphicon-menu-down",loading:"glyphicon-refresh fancytree-helper-spin",nodata:"glyphicon-info-sign",noExpander:"",radio:"glyphicon-remove-circle",radioSelected:"glyphicon-ok-circle",doc:"glyphicon-file",docOpen:"glyphicon-file",folder:"glyphicon-folder-close",folderOpen:"glyphicon-folder-open"},material:{_addClass:"material-icons",checkbox:{text:"check_box_outline_blank"},checkboxSelected:{text:"check_box"},checkboxUnknown:{text:"indeterminate_check_box"},dragHelper:{text:"play_arrow"},dropMarker:{text:"arrow-forward"},error:{text:"warning"},expanderClosed:{text:"chevron_right"},expanderLazy:{text:"last_page"},expanderOpen:{text:"expand_more"},loading:{text:"autorenew",addClass:"fancytree-helper-spin"},nodata:{text:"info"},noExpander:{text:""},radio:{text:"radio_button_unchecked"},radioSelected:{text:"radio_button_checked"},doc:{text:"insert_drive_file"},docOpen:{text:"insert_drive_file"},folder:{text:"folder"},folderOpen:{text:"folder_open"}}};function u(n,o,i,d,p){var c=(m=d.map)[p],f=t(o),m=(d=f.find(".fancytree-childcounter"),i+" "+(m._addClass||""));"string"==typeof(c="function"==typeof c?c.call(this,n,o,p):c)?(o.innerHTML="",f.attr("class",m+" "+c).append(d)):c&&(c.text?o.textContent=""+c.text:o.innerHTML=c.html?c.html:"",f.attr("class",m+" "+(c.addClass||"")).append(d))}return t.ui.fancytree.registerExtension({name:"glyph",version:"2.38.3",options:{preset:null,map:{}},treeInit:function(i){var o=i.tree;(i=i.options.glyph).preset?(e.assert(!!r[i.preset],"Invalid value for `options.glyph.preset`: "+i.preset),i.map=t.extend({},r[i.preset],i.map)):o.warn("ext-glyph: missing `preset` option."),this._superApply(arguments),o.$container.addClass("fancytree-ext-glyph")},nodeRenderStatus:function(n){var o,i,a=n.node,p=t(a.span),c=n.options.glyph,f=this._super(n);return a.isRootNode()||((i=p.children(".fancytree-expander").get(0))&&(o=a.expanded&&a.hasChildren()?"expanderOpen":a.isUndefined()?"expanderLazy":a.hasChildren()?"expanderClosed":"noExpander",u(a,i,"fancytree-expander",c,o)),(i=(a.tr?t("td",a.tr).find(".fancytree-checkbox"):p.children(".fancytree-checkbox")).get(0))&&(n=e.evalOption("checkbox",a,a,c,!1),a.parent&&a.parent.radiogroup||"radio"===n?u(a,i,"fancytree-checkbox fancytree-radio",c,o=a.selected?"radioSelected":"radio"):u(a,i,"fancytree-checkbox",c,o=a.selected?"checkboxSelected":a.partsel?"checkboxUnknown":"checkbox")),(i=p.children(".fancytree-icon").get(0))&&(o=a.statusNodeType||(a.folder?a.expanded&&a.hasChildren()?"folderOpen":"folder":a.expanded?"docOpen":"doc"),u(a,i,"fancytree-icon",c,o))),f},nodeSetStatus:function(d,o,i,a){var p,c=d.options.glyph,f=d.node;return d=this._superApply(arguments),"error"!==o&&"loading"!==o&&"nodata"!==o||(f.parent?(p=t(".fancytree-expander",f.span).get(0))&&u(f,p,"fancytree-expander",c,o):(p=t(".fancytree-statusnode-"+o,f[this.nodeContainerAttrName]).find(".fancytree-icon").get(0))&&u(f,p,"fancytree-icon",c,o)),d}}),t.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],C):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=C(require("jquery"))):C(jQuery),C=function(t){"use strict";var e=t.ui.keyCode,r={text:[e.UP,e.DOWN],checkbox:[e.UP,e.DOWN,e.LEFT,e.RIGHT],link:[e.UP,e.DOWN,e.LEFT,e.RIGHT],radiobutton:[e.UP,e.DOWN,e.LEFT,e.RIGHT],"select-one":[e.LEFT,e.RIGHT],"select-multiple":[e.LEFT,e.RIGHT]};return t.ui.fancytree.registerExtension({name:"gridnav",version:"2.38.3",options:{autofocusInput:!1,handleCursorKeys:!0},treeInit:function(n){this._requireExtension("table",!0,!0),this._superApply(arguments),this.$container.addClass("fancytree-ext-gridnav"),this.$container.on("focusin",function(o){var i=t.ui.fancytree.getNode(o.target);i&&!i.isActive()&&(o=n.tree._makeHookContext(i,o),n.tree._callHook("nodeSetActive",o,!0))})},nodeSetActive:function(n,o,i){var a=n.options.gridnav,p=n.node,c=t((c=n.originalEvent||{}).target).is(":input");o=!1!==o,this._superApply(arguments),o&&(n.options.titlesTabbable?(c||(t(p.span).find("span.fancytree-title").focus(),p.setFocus()),n.tree.$container.attr("tabindex","-1")):a.autofocusInput&&!c&&t(p.tr||p.span).find(":input:enabled").first().focus())},nodeKeydown:function(c){var o,i,a=c.options.gridnav,p=c.originalEvent;return(c=t(p.target)).is(":input:enabled")?o=c.prop("type"):c.is("a")&&(o="link"),o&&a.handleCursorKeys?!((o=r[o])&&0<=t.inArray(p.which,o)&&(i=function u(n,o){var i,a,p,c,f,d,m=n.closest("td"),y=null;switch(o){case e.LEFT:y=m.prev();break;case e.RIGHT:y=m.next();break;case e.UP:case e.DOWN:for(p=i=m.parent(),f=m.get(0),d=0,p.children().each(function(){return this!==f&&(c=t(this).prop("colspan"),void(d+=c||1))}),a=d;(i=o===e.UP?i.prev():i.next()).length&&(i.is(":hidden")||!(y=function(v,b){var T,M=null,O=0;return v.children().each(function(){return b<=O?(M=t(this),!1):(T=t(this).prop("colspan"),void(O+=T||1))}),M}(i,a))||!y.find(":input,a").length););}return y}(c,p.which))&&i.length&&(i.find(":input:enabled,a").focus(),1)):this._superApply(arguments)}}),t.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree","./jquery.fancytree.table"],C):"object"==typeof module&&module.exports?(require("./jquery.fancytree.table"),module.exports=C(require("jquery"))):C(jQuery),C=function(t){"use strict";return t.ui.fancytree.registerExtension({name:"multi",version:"2.38.3",options:{allowNoSelect:!1,mode:"sameParent"},treeInit:function(e){this._superApply(arguments),this.$container.addClass("fancytree-ext-multi"),1===e.options.selectMode&&t.error("Fancytree ext-multi: selectMode: 1 (single) is not compatible.")},nodeClick:function(e){var r=e.tree,u=e.node,n=r.getActiveNode()||r.getFirstChild(),o="checkbox"===e.targetType,i="expander"===e.targetType;switch(t.ui.fancytree.eventToString(e.originalEvent)){case"click":if(i)break;o||(r.selectAll(!1),u.setSelected());break;case"shift+click":r.visitRows(function(a){if(a.setSelected(),a===u)return!1},{start:n,reverse:n.isBelowOf(u)});break;case"ctrl+click":case"meta+click":return void u.toggleSelected()}return this._superApply(arguments)},nodeKeydown:function(e){var r=e.tree,u=e.node,n=e.originalEvent;switch(t.ui.fancytree.eventToString(n)){case"up":case"down":r.selectAll(!1),u.navigate(n.which,!0),r.getActiveNode().setSelected();break;case"shift+up":case"shift+down":u.navigate(n.which,!0),r.getActiveNode().setSelected()}return this._superApply(arguments)}}),t.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],C):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=C(require("jquery"))):C(jQuery),C=function(t){"use strict";var e=null,r=null,u=null,n=t.ui.fancytree.assert,o="active",i="expanded",a="focus",p="selected";try{n(window.localStorage&&window.localStorage.getItem),r={get:function(c){return window.localStorage.getItem(c)},set:function(c,f){window.localStorage.setItem(c,f)},remove:function(c){window.localStorage.removeItem(c)}}}catch(c){t.ui.fancytree.warn("Could not access window.localStorage",c)}try{n(window.sessionStorage&&window.sessionStorage.getItem),u={get:function(c){return window.sessionStorage.getItem(c)},set:function(c,f){window.sessionStorage.setItem(c,f)},remove:function(c){window.sessionStorage.removeItem(c)}}}catch(c){t.ui.fancytree.warn("Could not access window.sessionStorage",c)}return"function"==typeof Cookies?e={get:Cookies.get,set:function(c,f){Cookies.set(c,f,this.options.persist.cookie)},remove:Cookies.remove}:t&&"function"==typeof t.cookie&&(e={get:t.cookie,set:function(c,f){t.cookie(c,f,this.options.persist.cookie)},remove:t.removeCookie}),t.ui.fancytree._FancytreeClass.prototype.clearPersistData=function(c){var f=this.ext.persist,d=f.cookiePrefix;0<=(c=c||"active expanded focus selected").indexOf(o)&&f._data(d+o,null),0<=c.indexOf(i)&&f._data(d+i,null),0<=c.indexOf(a)&&f._data(d+a,null),0<=c.indexOf(p)&&f._data(d+p,null)},t.ui.fancytree._FancytreeClass.prototype.clearCookies=function(c){return this.warn("'tree.clearCookies()' is deprecated since v2.27.0: use 'clearPersistData()' instead."),this.clearPersistData(c)},t.ui.fancytree._FancytreeClass.prototype.getPersistData=function(){var c=this.ext.persist,f=c.cookiePrefix,d=c.cookieDelimiter,m={};return m[o]=c._data(f+o),m[i]=(c._data(f+i)||"").split(d),m[p]=(c._data(f+p)||"").split(d),m[a]=c._data(f+a),m},t.ui.fancytree.registerExtension({name:"persist",version:"2.38.3",options:{cookieDelimiter:"~",cookiePrefix:void 0,cookie:{raw:!1,expires:"",path:"",domain:"",secure:!1},expandLazy:!1,expandOpts:void 0,fireActivate:!0,overrideSource:!0,store:"auto",types:"active expanded focus selected"},_data:function(c,f){var d=this._local.store;if(void 0===f)return d.get.call(this,c);null===f?d.remove.call(this,c):d.set.call(this,c,f)},_appendKey:function(b,f,d){f=""+f;var T,m=this._local,y=this.options.persist.cookieDelimiter,v=m.cookiePrefix+b;b=(T=m._data(v))?T.split(y):[],0<=(T=t.inArray(f,b))&&b.splice(T,1),d&&b.push(f),m._data(v,b.join(y))},treeInit:function(c){var f=c.tree,d=c.options,m=this._local,y=this.options.persist;return m.cookiePrefix=y.cookiePrefix||"fancytree-"+f._id+"-",m.storeActive=0<=y.types.indexOf(o),m.storeExpanded=0<=y.types.indexOf(i),m.storeSelected=0<=y.types.indexOf(p),m.storeFocus=0<=y.types.indexOf(a),m.store=null,"auto"===y.store&&(y.store=r?"local":"cookie"),t.isPlainObject(y.store)?m.store=y.store:"cookie"===y.store?m.store=e:"local"!==y.store&&"session"!==y.store||(m.store="local"===y.store?r:u),n(m.store,"Need a valid store."),f.$div.on("fancytreeinit",function(v){var b,T,M,O,w,g;!1!==f._triggerTreeEvent("beforeRestore",null,{})&&(M=m._data(m.cookiePrefix+a),O=!1===y.fireActivate,w=m._data(m.cookiePrefix+i),g=w&&w.split(y.cookieDelimiter),(m.storeExpanded?function S(I,h,E,x,A){var D,B,N,U,Z=!1,ee=I.options.persist.expandOpts,Ee=[],K=[];for(E=E||[],A=A||t.Deferred(),D=0,N=E.length;Dtbody")).length||(f.find(">tr").length&&t.error("Expected table > tbody > tr. If you see this please open an issue."),i=t("").appendTo(f)),a.tbody=i[0],a.columnCount=t("thead >tr",f).last().find(">th",f).length,(o=i.children("tr").first()).length)u=o.children("td").length,a.columnCount&&u!==a.columnCount&&(a.warn("Column count mismatch between thead ("+a.columnCount+") and tbody ("+u+"): using tbody."),a.columnCount=u),o=o.clone();else for(e(1<=a.columnCount,"Need either or with elements to determine column count."),o=t(""),n=0;n");o.find(">td").eq(c.nodeColumnIdx).html(""),p.aria&&(o.attr("role","row"),o.find("td").attr("role","gridcell")),a.rowFragment=document.createDocumentFragment(),a.rowFragment.appendChild(o.get(0)),i.empty(),a.statusClassPropName="tr",a.ariaPropName="tr",this.nodeContainerAttrName="tr",a.$container=f,this._superApply(arguments),t(a.rootNode.ul).remove(),a.rootNode.ul=null,this.$container.attr("tabindex",p.tabindex),p.aria&&a.$container.attr("role","treegrid").attr("aria-readonly",!0)},nodeRemoveChildMarkup:function(u){u.node.visit(function(n){n.tr&&(t(n.tr).remove(),n.tr=null)})},nodeRemoveMarkup:function(u){var n=u.node;n.tr&&(t(n.tr).remove(),n.tr=null),this.nodeRemoveChildMarkup(u)},nodeRender:function(u,n,o,i,a){var p,c,f,d,m,y,v,b,T,M=u.tree,O=u.node,w=u.options,g=!O.parent;if(!1!==M._enableUpdate){if(a||(u.hasCollapsedParents=O.parent&&!O.parent.expanded),!g)if(O.tr&&n&&this.nodeRemoveMarkup(u),O.tr)n?this.nodeRenderTitle(u):this.nodeRenderStatus(u);else{if(u.hasCollapsedParents&&!o)return;m=M.rowFragment.firstChild.cloneNode(!0),b=function(S){var I,h,E=S.parent,x=E?E.children:null;if(x&&1td").eq(0).prop("colspan",o.columnCount).text(i.title).addClass("fancytree-status-merged").nextAll().remove():a.renderColumns&&a.renderColumns.call(o,{type:"renderColumns"},u)),c},nodeRenderStatus:function(u){var n=u.node,o=u.options;this._super(u),t(n.tr).removeClass("fancytree-node"),u=(n.getLevel()-1)*o.table.indentation,o.rtl?t(n.span).css({paddingRight:u+"px"}):t(n.span).css({paddingLeft:u+"px"})},nodeSetExpanded:function(u,n,o){if(n=!1!==n,u.node.expanded&&n||!u.node.expanded&&!n)return this._superApply(arguments);var i=new t.Deferred,a=t.extend({},o,{noEvents:!0,noAnimation:!0});function p(c){c?(r(u.node,n),n&&u.options.autoScroll&&!o.noAnimation&&u.node.hasChildren()?u.node.getLastChild().scrollIntoView(!0,{topNode:u.node}).always(function(){o.noEvents||u.tree._triggerNodeEvent(n?"expand":"collapse",u),i.resolveWith(u.node)}):(o.noEvents||u.tree._triggerNodeEvent(n?"expand":"collapse",u),i.resolveWith(u.node))):(o.noEvents||u.tree._triggerNodeEvent(n?"expand":"collapse",u),i.rejectWith(u.node))}return o=o||{},this._super(u,n,a).done(function(){p(!0)}).fail(function(){p(!1)}),i.promise()},nodeSetStatus:function(u,n,o,i){return"ok"!==n||(u=(u=u.node).children?u.children[0]:null)&&u.isStatusNode()&&t(u.tr).remove(),this._superApply(arguments)},treeClear:function(u){return this.nodeRemoveChildMarkup(this._makeHookContext(this.rootNode)),this._superApply(arguments)},treeDestroy:function(u){return this.$container.find("tbody").empty(),this.$source&&this.$source.removeClass("fancytree-helper-hidden"),this._superApply(arguments)}}),t.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],C):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=C(require("jquery"))):C(jQuery),C=function(t){"use strict";return t.ui.fancytree.registerExtension({name:"themeroller",version:"2.38.3",options:{activeClass:"ui-state-active",addClass:"ui-corner-all",focusClass:"ui-state-focus",hoverClass:"ui-state-hover",selectedClass:"ui-state-highlight"},treeInit:function(e){var r=e.widget.element,u=e.options.themeroller;this._superApply(arguments),"TABLE"===r[0].nodeName?(r.addClass("ui-widget ui-corner-all"),r.find(">thead tr").addClass("ui-widget-header"),r.find(">tbody").addClass("ui-widget-conent")):r.addClass("ui-widget ui-widget-content ui-corner-all"),r.on("mouseenter mouseleave",".fancytree-node",function(i){var o=t.ui.fancytree.getNode(i.target);i="mouseenter"===i.type,t(o.tr||o.span).toggleClass(u.hoverClass+" "+u.addClass,i)})},treeDestroy:function(e){this._superApply(arguments),e.widget.element.removeClass("ui-widget ui-widget-content ui-corner-all")},nodeRenderStatus:function(e){var r={},u=e.node,n=t(u.tr||u.span),o=e.options.themeroller;this._super(e),r[o.activeClass]=!1,r[o.focusClass]=!1,r[o.selectedClass]=!1,u.isActive()&&(r[o.activeClass]=!0),u.hasFocus()&&(r[o.focusClass]=!0),u.isSelected()&&!u.isActive()&&(r[o.selectedClass]=!0),n.toggleClass(o.activeClass,r[o.activeClass]),n.toggleClass(o.focusClass,r[o.focusClass]),n.toggleClass(o.selectedClass,r[o.selectedClass]),n.addClass(o.addClass)}}),t.ui.fancytree},"function"==typeof define&&define.amd?define(["jquery","./jquery.fancytree"],C):"object"==typeof module&&module.exports?(require("./jquery.fancytree"),module.exports=C(require("jquery"))):C(jQuery),C=function(t){"use strict";var e=/^([+-]?(?:\d+|\d*\.\d+))([a-z]*|%)$/;function r(n,o){var i=t("#"+(n="fancytree-style-"+n));if(o){i.length||(i=t("