diff --git a/.prettierignore b/.prettierignore index c6c190c..a156cc6 100644 --- a/.prettierignore +++ b/.prettierignore @@ -30,7 +30,7 @@ core/scripts/APIandLibraries/**/* .vscode # workbox -workbox-config.js +workbox-config.cjs core/scripts/serviceWorker # Visual Studio Code diff --git a/README.md b/README.md index 3e687eb..51fc680 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ SciGrade is an online web-tool that allows students (or any user with access) to ## Getting Started -It is recommended that you use the web-version available at . For local runs, the landing page [index.html](index.html) links to the runtime page [core/systemrun.html](core/systemrun.html), which initializes the practice flow by default and hides the account UI in the navigation. +It is recommended that you use the web-version available at . For local runs, the landing page [index.html](index.html) links to the runtime page [core/systemrun.html](core/systemrun.html), which initializes the practice flow by default. ## Browser Compatibilities diff --git a/core/scripts/crispr_scripts.js b/core/scripts/crispr_scripts.js index 788a38f..514bb7c 100644 --- a/core/scripts/crispr_scripts.js +++ b/core/scripts/crispr_scripts.js @@ -1,4 +1,3 @@ -/* eslint-disable indent */ //= ================================ SciGrade ================================== // // Purpose: General script for SciGrade @@ -212,7 +211,7 @@ let MARPAMseq = false; let MARCutPos = false; let MARstrand = false; let MAROffTarget = false; -let MAROffTarget_degree = 0; // 0 wrong, 1 above 75, 2 above 35, 3 only option +let MAROffTarget_degree = 0; // 0 wrong, 1 optimal or >=35 with max < 80, 2 >=35 with max >= 80, 3 only option let MAROffTarget_aboveOpt = false; let MAROffTarget_above35 = false; let MAROffTarget_onlyOption = false; @@ -402,7 +401,7 @@ let offtarget_Use = []; function checkOffTarget(score) { // Reset variables: MAROffTarget = false; - MAROffTarget_degree = 0; // 0 wrong, 1 above 75, 2 above 35, 3 only option + MAROffTarget_degree = 0; // 0 wrong, 1 optimal or >=35 with max < 80, 2 >=35 with max >= 80, 3 only option MAROffTarget_aboveOpt = false; MAROffTarget_above35 = false; MAROffTarget_onlyOption = false; @@ -446,17 +445,7 @@ function checkOffTarget(score) { // Is it within the optimal range? const Max_range = Math.max.apply(null, offtarget_List); - const Min_optimal = Max_range - Max_range * 0.2; - let optimalValue = Min_optimal; - const { studentClass } = student_reg_information[0].student_list[studentParseNum]; - // Change optimal range based on custom input - if (student_reg_information[0].classMarkingMod[studentClass][0] === "Optimal") { - if (Min_optimal > 80 || Min_optimal < 35) { - optimalValue = 80; - } - } else { - optimalValue = student_reg_information[0].classMarkingMod[studentClass][0]; - } + const optimalValue = getOffTargetOptimalValue(Max_range); // Determine if off-target is optimal or not if (InputOffTargetValue >= optimalValue) { MAROffTarget_aboveOpt = true; @@ -476,6 +465,18 @@ function checkOffTarget(score) { } } +/** + * Determine the default optimal off-target threshold for practice mode. + * @param {number} maxRange Maximum specificity score in the local window + */ +function getOffTargetOptimalValue(maxRange) { + const minOptimal = maxRange - maxRange * 0.2; + if (minOptimal > 80 || minOptimal < 35) { + return 80; + } + return minOptimal; +} + let possible_F1_primers = []; /** * Checks whether the F1 primer matches one of the generated candidates. @@ -542,6 +543,7 @@ function createComplementarySeq(seq) { for (const element of seq) { comp_seq = complementary_nt_dict[element] + comp_seq; } + return comp_seq; } let studentMark = 0; @@ -816,531 +818,9 @@ function showNewInput(docCheck, checkFor, docDisplay) { } let completed_assignments = []; -/** - * Generates a list of completed_assignments - */ -function generateCompletedAssignmentList() { - completed_assignments = []; - // Assignments - if (student_reg_information[0].student_list[studentParseNum]["assignment-HBB-Marks"]) { - if (!completed_assignments.includes("HBB")) { - completed_assignments.push("HBB"); - } - } - if (student_reg_information[0].student_list[studentParseNum]["assignment-CCR5-Marks"]) { - if (!completed_assignments.includes("CCR5")) { - completed_assignments.push("CCR5"); - } - } - if (student_reg_information[0].student_list[studentParseNum]["assignment-ANKK1-Marks"]) { - if (!completed_assignments.includes("ANKK1")) { - completed_assignments.push("ANKK1"); - } - } - if (student_reg_information[0].student_list[studentParseNum]["assignment-APOE-Marks"]) { - if (!completed_assignments.includes("APOE")) { - completed_assignments.push("APOE"); - } - } -} - -/** - * Account management functions. This function depends on login.js, without that, this will not run! - */ -function openAccountManagement() { - generateCompletedAssignmentList(); - $("#accountManagementBody").empty(); - let append_str = ` -
-

- Hello ${student_reg_information[0].student_list[studentParseNum].name.split(" ")[0]}! -

-
- `; - $("#accountManagementBody").append(append_str); - - // Create assignments card - append_str = ` -
-
-
- -
-
- -
-
- ${ - completed_assignments.length > 0 - ? ` -

You have completed the following assignments:

-

    - ${completed_assignments.map((assignment) => `
  • ${assignment}
  • `).join("")} -
- ` - : ` -

You have not completed any assignments yet.

- ` - } -
-
-
- `; - - $("#accountManagementBody").append(append_str); - - const classList = student_reg_information[0].class_list; - // Obtain student marks - if (["admin", "TA"].includes(student_reg_information[0].student_list[studentParseNum].type)) { - append_str = ` -
- -

- Oh wait! Hello ${student_reg_information[0].student_list[studentParseNum].type}! Would you like to download student marks? -

- - - - - -

- - - -

- `; - - $("#accountManagementBody").append(append_str); - } - - // TA access to add new students: - if ( - student_reg_information[0].student_list[studentParseNum].type === "TA" || - student_reg_information[0].student_list[studentParseNum].type === "admin" - ) { - append_str = "

ADMIN POWER!

"; - - // Create student card - append_str += "

"; - append_str += "
"; - append_str += "
"; - append_str += - ""; - append_str += "
"; - append_str += "
"; - append_str += "
"; - append_str += "
"; - - // Append all students at once: - append_str += - "

If you would like to create a new class, just fill the form below: "; - // Form opening - append_str += "

"; - - // Class choice: - append_str += '
'; - append_str += ''; - append_str += `"; - append_str += - ''; - append_str += - 'Choose class students will be added to or create a new class. Example of a new class: HMB396 - Winter - 2019 (NOTE: Spaces will be deleted once you submit so use capital letters to separate words)'; - append_str += "
"; - - // User number - append_str += '
'; - append_str += ''; - append_str += - ''; - append_str += - 'Input student numbers, separated by commas, new lines and/or tab indentation (BEWARE OF TYPOS!)'; - append_str += "
"; - - // User uMail - append_str += '
'; - append_str += - ''; - append_str += - ''; - append_str += - 'Input student University associated email in the same order of the student numbers, separated by commas, new lines and/or tab indentation (BEWARE OF TYPOS!)'; - append_str += "
"; - - // Close form - append_str += "
"; - - // Close card - append_str += "
"; - append_str += "
"; - append_str += "
"; - $("#accountManagementBody").append(append_str); - - // Create single card - append_str = "
"; - append_str += "
"; - append_str += "
"; - append_str += - ""; - append_str += "
"; - append_str += "
"; - append_str += "
"; - append_str += "
"; - - // Append TAs or Admins: - append_str += - "

If you would like to add a single user (students, TAs or admins), please fill in the form below. Please note, this will default the user as a student. Only admins will be able to create new TAs or admins

"; - // Form opening - append_str += "
"; - - // Class choice: - append_str += '
'; - append_str += ''; - append_str += - '"; - append_str += - 'Choose class TA will be added.'; - append_str += "
"; - - // User number - append_str += '
'; - append_str += ''; - append_str += ''; - append_str += - 'The user\'s access number (like a student or employee number)'; - append_str += "
"; - - // User umail - append_str += '
'; - append_str += ''; - append_str += - ''; - append_str += - 'The user\'s University associated email'; - append_str += "
"; - - // Form closing - append_str += "
"; - - // Close card - append_str += "
"; - append_str += "
"; - append_str += "
"; - $("#accountManagementBody").append(append_str); - } - - // Admin controls: - if (student_reg_information[0].student_list[studentParseNum].type === "admin") { - // Create modifying controls card - append_str = "
"; - append_str += "
"; - append_str += "
"; - append_str += - ""; - append_str += "
"; - append_str += "
"; - append_str += - "
"; - append_str += "
"; - - // Info: - append_str += - "

If you would like to change a class's off-target optimal goal, you can do that here

"; - - // Choose class: - append_str += '
'; - append_str += ''; - append_str += `"; - append_str += - 'Choose the class for which you are modifying marking scheme for.'; - append_str += "
"; - - // Modify controls: - append_str += '
'; - append_str += '

Modify controls for:

'; - append_str += ''; - - append_str += '

Current off-target marking is set to:

'; - append_str += - '"; - append_str += - ''; - append_str += - 'Choose how you want the off-target score to be marked. Optimal is Min_optimal = Max_range - (Max_range * 0.2) if below 80 (if below, optimal = 80). Custom value can be any number between 0.01 and 100 which will be the new custom "optimal" value for your class.'; - append_str += "
"; - - // Close card - append_str += "
"; - append_str += "
"; - append_str += "
"; - $("#accountManagementBody").append(append_str); - - // Modify user's type card - append_str = "
"; - append_str += "
"; - append_str += "
"; - append_str += - ""; - append_str += "
"; - append_str += "
"; - append_str += "
"; - append_str += "
"; - - // Info: - append_str += - "

If you would like to change a user's account type (to TA or admin or back to student), you can do that below

"; - - // Choose class: - append_str += '
'; - append_str += ''; - append_str += `"; - append_str += - 'Choose the class for which the user belongs to.'; - append_str += "
"; - - // Choose user: - append_str += '
'; - append_str += ''; - append_str += - '"; - append_str += - 'Choose the user for which you change their account type for.'; - append_str += "
"; - - // Choose type: - append_str += '
'; - append_str += ''; - append_str += - '"; - append_str += - 'Choose the type for which you want the user to become.'; - append_str += "
"; - - // Submit button - append_str += "
"; - - // Close card - append_str += "
"; - append_str += "
"; - append_str += "
"; - $("#accountManagementBody").append(append_str); - } - - // Close account management - append_str = ""; - $("#accountManagementBody").append(append_str); - - // Open the Bootstrap modal - $("#accountModal").modal("show"); -} - -/** - * Update the choose user's options in the account management's change user type - * @param {string} domUser - */ -function UpdateChooseUser(domUser) { - ClearSelectOptions(domUser); - for (const key in updatedListOfStudents) { - if (updatedListOfStudents[key]) { - AddToOptions(domUser, key, updatedListOfStudents[key]); - } - } -} - -/** - * Add more options to a select - * @param {string} domID The DOM ID in the HTML file for the select - * @param {string} optionsValue The value and ID for the options being added - * @param {string} optionsInner The InnerHTML for the options being added - */ -function AddToOptions(domID, optionsValue, optionsInner) { - const dom = document.getElementById(domID); - const option = document.createElement("option"); - option.value = optionsValue; - option.innerHTML = optionsInner; - dom.appendChild(option); -} - -/** - * Clear an select's options - * @param {string} domID The DOM ID in the HTML for the select - */ -function ClearSelectOptions(domID) { - const dom = document.getElementById(domID); - while (dom.options.length > 0) { - dom.options.remove(0); - } -} - -let updatedListOfStudents = {}; -/** - * Update the list of students available for a class - * @param {string} className The class for which the students belong to - */ -function UpdateStudentList(className) { - updatedListOfStudents = {}; - const studentList = student_reg_information[0].student_list; - for (const student of studentList) { - if (student.studentClass === className) { - updatedListOfStudents[student.name] = `${student.name} - ${student.type}`; - } - } -} - -/** - * Change a DOM's innerHTML - * @param {string} domID DOM's ID that is being modified - * @param {string} changeTo The content of the innerHTML - */ -function ChangeDOMInnerhtml(domID, changeTo) { - document.getElementById(domID).innerHTML = changeTo; -} - -const downloadIndexTable_start = "\t\t\n\t\t\tStudent Number\n\t\t\tName"; -const downloadIndexTable_end = "\n\t\t\n"; -let downloadIndexTable_fill = ""; -/** - * Generates the base index table header used for CSV export. - * @param {string} whichIndexTable Placeholder parameter for compatibility - * @param {boolean} SimpleComplex Placeholder parameter for compatibility - */ -function generateRestOfIndexTable(whichIndexTable, SimpleComplex) { - whichIndexTable = downloadIndexTable_start; - whichIndexTable += downloadIndexTable_end; - return whichIndexTable; -} - -/** - * Generated a download button from JSON to CSV - * @param {string} whichClass Which class is being downloaded - * @param {boolean} whichType True is simple, false is complex - */ -function generateHiddenStudentDownload(whichClass, whichType) { - // Check if TA/Admin - if ( - student_reg_information[0].student_list[studentParseNum].type === "TA" || - student_reg_information[0].student_list[studentParseNum].type === "admin" - ) { - downloadIndexTable_fill = generateRestOfIndexTable(downloadIndexTable_fill, whichType); - $("#hiddenDownloadModal_table").empty(); // reset - const d = new Date(); - let downloadIndexTable_str = "\n\t\n"; - let captionTitleBegin = "SciGrade_studentMark_"; - if (!whichType) { - captionTitleBegin = "SciGrade_studentMarkRaw_"; - } - downloadIndexTable_str += `\t\t\n`; - downloadIndexTable_str += downloadIndexTable_fill; - // Looping through each row of the table - const studentRegList = student_reg_information[0].student_list; - for (const student of studentRegList) { - if (student.type === "Student" && student.studentClass === whichClass) { - downloadIndexTable_str += "\t\t\n"; - downloadIndexTable_str += `\t\t\t\n`; - downloadIndexTable_str += `\t\t\t\n`; - downloadIndexTable_str += "\t\t\n"; - } - } - downloadIndexTable_str += "\t\n
${captionTitleBegin}${student_reg_information[0].student_list[ - studentParseNum - ].name.replace(/\s/g, "")}_${d.getFullYear()}-${d.getMonth()}_${d.getDate()}
${student.student_number}${student.name}
"; // Closing - document.getElementById("hiddenDownloadModal_table").innerHTML += downloadIndexTable_str; - $("#hiddenDownloadModal_table").tableToCSV(); - } else { - showRegError(7); - } -} - -/** - * Updates one input value when another input matches a target value. - * @param {string} docCheck The DOM ID to check - * @param {string} checkFor The value to compare against - * @param {string} docChange The DOM ID to update - * @param {string} trueChangeValueTo The value to apply when matched - */ -function changeInputClass(docCheck, checkFor, docChange, trueChangeValueTo) { - if (trueChangeValueTo === "" || trueChangeValueTo === undefined) { - trueChangeValueTo = "undefined Class"; - } - trueChangeValueTo = trueChangeValueTo.replace(/\s/g, ""); - if (document.getElementById(docCheck).value === checkFor) { - document.getElementById(docChange).value = trueChangeValueTo; - } -} - let all_answers = []; let all_outputs = []; let all_marks = []; -let studentAnswers = `student_list.${studentParseNum}.${loadedMode}-${current_gene}-Answers`; -let studentOutputs = `student_list.${studentParseNum}.${loadedMode}-${current_gene}-Outputs`; -let studentMarks = `student_list.${studentParseNum}.${loadedMode}-${current_gene}-Marks`; /** * Collects the student's answers, calculates marks, and triggers feedback display. */ @@ -1351,9 +831,6 @@ function submitAnswers() { checkAnswers(); setTimeout(() => { markAnswers(); - studentAnswers = `student_list.${studentParseNum}.${loadedMode}-${current_gene}-Answers`; - studentOutputs = `student_list.${studentParseNum}.${loadedMode}-${current_gene}-Outputs`; - studentMarks = `student_list.${studentParseNum}.${loadedMode}-${current_gene}-Marks`; all_answers.push( document?.getElementById("sequence_input")?.value?.trim() || "", document.getElementById("pam_input").value.trim(), @@ -1412,3 +889,50 @@ function backToAssignments() { $(() => { $("form").submit(() => false); }); + +// Export for testing +if (typeof module !== "undefined" && module.exports) { + module.exports = { + getOffTargetOptimalValue, + isNumberOrDashKey, + createComplementarySeq, + checkOffTarget, + checkF1Primers, + checkR1Primers, + fillGeneList, + // Export getters for testing + get MAROffTarget() { + return MAROffTarget; + }, + get MAROffTarget_degree() { + return MAROffTarget_degree; + }, + get MARF1primers() { + return MARF1primers; + }, + get MARR1primers() { + return MARR1primers; + }, + // Export setters for test setup + __setTestState(state = {}) { + if (state.correctNucleotideIncluded !== undefined) + correctNucleotideIncluded = state.correctNucleotideIncluded; + if (state.MARgRNAseq !== undefined) MARgRNAseq = state.MARgRNAseq; + if (state.benchling_gRNA_outputs !== undefined) benchling_gRNA_outputs = state.benchling_gRNA_outputs; + if (state.current_gene !== undefined) current_gene = state.current_gene; + if (state.gene_backgroundInfo !== undefined) gene_backgroundInfo = state.gene_backgroundInfo; + }, + // Reset state for clean tests + __resetState() { + MAROffTarget = false; + MAROffTarget_degree = 0; + MARF1primers = false; + MARR1primers = false; + MAROffTarget_aboveOpt = false; + MAROffTarget_above35 = false; + MAROffTarget_onlyOption = false; + correctNucleotideIncluded = false; + MARgRNAseq = false; + }, + }; +} diff --git a/core/scripts/crispr_scripts.min.js b/core/scripts/crispr_scripts.min.js index eab48c5..791d708 100644 --- a/core/scripts/crispr_scripts.min.js +++ b/core/scripts/crispr_scripts.min.js @@ -1 +1,27 @@ -let selection_inMode="practice";const listOfGenes=["eBFP","ACTN3","HBB","CCR5","ANKK1","APOE"];let gene_backgroundInfo,benchling_gRNA_outputs,possible_gene="eBFP",current_gene="empty";function select_Gene(){""!==possible_gene||possible_gene?(current_gene=possible_gene,loadWork(),checkAnswers_executed=!1):("empty"===current_gene&&"eBFP"===current_gene&&"ACTN3"===current_gene&&"HBB"===current_gene&&"CCR5"===current_gene&&"ANKK1"===current_gene&&"APOE"===current_gene||(current_gene="empty"),alert("Error code sG34-42 occurred. Please contact admin or TA!"))}async function loadCRISPRJSON_Files(){try{const e=await fetch("./data/Benchling_gRNA_Outputs.json");benchling_gRNA_outputs=await e.json();const t=await fetch("data/Background_info/gene_background_info.json");gene_backgroundInfo=await t.json()}catch(e){console.error("Error fetching file:",e)}}function fillGeneList(){if(gene_backgroundInfo?.gene_list){let e;$("#gene_dropdown_selection").empty();const t=Object.keys(gene_backgroundInfo.gene_list);for(const n of t)e+=`\n\t\t\t\t\n\t\t\t`;$("#gene_dropdown_selection").append(e)}}let loadedMode="practice";function loadWork(){if(gene_backgroundInfo||""!==gene_backgroundInfo||backgroundInfo?.[0].gene_list[current_gene]){let e;$("#work").empty(),loadedMode=selection_inMode,checkAnswers_executed=!1,e='
',e+='
\n

Please refer to your dry lab protocol for full instructions on how and what to do. Below is a brief reminder of what you are supposed to do with each gene: \n Your objective is to find these mutations, design a gRNA and its corresponding F1/R1 primers

\n
\n',e+=`

Here is some background information about your gene: ${gene_backgroundInfo?.gene_list[current_gene].name} (${current_gene})

\n`,e+=`

Background information: ${gene_backgroundInfo?.gene_list[current_gene].Background}

\n`,e+=`

Target site: ${gene_backgroundInfo?.gene_list[current_gene]["Target site"]}

\n`,e+=`

Modified genetic sequence: ${gene_backgroundInfo?.gene_list[current_gene].Sequence}

\n`,e+="
",e+="
",e+='
',e+="

Please input the following information for your gRNA for your selected gene.

\n",e+="
",e+='
',e+='',e+='',e+='This would be your gRNA sequence 5\' to 3\'. NOTE: This is maxed out at 20 characters long',e+="
",e+='
',e+='',e+='',e+='This would be your PAM sequence 5\' to 3\'. NOTE: This is maxed out at 3 characters long',e+="
",e+='
',e+='',e+='',e+='This would be your cut position for your gRNA. NOTE: This input only takes numbers',e+="
",e+='\n\t\t\t
\n\t\t\t\t\n\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t',e+='
',e+='',e+='',e+='This would be your off-target score for your gRNA. NOTE: This input only takes numbers',e+="
",e+='
',e+='',e+='',e+='This would be your forward primer (F1) for your gRNA',e+="
",e+='
',e+='',e+='',e+='This would be your reverse primer (R1) for your gRNA',e+="
",e+='',e+='',e+="
",e+="
",$("#work").append(e)}else""!==gene_backgroundInfo&&gene_backgroundInfo&&backgroundInfo?.[0].gene_list[current_gene]||alert("Error code lFS50-66 occurred. Please contact admin or TA!")}function isNumberOrDashKey(e){const t=e.which?e.which:e.keyCode;return!(46!==t&&45!==t&&t>31&&(t<48||t>57))}let MARgRNAseq=!1,MARgRNAseq_degree=0,MARPAMseq=!1,MARCutPos=!1,MARstrand=!1,MAROffTarget=!1,MAROffTarget_degree=0,MAROffTarget_aboveOpt=!1,MAROffTarget_above35=!1,MAROffTarget_onlyOption=!1,MARF1primers=!1,MARR1primers=!1,possible_comparable_answers=[],correctNucleotideIncluded=!1,true_counts=0,checkAnswers_executed=!1;function checkAnswers(){MARgRNAseq=!1,MARgRNAseq_degree=0,MARPAMseq=!1,MARCutPos=!1,MARstrand=!1,correctNucleotideIncluded=!1,true_counts=0;const e=gene_backgroundInfo.gene_list[current_gene]["Target position"]-1,t=document?.getElementById("sequence_input")?.value?.trim()||void 0;if(possible_comparable_answers=[],t)for(const n of benchling_gRNA_outputs.gene_list[current_gene])n.Sequence===t&&possible_comparable_answers.push(n);if(possible_comparable_answers.length>0)for(const n of possible_comparable_answers){if(true_counts=0,correctNucleotideIncluded=!1,1===n.Strand){const t=n.Position-1-1+3;e>=n.Position-1-17&&e<=t&&(correctNucleotideIncluded=!0)}else if(-1===n.Strand){const t=n.Position-1+17;e>=n.Position-1-3&&e<=t&&(correctNucleotideIncluded=!0)}if(correctNucleotideIncluded){let t,a;if(1===n.Strand?(t=n.Position-1+2,a=n.Position-1+4,"Sense (+)"===document.getElementById("strand_input").value&&(MARstrand=!0,true_counts+=1)):-1===n.Strand&&(t=n.Position-1-2,a=n.Position-1-4,"Antisense (-)"===document.getElementById("strand_input").value&&(MARstrand=!0,true_counts+=1)),e>=t&&e<=a?(MARgRNAseq=!1,MARgRNAseq_degree=0):e>=possible_comparable_answers[i].Position-1+1&&e<=possible_comparable_answers[i].Position-1+10||e<=possible_comparable_answers[i].Position-1-1&&e>=possible_comparable_answers[i].Position-1-10?(MARgRNAseq=!0,MARgRNAseq_degree=1,true_counts+=1):e>=possible_comparable_answers[i].Position-1&&e<=possible_comparable_answers[i].Position-1+20||e<=possible_comparable_answers[i].Position-1&&e>=possible_comparable_answers[i].Position-1-20?(MARgRNAseq=!0,MARgRNAseq_degree=2,true_counts+=1):(e>=possible_comparable_answers[i].Position-1&&e<=possible_comparable_answers[i].Position-1+30||e<=possible_comparable_answers[i].Position-1&&e>=possible_comparable_answers[i].Position-1-30)&&(MARgRNAseq=!0,MARgRNAseq_degree=3,true_counts+=1),MARgRNAseq){const e=element;e.Position&&parseInt(e.Position,10)===parseInt(document.getElementById("position_input").value,10)?(MARCutPos=!0,true_counts+=1):null!==e.Position&&void 0!==e.Position||alert("Error code cA302-307: retrieving server information on 'Position' answers occurred. Please contact admin or TA!"),(e.PAM||e.PAM)&&e.PAM===document.getElementById("pam_input").value.trim()?(MARPAMseq=!0,true_counts+=1):null!==e.PAM&&void 0!==e.PAM||alert("Error code cA311-317: retrieving server information on 'PAM' answers occurred. Please contact admin or TA!"),e["Specificity Score"]||e["Specificity Score"]?checkOffTarget(e["Specificity Score"]):null!==e["Specificity Score"]&&void 0!==e["Specificity Score"]||alert("Error code cA342-348: retrieving server information on 'Specificity Score' answers occurred. Please contact admin or TA!"),checkF1Primers(document?.getElementById("sequence_input")?.value?.trim()||""),checkR1Primers(document?.getElementById("sequence_input")?.value?.trim()||"")}}}checkAnswers_executed=!0}let offtarget_List=[],offtarget_dict={},offtarget_dictParse=[],offtarget_Use=[];function checkOffTarget(e){MAROffTarget=!1,MAROffTarget_degree=0,MAROffTarget_aboveOpt=!1,MAROffTarget_above35=!1,MAROffTarget_onlyOption=!1;const t=Math.floor(e),n=Math.ceil(e),a=parseInt(document.getElementById("offtarget_input").value,10);if(correctNucleotideIncluded&&MARgRNAseq&&a>=t&&a<=n&&(MAROffTarget=!0,true_counts+=1),MAROffTarget){const e=parseInt(document.getElementById("position_input").value,10)+35,t=parseInt(document.getElementById("position_input").value,10)-35;offtarget_List=[],offtarget_dict={},offtarget_dictParse=[],offtarget_Use=[];for(let a=0;a=t&&benchling_gRNA_outputs.gene_list[current_gene][a].Position<=e&&benchling_gRNA_outputs.gene_list[current_gene][a]["Specificity Score"]&&(offtarget_List.push(benchling_gRNA_outputs.gene_list[current_gene][a]["Specificity Score"]),offtarget_dict[a]=benchling_gRNA_outputs.gene_list[current_gene][a]["Specificity Score"],offtarget_dictParse.push(a));let n=!0;Math.max.apply(null,offtarget_List)<35&&(n=!1);const s=Math.max.apply(null,offtarget_List),o=s-.2*s;let r=o;const{studentClass:l}=student_reg_information[0].student_list[studentParseNum];"Optimal"===student_reg_information[0].classMarkingMod[l][0]?(o>80||o<35)&&(r=80):r=student_reg_information[0].classMarkingMod[l][0],a>=r?(MAROffTarget_aboveOpt=!0,MAROffTarget_above35=!0,MAROffTarget_degree=1):a>=35?(MAROffTarget_above35=!0,MAROffTarget_degree=Math.max.apply(null,offtarget_List)<80?1:2):n||(MAROffTarget_onlyOption=!0,MAROffTarget_degree=3)}}let possible_F1_primers=[];function checkF1Primers(e){MARF1primers=!1,possible_F1_primers=[];const t="TAATACGACTCACTATAG";let n=!0;"G"===e[0]&&(n=!1);for(let a=16;a<=20;a+=1)possible_F1_primers.push(t+e.slice(0,a));if(!n)for(let a=16;a<=20;a+=1)possible_F1_primers.push(t+e.slice(1,a));possible_F1_primers.includes(document.getElementById("f1_input").value.trim())&&(MARF1primers=!0)}let possible_R1_primers=[];const complementary_nt_dict={A:"T",T:"A",C:"G",G:"C"};function checkR1Primers(e){MARR1primers=!1,possible_R1_primers=[];let t="";for(const n of e)t=complementary_nt_dict[n]+t;for(let n=19;n<=20;n+=1)possible_R1_primers.push("TTCTAGCTCTAAAAC"+t.slice(0,n));possible_R1_primers.includes(document.getElementById("r1_input").value.trim())&&(MARR1primers=!0)}function createComplementarySeq(e){let t="";for(const n of e)t=complementary_nt_dict[n]+t}let studentMark=0,studentMarkPercentage=0;const markTotal=10;function markAnswers(){studentMark=0,checkAnswers_executed||checkAnswers(),checkAnswers_executed&&(MARgRNAseq&&(1===MARgRNAseq_degree?studentMark+=2:2===MARgRNAseq_degree?studentMark+=1:3===MARgRNAseq_degree&&(studentMark+=.5)),MARPAMseq&&(studentMark+=2),MAROffTarget&&(1===MAROffTarget_degree?studentMark+=2:2===MAROffTarget_degree?studentMark+=1:3===MAROffTarget_degree&&(studentMark+=.5)),MARF1primers&&(studentMark+=2),MARR1primers&&(studentMark+=2),studentMarkPercentage=(studentMark/markTotal*100).toFixed(2),studentMarkPercentage>100?studentMarkPercentage=100:studentMarkPercentage<0&&(studentMarkPercentage=0))}function showFeedback(){$("#mainContainer").empty();let e="

You would only receive feedback on your practice attempts and not your final assignments.

";e+="

The assignment itself is marked out of 10 marks with 2 marks for each input excluding gRNA strand direction, cut position and target region range (these three values are used to calculate if you have the right answer or not which means they are still crucial that they are still correct).

",e+="

The following is the breakdown of what marks you would have received and why you would have gotten them:

",e+=`

Mark: ${all_marks[0]}/10 (${all_marks[1]})

`;let t=0,n="Your gRNA sequence was wrong and not found in the Benchling gRNA outputs. Either you made a typo or this answer was not correct and did not contain the target cut site within an acceptable range.";MARgRNAseq&&(1===MARgRNAseq_degree?(t=2,n="This means your answer was correct and you received full marks."):2===MARgRNAseq_degree?(t=1,n="This means your sequence was partially correct as it contains the target sequence within a 20bp range but was not optimal. One mark."):3===MARgRNAseq_degree&&(t=.5,n="This means your sequence was not wrong (therefore was still correct) but there were better options out there. I recommend you try this practice assignment again. Still worth some marks though (half a mark)."));let a=0,s="Your PAM sequence was wrong and not found relative to your gRNA sequence. Either you made a typo or this answer was not correct. Either it contained the cut site within the PAM site or it was not an NGG or NAG PAM site (SciGrade only accepts either of those two PAM sites).";MARPAMseq&&(a=2,s="This means your answer was correct and you received full marks.");let o=0,r="Your off-target score was wrong. Either it was not above/within the optimal range (or above 35) or the last-resort option.";MAROffTarget&&(1===MAROffTarget_degree?(o=2,r="This means your answer was correct while above/within the optimal and you received full marks."):2===MAROffTarget_degree?(o=1,r="This means your answer was technically correct as its on-target value was above 35."):3===MAROffTarget_degree&&(o=.5,r="This means your answer was partially correct as it was found to be your only option is solely based on the target region range you selected."));let l=0,i="";for(let p=0;p For gRNA Strand Sequence, you put down "${all_answers[0]}" which gave you the mark ${t}.

`,e+=n,e+="",e+="",e+="",$("#mainContainer").append(e),e="
",e+="
",e+="
",e+="",e+="
",e+="
",e+="
",e+="
",e+=`

For gRNA PAM Sequence, you put down "${all_answers[1]}" which gave you the mark ${a}.

`,e+=s,e+="
",e+="
",e+="
",$("#mainContainer").append(e),e="
",e+="
",e+="
",e+="",e+="
",e+="
",e+="
",e+="
",e+=`

For Off-Target Score, you put down "${all_answers[4]}" which gave you the mark ${o}.

`,e+=r,e+="
",e+="
",e+="
",$("#mainContainer").append(e),e="
",e+="
",e+="
",e+="",e+="
",e+="
",e+="
",e+="
",e+=`

For F1 Primer, you put down "${all_answers[5]}" which gave you the mark ${l}.

`,e+=d,e+="
",e+="
",e+="
",$("#mainContainer").append(e),e="
",e+="
",e+="
",e+="",e+="
",e+="
",e+="
",e+="
",e+=`

For R1 Primer, you put down "${all_answers[6]}" which gave you the mark ${c}.

`,e+=m,e+="
",e+="
",e+="
",$("#mainContainer").append(e),e="
",e+="

If at any point you wish to dispute marks, please contact your TA or professor once you completed your assignment. If you have found a bug in our SciGrade marking system, please contact your professor or our admin.

",e+="
",e+='

',$("#mainContainer").append(e)}function showNewInput(e,t,n){document.getElementById(String(e)).value===String(t)?document.getElementById(String(n)).removeAttribute("hidden"):document.getElementById(String(n)).setAttribute("hidden",!0)}let completed_assignments=[];function generateCompletedAssignmentList(){completed_assignments=[],student_reg_information[0].student_list[studentParseNum]["assignment-HBB-Marks"]&&(completed_assignments.includes("HBB")||completed_assignments.push("HBB")),student_reg_information[0].student_list[studentParseNum]["assignment-CCR5-Marks"]&&(completed_assignments.includes("CCR5")||completed_assignments.push("CCR5")),student_reg_information[0].student_list[studentParseNum]["assignment-ANKK1-Marks"]&&(completed_assignments.includes("ANKK1")||completed_assignments.push("ANKK1")),student_reg_information[0].student_list[studentParseNum]["assignment-APOE-Marks"]&&(completed_assignments.includes("APOE")||completed_assignments.push("APOE"))}function openAccountManagement(){generateCompletedAssignmentList(),$("#accountManagementBody").empty();let e=`\n\t\t
\n\t\t\t

\n\t\t\t\tHello ${student_reg_information[0].student_list[studentParseNum].name.split(" ")[0]}!\n\t\t\t

\n\t\t
\n\t\t`;$("#accountManagementBody").append(e),e=`\n\t\t
\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t${completed_assignments.length>0?`\n\t\t\t\t\t\t\t

You have completed the following assignments:

\n\t\t\t\t\t\t\t

    \n\t\t\t\t\t\t\t\t${completed_assignments.map((e=>`
  • ${e}
  • `)).join("")}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t`:"\n\t\t\t\t\t\t\t

You have not completed any assignments yet.

\n\t\t\t\t\t\t\t"}\n\t\t\t\t
\n\t\t\t
\n\t\t
\n\t`,$("#accountManagementBody").append(e);const t=student_reg_information[0].class_list;if(["admin","TA"].includes(student_reg_information[0].student_list[studentParseNum].type)&&(e=`\n\t\t\t
\n\n\t\t\t

\n\t\t\t\t Oh wait! Hello ${student_reg_information[0].student_list[studentParseNum].type}! Would you like to download student marks?\n\t\t\t

\n\n\t\t\t\n\n\t\t\t\n\n\t\t\t${Object.keys(t).filter((e=>e)).map((e=>`\n`)).join("")}\n\t\t\t\n\n\t\t\t

\n\t\t\t\t\n\t\t\t\t\tDownload Marks\n\t\t\t\t\n\n\t\t\t\t\n\t\t\t\t\tDownload Raw Marks\n\t\t\t\t\n\t\t\t

\n\t\t`,$("#accountManagementBody").append(e)),"TA"===student_reg_information[0].student_list[studentParseNum].type||"admin"===student_reg_information[0].student_list[studentParseNum].type){e="

ADMIN POWER!

",e+="

",e+="
",e+="
",e+="",e+="
",e+="
",e+="
",e+="
",e+="

If you would like to create a new class, just fill the form below: ",e+="

",e+='
',e+='',e+='",e+='',e+='Choose class students will be added to or create a new class. Example of a new class: HMB396 - Winter - 2019 (NOTE: Spaces will be deleted once you submit so use capital letters to separate words)',e+="
",e+='
',e+='',e+='',e+='Input student numbers, separated by commas, new lines and/or tab indentation (BEWARE OF TYPOS!)',e+="
",e+='
',e+='',e+='',e+='Input student University associated email in the same order of the student numbers, separated by commas, new lines and/or tab indentation (BEWARE OF TYPOS!)',e+="
",e+="
",e+="
",e+="
",e+="
",$("#accountManagementBody").append(e),e="
",e+="
",e+="
",e+="",e+="
",e+="
",e+="
",e+="
",e+="

If you would like to add a single user (students, TAs or admins), please fill in the form below. Please note, this will default the user as a student. Only admins will be able to create new TAs or admins

",e+="
",e+='
',e+='',e+='",e+='Choose class TA will be added.',e+="
",e+='
',e+='',e+='',e+='The user\'s access number (like a student or employee number)',e+="
",e+='
',e+='',e+='',e+='The user\'s University associated email',e+="
",e+="
",e+="
",e+="
",e+="
",$("#accountManagementBody").append(e)}if("admin"===student_reg_information[0].student_list[studentParseNum].type){e="
",e+="
",e+="
",e+="",e+="
",e+="
",e+="
",e+="
",e+="

If you would like to change a class's off-target optimal goal, you can do that here

",e+='
',e+='',e+='",e+='Choose the class for which you are modifying marking scheme for.',e+="
",e+='
',e+='

Modify controls for:

',e+='',e+='

Current off-target marking is set to:

',e+='",e+='',e+='Choose how you want the off-target score to be marked. Optimal is Min_optimal = Max_range - (Max_range * 0.2) if below 80 (if below, optimal = 80). Custom value can be any number between 0.01 and 100 which will be the new custom "optimal" value for your class.',e+="
",e+="
",e+="
",e+="
",$("#accountManagementBody").append(e),e="
",e+="
",e+="
",e+="",e+="
",e+="
",e+="
",e+="
",e+="

If you would like to change a user's account type (to TA or admin or back to student), you can do that below

",e+='
',e+='',e+='",e+='Choose the class for which the user belongs to.',e+="
",e+='
',e+='',e+='",e+='Choose the user for which you change their account type for.',e+="
",e+='
',e+='',e+='",e+='Choose the type for which you want the user to become.',e+="
",e+="
",e+="
",e+="
",e+="
",$("#accountManagementBody").append(e)}e="",$("#accountManagementBody").append(e),$("#accountModal").modal("show")}function UpdateChooseUser(e){ClearSelectOptions(e);for(const t in updatedListOfStudents)updatedListOfStudents[t]&&AddToOptions(e,t,updatedListOfStudents[t])}function AddToOptions(e,t,n){const a=document.getElementById(e),s=document.createElement("option");s.value=t,s.innerHTML=n,a.appendChild(s)}function ClearSelectOptions(e){const t=document.getElementById(e);for(;t.options.length>0;)t.options.remove(0)}let updatedListOfStudents={};function UpdateStudentList(e){updatedListOfStudents={};const t=student_reg_information[0].student_list;for(const n of t)n.studentClass===e&&(updatedListOfStudents[n.name]=`${n.name} - ${n.type}`)}function ChangeDOMInnerhtml(e,t){document.getElementById(e).innerHTML=t}const downloadIndexTable_start="\t\t\n\t\t\tStudent Number\n\t\t\tName",downloadIndexTable_end="\n\t\t\n";let downloadIndexTable_fill="";function generateRestOfIndexTable(e,t){return e=downloadIndexTable_start,e+=downloadIndexTable_end}function generateHiddenStudentDownload(e,t){if("TA"===student_reg_information[0].student_list[studentParseNum].type||"admin"===student_reg_information[0].student_list[studentParseNum].type){downloadIndexTable_fill=generateRestOfIndexTable(downloadIndexTable_fill,t),$("#hiddenDownloadModal_table").empty();const n=new Date;let a="\n\t\n",s="SciGrade_studentMark_";t||(s="SciGrade_studentMarkRaw_"),a+=`\t\t\n`,a+=downloadIndexTable_fill;const o=student_reg_information[0].student_list;for(const t of o)"Student"===t.type&&t.studentClass===e&&(a+="\t\t\n",a+=`\t\t\t\n`,a+=`\t\t\t\n`,a+="\t\t\n");a+="\t\n
${s}${student_reg_information[0].student_list[studentParseNum].name.replace(/\s/g,"")}_${n.getFullYear()}-${n.getMonth()}_${n.getDate()}
${t.student_number}${t.name}
",document.getElementById("hiddenDownloadModal_table").innerHTML+=a,$("#hiddenDownloadModal_table").tableToCSV()}else showRegError(7)}function changeInputClass(e,t,n,a){""!==a&&void 0!==a||(a="undefined Class"),a=a.replace(/\s/g,""),document.getElementById(e).value===t&&(document.getElementById(n).value=a)}let all_answers=[],all_outputs=[],all_marks=[],studentAnswers=`student_list.${studentParseNum}.${loadedMode}-${current_gene}-Answers`,studentOutputs=`student_list.${studentParseNum}.${loadedMode}-${current_gene}-Outputs`,studentMarks=`student_list.${studentParseNum}.${loadedMode}-${current_gene}-Marks`;function submitAnswers(){all_answers=[],all_outputs=[],all_marks=[],checkAnswers(),setTimeout((()=>{markAnswers(),studentAnswers=`student_list.${studentParseNum}.${loadedMode}-${current_gene}-Answers`,studentOutputs=`student_list.${studentParseNum}.${loadedMode}-${current_gene}-Outputs`,studentMarks=`student_list.${studentParseNum}.${loadedMode}-${current_gene}-Marks`,all_answers.push(document?.getElementById("sequence_input")?.value?.trim()||"",document.getElementById("pam_input").value.trim(),document.getElementById("position_input").value,document.getElementById("strand_input").value,document.getElementById("offtarget_input").value,document.getElementById("f1_input").value.trim(),document.getElementById("r1_input").value.trim()),all_outputs.push(MARstrand,MARgRNAseq,MARgRNAseq_degree,MARCutPos,MARPAMseq,MAROffTarget,MAROffTarget_degree,MAROffTarget_aboveOpt,MAROffTarget_above35,MAROffTarget_onlyOption,MARF1primers,MARR1primers),all_marks.push(studentMark,studentMarkPercentage),document.getElementById("options_label").innerHTML="Would you like to see feedback on your answers or start a new assignment?",document.getElementById("seeFeedback").removeAttribute("hidden"),showFeedback(),$("#feedbackButton").click()}),750)}function IfPressEnter(e,t){13!==e.which&&13!==e.keyCode||$(`#${t}`).click()}function backToAssignments(){redirectCRISPR(),$("#practice").click()}$((()=>{$("form").submit((()=>!1))})); \ No newline at end of file +let selection_inMode="practice";const listOfGenes=["eBFP","ACTN3","HBB","CCR5","ANKK1","APOE"];let possible_gene="eBFP",current_gene="empty";function select_Gene(){possible_gene!==""||possible_gene?(current_gene=possible_gene,loadWork(),checkAnswers_executed=!1):((current_gene!=="empty"||current_gene!=="eBFP"||current_gene!=="ACTN3"||current_gene!=="HBB"||current_gene!=="CCR5"||current_gene!=="ANKK1"||current_gene!=="APOE")&&(current_gene="empty"),alert("Error code sG34-42 occurred. Please contact admin or TA!"))}let gene_backgroundInfo,benchling_gRNA_outputs;async function loadCRISPRJSON_Files(){try{benchling_gRNA_outputs=await(await fetch("./data/Benchling_gRNA_Outputs.json")).json(),gene_backgroundInfo=await(await fetch("data/Background_info/gene_background_info.json")).json()}catch(e){console.error("Error fetching file:",e)}}function fillGeneList(){if(gene_backgroundInfo?.gene_list){$("#gene_dropdown_selection").empty();let e;const n=Object.keys(gene_backgroundInfo.gene_list);for(const t of n)e+=` + + `;$("#gene_dropdown_selection").append(e)}}let loadedMode="practice";function loadWork(){if(gene_backgroundInfo||gene_backgroundInfo!==""||backgroundInfo?.[0].gene_list[current_gene]){$("#work").empty(),loadedMode=selection_inMode,checkAnswers_executed=!1;let e;e='
',e+=`
+

Please refer to your dry lab protocol for full instructions on how and what to do. Below is a brief reminder of what you are supposed to do with each gene: + Your objective is to find these mutations, design a gRNA and its corresponding F1/R1 primers

+
+`,e+=`

Here is some background information about your gene: ${gene_backgroundInfo?.gene_list[current_gene].name} (${current_gene})

+`,e+=`

Background information: ${gene_backgroundInfo?.gene_list[current_gene].Background}

+`,e+=`

Target site: ${gene_backgroundInfo?.gene_list[current_gene]["Target site"]}

+`,e+=`

Modified genetic sequence: ${gene_backgroundInfo?.gene_list[current_gene].Sequence}

+`,e+="
",e+="
",e+='
',e+=`

Please input the following information for your gRNA for your selected gene.

+`,e+="
",e+='
',e+='',e+='',e+=`This would be your gRNA sequence 5' to 3'. NOTE: This is maxed out at 20 characters long`,e+="
",e+='
',e+='',e+='',e+=`This would be your PAM sequence 5' to 3'. NOTE: This is maxed out at 3 characters long`,e+="
",e+='
',e+='',e+='',e+='This would be your cut position for your gRNA. NOTE: This input only takes numbers',e+="
",e+=` +
+ + + +
+ `,e+='
',e+='',e+='',e+='This would be your off-target score for your gRNA. NOTE: This input only takes numbers',e+="
",e+='
',e+='',e+='',e+='This would be your forward primer (F1) for your gRNA',e+="
",e+='
',e+='',e+='',e+='This would be your reverse primer (R1) for your gRNA',e+="
",e+='',e+='',e+="
",e+="
",$("#work").append(e)}else(gene_backgroundInfo===""||!gene_backgroundInfo||!backgroundInfo?.[0].gene_list[current_gene])&&alert("Error code lFS50-66 occurred. Please contact admin or TA!")}function isNumberOrDashKey(e){const n=e.which?e.which:e.keyCode;return!(n!==46&&n!==45&&n>31&&(n<48||n>57))}let MARgRNAseq=!1,MARgRNAseq_degree=0,MARPAMseq=!1,MARCutPos=!1,MARstrand=!1,MAROffTarget=!1,MAROffTarget_degree=0,MAROffTarget_aboveOpt=!1,MAROffTarget_above35=!1,MAROffTarget_onlyOption=!1,MARF1primers=!1,MARR1primers=!1,possible_comparable_answers=[],correctNucleotideIncluded=!1,true_counts=0,checkAnswers_executed=!1;function checkAnswers(){MARgRNAseq=!1,MARgRNAseq_degree=0,MARPAMseq=!1,MARCutPos=!1,MARstrand=!1,correctNucleotideIncluded=!1,true_counts=0;const e=gene_backgroundInfo.gene_list[current_gene]["Target position"]-1,n=document?.getElementById("sequence_input")?.value?.trim()||void 0;if(possible_comparable_answers=[],n)for(const t of benchling_gRNA_outputs.gene_list[current_gene])t.Sequence===n&&possible_comparable_answers.push(t);if(possible_comparable_answers.length>0)for(const t of possible_comparable_answers){if(true_counts=0,correctNucleotideIncluded=!1,t.Strand===1){const o=t.Position-1-1+3,a=t.Position-1-17;e>=a&&e<=o&&(correctNucleotideIncluded=!0)}else if(t.Strand===-1){const o=t.Position-1+17,a=t.Position-1-3;e>=a&&e<=o&&(correctNucleotideIncluded=!0)}if(correctNucleotideIncluded){let o,a;if(t.Strand===1?(o=t.Position-1+2,a=t.Position-1+4,document.getElementById("strand_input").value==="Sense (+)"&&(MARstrand=!0,true_counts+=1)):t.Strand===-1&&(o=t.Position-1-2,a=t.Position-1-4,document.getElementById("strand_input").value==="Antisense (-)"&&(MARstrand=!0,true_counts+=1)),e>=o&&e<=a?(MARgRNAseq=!1,MARgRNAseq_degree=0):e>=possible_comparable_answers[i].Position-1+1&&e<=possible_comparable_answers[i].Position-1+10||e<=possible_comparable_answers[i].Position-1-1&&e>=possible_comparable_answers[i].Position-1-10?(MARgRNAseq=!0,MARgRNAseq_degree=1,true_counts+=1):e>=possible_comparable_answers[i].Position-1&&e<=possible_comparable_answers[i].Position-1+20||e<=possible_comparable_answers[i].Position-1&&e>=possible_comparable_answers[i].Position-1-20?(MARgRNAseq=!0,MARgRNAseq_degree=2,true_counts+=1):(e>=possible_comparable_answers[i].Position-1&&e<=possible_comparable_answers[i].Position-1+30||e<=possible_comparable_answers[i].Position-1&&e>=possible_comparable_answers[i].Position-1-30)&&(MARgRNAseq=!0,MARgRNAseq_degree=3,true_counts+=1),MARgRNAseq){const r=element;r.Position&&parseInt(r.Position,10)===parseInt(document.getElementById("position_input").value,10)?(MARCutPos=!0,true_counts+=1):(r.Position===null||r.Position===void 0)&&alert("Error code cA302-307: retrieving server information on 'Position' answers occurred. Please contact admin or TA!"),(r.PAM||r.PAM)&&r.PAM===document.getElementById("pam_input").value.trim()?(MARPAMseq=!0,true_counts+=1):(r.PAM===null||r.PAM===void 0)&&alert("Error code cA311-317: retrieving server information on 'PAM' answers occurred. Please contact admin or TA!"),r["Specificity Score"]||r["Specificity Score"]?checkOffTarget(r["Specificity Score"]):(r["Specificity Score"]===null||r["Specificity Score"]===void 0)&&alert("Error code cA342-348: retrieving server information on 'Specificity Score' answers occurred. Please contact admin or TA!"),checkF1Primers(document?.getElementById("sequence_input")?.value?.trim()||""),checkR1Primers(document?.getElementById("sequence_input")?.value?.trim()||"")}}}checkAnswers_executed=!0}let offtarget_List=[],offtarget_dict={},offtarget_dictParse=[],offtarget_Use=[];function checkOffTarget(e){MAROffTarget=!1,MAROffTarget_degree=0,MAROffTarget_aboveOpt=!1,MAROffTarget_above35=!1,MAROffTarget_onlyOption=!1;const n=Math.floor(e),t=Math.ceil(e),o=parseInt(document.getElementById("offtarget_input").value,10);if(correctNucleotideIncluded&&MARgRNAseq&&o>=n&&o<=t&&(MAROffTarget=!0,true_counts+=1),MAROffTarget){const a=parseInt(document.getElementById("position_input").value,10)+35,r=parseInt(document.getElementById("position_input").value,10)-35;offtarget_List=[],offtarget_dict={},offtarget_dictParse=[],offtarget_Use=[];for(let s=0;s=r&&benchling_gRNA_outputs.gene_list[current_gene][s].Position<=a&&benchling_gRNA_outputs.gene_list[current_gene][s]["Specificity Score"]&&(offtarget_List.push(benchling_gRNA_outputs.gene_list[current_gene][s]["Specificity Score"]),offtarget_dict[s]=benchling_gRNA_outputs.gene_list[current_gene][s]["Specificity Score"],offtarget_dictParse.push(s));let c=!0;Math.max.apply(null,offtarget_List)<35&&(c=!1);const d=Math.max.apply(null,offtarget_List),u=getOffTargetOptimalValue(d);o>=u?(MAROffTarget_aboveOpt=!0,MAROffTarget_above35=!0,MAROffTarget_degree=1):o>=35?(MAROffTarget_above35=!0,Math.max.apply(null,offtarget_List)<80?MAROffTarget_degree=1:MAROffTarget_degree=2):c||(MAROffTarget_onlyOption=!0,MAROffTarget_degree=3)}}function getOffTargetOptimalValue(e){const n=e-e*.2;return n>80||n<35?80:n}let possible_F1_primers=[];function checkF1Primers(e){MARF1primers=!1,possible_F1_primers=[];const n="TAATACGACTCACTATAG";let t=!0;e[0]==="G"&&(t=!1);for(let o=16;o<=20;o+=1)possible_F1_primers.push(n+e.slice(0,o));if(!t)for(let o=16;o<=20;o+=1)possible_F1_primers.push(n+e.slice(1,o));possible_F1_primers.includes(document.getElementById("f1_input").value.trim())&&(MARF1primers=!0)}let possible_R1_primers=[];const complementary_nt_dict={A:"T",T:"A",C:"G",G:"C"};function checkR1Primers(e){MARR1primers=!1,possible_R1_primers=[];const n="TTCTAGCTCTAAAAC";let t="";for(const o of e)t=complementary_nt_dict[o]+t;for(let o=19;o<=20;o+=1)possible_R1_primers.push(n+t.slice(0,o));possible_R1_primers.includes(document.getElementById("r1_input").value.trim())&&(MARR1primers=!0)}function createComplementarySeq(e){let n="";for(const t of e)n=complementary_nt_dict[t]+n;return n}let studentMark=0,studentMarkPercentage=0;const markTotal=10;function markAnswers(){studentMark=0,checkAnswers_executed||checkAnswers(),checkAnswers_executed&&(MARgRNAseq&&(MARgRNAseq_degree===1?studentMark+=2:MARgRNAseq_degree===2?studentMark+=1:MARgRNAseq_degree===3&&(studentMark+=.5)),MARPAMseq&&(studentMark+=2),MAROffTarget&&(MAROffTarget_degree===1?studentMark+=2:MAROffTarget_degree===2?studentMark+=1:MAROffTarget_degree===3&&(studentMark+=.5)),MARF1primers&&(studentMark+=2),MARR1primers&&(studentMark+=2),studentMarkPercentage=(studentMark/markTotal*100).toFixed(2),studentMarkPercentage>100?studentMarkPercentage=100:studentMarkPercentage<0&&(studentMarkPercentage=0))}function showFeedback(){$("#mainContainer").empty();let e="

You would only receive feedback on your practice attempts and not your final assignments.

";e+="

The assignment itself is marked out of 10 marks with 2 marks for each input excluding gRNA strand direction, cut position and target region range (these three values are used to calculate if you have the right answer or not which means they are still crucial that they are still correct).

",e+="

The following is the breakdown of what marks you would have received and why you would have gotten them:

",e+=`

Mark: ${all_marks[0]}/10 (${all_marks[1]})

`;let n=0,t="Your gRNA sequence was wrong and not found in the Benchling gRNA outputs. Either you made a typo or this answer was not correct and did not contain the target cut site within an acceptable range.";MARgRNAseq&&(MARgRNAseq_degree===1?(n=2,t="This means your answer was correct and you received full marks."):MARgRNAseq_degree===2?(n=1,t="This means your sequence was partially correct as it contains the target sequence within a 20bp range but was not optimal. One mark."):MARgRNAseq_degree===3&&(n=.5,t="This means your sequence was not wrong (therefore was still correct) but there were better options out there. I recommend you try this practice assignment again. Still worth some marks though (half a mark)."));let o=0,a="Your PAM sequence was wrong and not found relative to your gRNA sequence. Either you made a typo or this answer was not correct. Either it contained the cut site within the PAM site or it was not an NGG or NAG PAM site (SciGrade only accepts either of those two PAM sites).";MARPAMseq&&(o=2,a="This means your answer was correct and you received full marks.");let r=0,c="Your off-target score was wrong. Either it was not above/within the optimal range (or above 35) or the last-resort option.";MAROffTarget&&(MAROffTarget_degree===1?(r=2,c="This means your answer was correct while above/within the optimal and you received full marks."):MAROffTarget_degree===2?(r=1,c="This means your answer was technically correct as its on-target value was above 35."):MAROffTarget_degree===3&&(r=.5,c="This means your answer was partially correct as it was found to be your only option is solely based on the target region range you selected."));let d=0,u="";for(let l=0;l For gRNA Strand Sequence, you put down "${all_answers[0]}" which gave you the mark ${n}.

`,e+=t,e+="",e+="",e+="",$("#mainContainer").append(e),e="
",e+="
",e+="
",e+="",e+="
",e+="
",e+="
",e+="
",e+=`

For gRNA PAM Sequence, you put down "${all_answers[1]}" which gave you the mark ${o}.

`,e+=a,e+="
",e+="
",e+="
",$("#mainContainer").append(e),e="
",e+="
",e+="
",e+="",e+="
",e+="
",e+="
",e+="
",e+=`

For Off-Target Score, you put down "${all_answers[4]}" which gave you the mark ${r}.

`,e+=c,e+="
",e+="
",e+="
",$("#mainContainer").append(e),e="
",e+="
",e+="
",e+="",e+="
",e+="
",e+="
",e+="
",e+=`

For F1 Primer, you put down "${all_answers[5]}" which gave you the mark ${d}.

`,e+=s,e+="
",e+="
",e+="
",$("#mainContainer").append(e),e="
",e+="
",e+="
",e+="",e+="
",e+="
",e+="
",e+="
",e+=`

For R1 Primer, you put down "${all_answers[6]}" which gave you the mark ${p}.

`,e+=m,e+="
",e+="
",e+="
",$("#mainContainer").append(e),e="
",e+="

If at any point you wish to dispute marks, please contact your TA or professor once you completed your assignment. If you have found a bug in our SciGrade marking system, please contact your professor or our admin.

",e+="
",e+='

',$("#mainContainer").append(e)}function showNewInput(e,n,t){document.getElementById(String(e)).value===String(n)?document.getElementById(String(t)).removeAttribute("hidden"):document.getElementById(String(t)).setAttribute("hidden",!0)}let completed_assignments=[],all_answers=[],all_outputs=[],all_marks=[];function submitAnswers(){all_answers=[],all_outputs=[],all_marks=[],checkAnswers(),setTimeout(()=>{markAnswers(),all_answers.push(document?.getElementById("sequence_input")?.value?.trim()||"",document.getElementById("pam_input").value.trim(),document.getElementById("position_input").value,document.getElementById("strand_input").value,document.getElementById("offtarget_input").value,document.getElementById("f1_input").value.trim(),document.getElementById("r1_input").value.trim()),all_outputs.push(MARstrand,MARgRNAseq,MARgRNAseq_degree,MARCutPos,MARPAMseq,MAROffTarget,MAROffTarget_degree,MAROffTarget_aboveOpt,MAROffTarget_above35,MAROffTarget_onlyOption,MARF1primers,MARR1primers),all_marks.push(studentMark,studentMarkPercentage),document.getElementById("options_label").innerHTML="Would you like to see feedback on your answers or start a new assignment?",document.getElementById("seeFeedback").removeAttribute("hidden"),showFeedback(),$("#feedbackButton").click()},750)}function IfPressEnter(e,n){(e.which===13||e.keyCode===13)&&$(`#${n}`).click()}function backToAssignments(){redirectCRISPR(),$("#practice").click()}$(()=>{$("form").submit(()=>!1)}),typeof module<"u"&&module.exports&&(module.exports={getOffTargetOptimalValue,isNumberOrDashKey,createComplementarySeq,checkOffTarget,checkF1Primers,checkR1Primers,fillGeneList,get MAROffTarget(){return MAROffTarget},get MAROffTarget_degree(){return MAROffTarget_degree},get MARF1primers(){return MARF1primers},get MARR1primers(){return MARR1primers},__setTestState(e={}){e.correctNucleotideIncluded!==void 0&&(correctNucleotideIncluded=e.correctNucleotideIncluded),e.MARgRNAseq!==void 0&&(MARgRNAseq=e.MARgRNAseq),e.benchling_gRNA_outputs!==void 0&&(benchling_gRNA_outputs=e.benchling_gRNA_outputs),e.current_gene!==void 0&&(current_gene=e.current_gene),e.gene_backgroundInfo!==void 0&&(gene_backgroundInfo=e.gene_backgroundInfo)},__resetState(){MAROffTarget=!1,MAROffTarget_degree=0,MARF1primers=!1,MARR1primers=!1,MAROffTarget_aboveOpt=!1,MAROffTarget_above35=!1,MAROffTarget_onlyOption=!1,correctNucleotideIncluded=!1,MARgRNAseq=!1}}); diff --git a/core/scripts/crispr_scripts.test.js b/core/scripts/crispr_scripts.test.js index c564314..ff4325c 100644 --- a/core/scripts/crispr_scripts.test.js +++ b/core/scripts/crispr_scripts.test.js @@ -1,375 +1,405 @@ +const crispr = require("./crispr_scripts"); +const { + getOffTargetOptimalValue, + isNumberOrDashKey, + createComplementarySeq, + checkOffTarget, + checkF1Primers, + checkR1Primers, + fillGeneList, + __resetState: resetState, + __setTestState: setTestState, +} = crispr; + describe("crispr_scripts.js - Utility Functions", () => { - // Test the isNumberOrDashKey function without DOM dependencies - describe("isNumberOrDashKey()", () => { - // Define the function for testing - const isNumberOrDashKey = (evt) => { - const charCode = evt.which ? evt.which : evt.keyCode; - return !(charCode !== 46 && charCode !== 45 && charCode > 31 && (charCode < 48 || charCode > 57)); + // Setup mocks for DOM-dependent functions + let mockDocument; + let mockInput; + + beforeEach(() => { + jest.clearAllMocks(); + + // Mock DOM elements + mockInput = { + value: "", + trim: jest.fn(function () { + return this.value.trim(); + }), }; - it("should return true for number keys", () => { - const event1 = { which: 48 }; // '0' - const event2 = { which: 57 }; // '9' - const event3 = { keyCode: 53 }; // '5' - - expect(isNumberOrDashKey(event1)).toBe(true); - expect(isNumberOrDashKey(event2)).toBe(true); - expect(isNumberOrDashKey(event3)).toBe(true); - }); - - it("should return true for dash key", () => { - const event = { which: 45 }; // '-' - expect(isNumberOrDashKey(event)).toBe(true); - }); - - it("should return true for period key", () => { - const event = { which: 46 }; // '.' - expect(isNumberOrDashKey(event)).toBe(true); - }); - - it("should return false for letter keys", () => { - const event1 = { which: 65 }; // 'A' - const event2 = { which: 90 }; // 'Z' - const event3 = { which: 97 }; // 'a' + mockDocument = { + getElementById: jest.fn((id) => { + if (id === "offtarget_input") { + return { value: "75", trim: jest.fn(() => "75") }; + } else if (id === "position_input") { + return { value: "100", trim: jest.fn(() => "100") }; + } else if (id === "f1_input") { + return mockInput; + } else if (id === "r1_input") { + return mockInput; + } + return mockInput; + }), + }; - expect(isNumberOrDashKey(event1)).toBe(false); - expect(isNumberOrDashKey(event2)).toBe(false); - expect(isNumberOrDashKey(event3)).toBe(false); - }); + global.document = mockDocument; + // Setup global variables used by these functions + global.MAROffTarget = false; + global.MAROffTarget_degree = 0; + global.MAROffTarget_aboveOpt = false; + global.MAROffTarget_above35 = false; + global.MAROffTarget_onlyOption = false; + global.MARF1primers = false; + global.MARR1primers = false; + global.benchling_gRNA_outputs = { + gene_list: { + test_gene: [ + { Position: 100, "Specificity Score": 85 }, + { Position: 120, "Specificity Score": 75 }, + ], + }, + }; + global.current_gene = "test_gene"; + global.correctNucleotideIncluded = true; + global.MARgRNAseq = true; + }); - it("should return false for special characters", () => { - const event1 = { which: 33 }; // '!' - const event2 = { which: 64 }; // '@' + afterEach(() => { + jest.clearAllMocks(); + delete global.document; + delete global.MAROffTarget; + delete global.MAROffTarget_degree; + delete global.MAROffTarget_aboveOpt; + delete global.MAROffTarget_above35; + delete global.MAROffTarget_onlyOption; + delete global.MARF1primers; + delete global.MARR1primers; + delete global.benchling_gRNA_outputs; + delete global.current_gene; + delete global.correctNucleotideIncluded; + delete global.MARgRNAseq; + }); - expect(isNumberOrDashKey(event1)).toBe(false); - expect(isNumberOrDashKey(event2)).toBe(false); + describe("isNumberOrDashKey()", () => { + const cases = [ + { name: "number key 0", input: { which: 48 }, want: true }, + { name: "number key 9", input: { which: 57 }, want: true }, + { name: "number key 5 (keyCode)", input: { keyCode: 53 }, want: true }, + { name: "dash key", input: { which: 45 }, want: true }, + { name: "period key", input: { which: 46 }, want: true }, + { name: "letter A", input: { which: 65 }, want: false }, + { name: "letter Z", input: { which: 90 }, want: false }, + { name: "letter a", input: { which: 97 }, want: false }, + { name: "special ! character", input: { which: 33 }, want: false }, + { name: "special @ character", input: { which: 64 }, want: false }, + ]; + + it.each(cases)("$name", ({ input, want }) => { + expect(isNumberOrDashKey(input)).toBe(want); }); }); - // Test the createComplementarySeq function describe("createComplementarySeq()", () => { - const complementary_nt_dict = { - A: "T", - T: "A", - C: "G", - G: "C", - }; - - const createComplementarySeq = (seq) => { - let comp_seq = ""; - for (const element of seq) { - comp_seq = complementary_nt_dict[element] + comp_seq; - } - return comp_seq; - }; - - it("should create correct complementary sequence", () => { - const sequence = "ATCG"; - const result = createComplementarySeq(sequence); - expect(result).toBe("CGAT"); - }); - - it("should handle longer sequences", () => { - const sequence = "GCTCGTGACCACCCTGACCT"; - const result = createComplementarySeq(sequence); - expect(result).toBe("AGGTCAGGGTGGTCACGAGC"); + const cases = [ + { name: "short sequence ATCG", input: "ATCG", want: "CGAT" }, + { name: "longer sequence", input: "GCTCGTGACCACCCTGACCT", want: "AGGTCAGGGTGGTCACGAGC" }, + { name: "empty sequence", input: "", want: "" }, + { name: "single nucleotide A", input: "A", want: "T" }, + { name: "single nucleotide T", input: "T", want: "A" }, + { name: "single nucleotide C", input: "C", want: "G" }, + { name: "single nucleotide G", input: "G", want: "C" }, + ]; + + it.each(cases)("$name", ({ input, want }) => { + const result = createComplementarySeq(input); + expect(result).toBe(want); }); + }); - it("should handle empty sequence", () => { - const sequence = ""; - const result = createComplementarySeq(sequence); - expect(result).toBe(""); + describe("getOffTargetOptimalValue()", () => { + const cases = [ + { + name: "returns 80 when min optimal is below 35 threshold (input: 30)", + input: 30, + want: 80, + expectedType: "number", + }, + { + name: "returns 80 when min optimal is below 35 threshold (input: 170)", + input: 170, + want: 80, + expectedType: "number", + }, + { + name: "returns 80 when min optimal exceeds 80 threshold (input: 500)", + input: 500, + want: 80, + expectedType: "number", + }, + { + name: "returns min optimal for typical range (input: 90)", + input: 90, + want: 72, + expectedType: "number", + }, + { + name: "returns min optimal for typical range (input: 60)", + input: 60, + want: 48, + expectedType: "number", + }, + { + name: "returns min optimal for typical range (input: 75)", + input: 75, + want: 60, + expectedType: "number", + }, + { + name: "handles boundary case at 175 (minOptimal = 140, > 80)", + input: 175, + want: 80, + expectedType: "number", + }, + { + name: "handles boundary case at 100 (minOptimal = 80, exactly at boundary)", + input: 100, + want: 80, + expectedType: "number", + }, + { + name: "handles small positive value (input: 50)", + input: 50, + want: 40, + expectedType: "number", + }, + ]; + + it.each(cases)("$name", ({ input, want, expectedType }) => { + const result = getOffTargetOptimalValue(input); + expect(result).toEqual(want); + expect(typeof result).toBe(expectedType); }); - it("should handle single nucleotide", () => { - expect(createComplementarySeq("A")).toBe("T"); - expect(createComplementarySeq("T")).toBe("A"); - expect(createComplementarySeq("C")).toBe("G"); - expect(createComplementarySeq("G")).toBe("C"); + it("should not modify input parameter", () => { + const input = 90; + const inputCopy = input; + getOffTargetOptimalValue(input); + expect(input).toBe(inputCopy); }); }); - // Test marking logic without DOM dependencies - describe("markAnswers() logic", () => { - const calculateMarks = (answers) => { - let studentMark = 0; - const { - MARgRNAseq, - MARgRNAseq_degree, - MARPAMseq, - MAROffTarget, - MAROffTarget_degree, - MARF1primers, - MARR1primers, - } = answers; - - if (MARgRNAseq) { - if (MARgRNAseq_degree === 1) { - studentMark += 2; - } else if (MARgRNAseq_degree === 2) { - studentMark += 1; - } else if (MARgRNAseq_degree === 3) { - studentMark += 0.5; - } - } - if (MARPAMseq) { - studentMark += 2; - } - if (MAROffTarget) { - if (MAROffTarget_degree === 1) { - studentMark += 2; - } else if (MAROffTarget_degree === 2) { - studentMark += 1; - } else if (MAROffTarget_degree === 3) { - studentMark += 0.5; - } - } - if (MARF1primers) { - studentMark += 2; - } - if (MARR1primers) { - studentMark += 2; - } - - const studentMarkPercentage = ((studentMark / 10) * 100).toFixed(2); - return { studentMark, studentMarkPercentage }; - }; - - it("should calculate full marks correctly", () => { - const answers = { + describe("checkOffTarget()", () => { + beforeEach(() => { + resetState(); + setTestState({ + correctNucleotideIncluded: true, MARgRNAseq: true, - MARgRNAseq_degree: 1, - MARPAMseq: true, - MAROffTarget: true, - MAROffTarget_degree: 1, - MARF1primers: true, - MARR1primers: true, - }; - - const result = calculateMarks(answers); - expect(result.studentMark).toBe(10); - expect(parseFloat(result.studentMarkPercentage)).toBe(100); + benchling_gRNA_outputs: { + gene_list: { + test_gene: [{ Position: 100, "Specificity Score": 85 }], + }, + }, + current_gene: "test_gene", + }); }); - it("should calculate partial marks correctly", () => { - const answers = { - MARgRNAseq: true, - MARgRNAseq_degree: 2, // 1 mark - MARPAMseq: true, // 2 marks - MAROffTarget: false, // 0 marks - MAROffTarget_degree: 0, - MARF1primers: true, // 2 marks - MARR1primers: false, // 0 marks - }; - - const result = calculateMarks(answers); - expect(result.studentMark).toBe(5); - expect(parseFloat(result.studentMarkPercentage)).toBe(50); - }); + it("sets MAROffTarget true when score matches input within bounds", () => { + global.document.getElementById = jest.fn((id) => { + if (id === "offtarget_input") return { value: "75" }; + if (id === "position_input") return { value: "100" }; + return { value: "" }; + }); - it("should handle zero marks", () => { - const answers = { - MARgRNAseq: false, - MARgRNAseq_degree: 0, - MARPAMseq: false, - MAROffTarget: false, - MAROffTarget_degree: 0, - MARF1primers: false, - MARR1primers: false, - }; - - const result = calculateMarks(answers); - expect(result.studentMark).toBe(0); - expect(parseFloat(result.studentMarkPercentage)).toBe(0); - }); + checkOffTarget(75); - it("should handle fractional marks", () => { - const answers = { - MARgRNAseq: true, - MARgRNAseq_degree: 3, // 0.5 marks - MARPAMseq: false, - MAROffTarget: true, - MAROffTarget_degree: 3, // 0.5 marks - MARF1primers: false, - MARR1primers: false, - }; - - const result = calculateMarks(answers); - expect(result.studentMark).toBe(1); - expect(parseFloat(result.studentMarkPercentage)).toBe(10); + expect(crispr.MAROffTarget).toBe(true); }); - }); - // Test off-target scoring logic - describe("checkOffTarget() logic", () => { - const checkOffTargetScore = (score, inputValue, prerequisites) => { - const { correctNucleotideIncluded, MARgRNAseq } = prerequisites; - let MAROffTarget = false; - let MAROffTarget_degree = 0; - let MAROffTarget_aboveOpt = false; - let MAROffTarget_above35 = false; - let MAROffTarget_onlyOption = false; - - const OffTargetValue_down = Math.floor(score); - const OffTargetValue_up = Math.ceil(score); - - if (correctNucleotideIncluded && MARgRNAseq) { - if (inputValue >= OffTargetValue_down && inputValue <= OffTargetValue_up) { - MAROffTarget = true; - if (score >= 75) { - MAROffTarget_degree = 1; - MAROffTarget_aboveOpt = true; - } else if (score >= 35) { - MAROffTarget_degree = 2; - MAROffTarget_above35 = true; - } else { - MAROffTarget_degree = 3; - MAROffTarget_onlyOption = true; - } - } - } - - return { - MAROffTarget, - MAROffTarget_degree, - MAROffTarget_aboveOpt, - MAROffTarget_above35, - MAROffTarget_onlyOption, - }; - }; - - it("should set correct off-target marking for high score", () => { - const result = checkOffTargetScore(85, 85, { - correctNucleotideIncluded: true, - MARgRNAseq: true, + it("sets MAROffTarget degree 1 for score >= 75", () => { + global.document.getElementById = jest.fn((id) => { + if (id === "offtarget_input") return { value: "85" }; + if (id === "position_input") return { value: "100" }; + return { value: "" }; }); - expect(result.MAROffTarget).toBe(true); - expect(result.MAROffTarget_degree).toBe(1); - expect(result.MAROffTarget_aboveOpt).toBe(true); + checkOffTarget(85); + + expect(crispr.MAROffTarget).toBe(true); + expect(crispr.MAROffTarget_degree).toBe(1); }); - it("should set correct off-target marking for medium score", () => { - const result = checkOffTargetScore(50, 50, { - correctNucleotideIncluded: true, - MARgRNAseq: true, + it("sets MAROffTarget degree 2 for score 35-75", () => { + global.document.getElementById = jest.fn((id) => { + if (id === "offtarget_input") return { value: "50" }; + if (id === "position_input") return { value: "100" }; + return { value: "" }; }); - expect(result.MAROffTarget).toBe(true); - expect(result.MAROffTarget_degree).toBe(2); - expect(result.MAROffTarget_above35).toBe(true); + checkOffTarget(50); + + expect(crispr.MAROffTarget).toBe(true); + expect(crispr.MAROffTarget_degree).toBe(2); }); - it("should set correct off-target marking for low score", () => { - const result = checkOffTargetScore(25, 25, { - correctNucleotideIncluded: true, - MARgRNAseq: true, - }); + it("should not set MAROffTarget when prerequisites not met", () => { + global.correctNucleotideIncluded = false; + global.MARgRNAseq = false; - expect(result.MAROffTarget).toBe(true); - expect(result.MAROffTarget_degree).toBe(3); - expect(result.MAROffTarget_onlyOption).toBe(true); + checkOffTarget(75); + + expect(global.MAROffTarget).toBe(false); + expect(global.MAROffTarget_degree).toBe(0); }); + }); - it("should not set off-target marking when prerequisites not met", () => { - const result = checkOffTargetScore(85, 85, { - correctNucleotideIncluded: false, - MARgRNAseq: false, + describe("checkF1Primers()", () => { + beforeEach(() => { + resetState(); + }); + + it("validates F1 primer for sequence starting with G", () => { + const seq = "GCTCGTGACCACCCTGACCT"; + global.document.getElementById = jest.fn((id) => { + if (id === "f1_input") { + return { + value: "TAATACGACTCACTATAGGCTCGTGACCACCCTG", + trim: jest.fn(function () { + return this.value; + }), + }; + } + return { value: "", trim: jest.fn(() => "") }; }); - expect(result.MAROffTarget).toBe(false); - expect(result.MAROffTarget_degree).toBe(0); + checkF1Primers(seq); + + expect(crispr.MARF1primers).toBe(true); }); - it("should handle decimal scores correctly", () => { - const result = checkOffTargetScore(85.7, 85, { - correctNucleotideIncluded: true, - MARgRNAseq: true, + it("rejects incorrect F1 primer", () => { + const seq = "GCTCGTGACCACCCTGACCT"; + global.document.getElementById = jest.fn((id) => { + if (id === "f1_input") { + return { + value: "WRONGPRIMER", + trim: jest.fn(function () { + return this.value; + }), + }; + } + return { value: "", trim: jest.fn(() => "") }; }); - expect(result.MAROffTarget).toBe(true); - expect(result.MAROffTarget_degree).toBe(1); + checkF1Primers(seq); + + expect(crispr.MARF1primers).toBe(false); }); - }); - // Test primer generation logic - describe("F1 Primer generation logic", () => { - const generateF1Primers = (seq) => { - const possible_F1_primers = []; - const begin_F1 = "TAATACGACTCACTATAG"; - let count_First = true; - if (seq[0] === "G") { - count_First = false; - } - for (let i = 16; i <= 20; i += 1) { - possible_F1_primers.push(begin_F1 + seq.slice(0, i)); - } - if (!count_First) { - for (let i = 16; i <= 20; i += 1) { - possible_F1_primers.push(begin_F1 + seq.slice(1, i)); + it("accepts valid F1 primer within range", () => { + const seq = "ATCGATCGATCG"; + global.document.getElementById = jest.fn((id) => { + if (id === "f1_input") { + return { + value: "TAATACGACTCACTATAGATCGATCGATCG", + trim: jest.fn(function () { + return this.value; + }), + }; } - } - return possible_F1_primers; - }; + return { value: "", trim: jest.fn(() => "") }; + }); - const validateF1Primer = (seq, inputPrimer) => { - const possiblePrimers = generateF1Primers(seq); - return possiblePrimers.includes(inputPrimer); - }; + checkF1Primers(seq); - it("should generate correct F1 primers for sequence starting with G", () => { - const sequence = "GCTCGTGACCACCCTGACCT"; - const primers = generateF1Primers(sequence); + expect(crispr.MARF1primers).toBe(true); + }); + }); - expect(primers.length).toBeGreaterThan(5); - expect(primers[0]).toBe("TAATACGACTCACTATAGGCTCGTGACCACCCTG"); + describe("checkR1Primers()", () => { + beforeEach(() => { + resetState(); }); - it("should validate correct F1 primer", () => { - const sequence = "GCTCGTGACCACCCTGACCT"; - const primer = "TAATACGACTCACTATAGCTCGTGACCACCCTGA"; + it("validates R1 primer for complementary sequence", () => { + const seq = "ATCGATCG"; + global.document.getElementById = jest.fn((id) => { + if (id === "r1_input") { + return { + value: "TTCTAGCTCTAAAACCGATCGAT", + trim: jest.fn(function () { + return this.value; + }), + }; + } + return { value: "", trim: jest.fn(() => "") }; + }); + + checkR1Primers(seq); - expect(validateF1Primer(sequence, primer)).toBe(true); + expect(crispr.MARR1primers).toBe(true); }); - it("should reject incorrect F1 primer", () => { - const sequence = "GCTCGTGACCACCCTGACCT"; - const primer = "WRONGPRIMER"; + it("rejects incorrect R1 primer", () => { + const seq = "ATCGATCG"; + global.document.getElementById = jest.fn((id) => { + if (id === "r1_input") { + return { + value: "WRONGPRIMER", + trim: jest.fn(function () { + return this.value; + }), + }; + } + return { value: "", trim: jest.fn(() => "") }; + }); - expect(validateF1Primer(sequence, primer)).toBe(false); + checkR1Primers(seq); + + expect(crispr.MARR1primers).toBe(false); }); }); - // Test gene list functionality - describe("Gene list functionality", () => { - const fillGeneList = (gene_backgroundInfo) => { - if (gene_backgroundInfo?.gene_list) { - return Object.keys(gene_backgroundInfo.gene_list); - } - return []; - }; + describe("fillGeneList()", () => { + beforeEach(() => { + resetState(); + // Setup jQuery mock + global.$ = jest.fn((selector) => { + return { + empty: jest.fn(() => global.$returnValue), + append: jest.fn(() => global.$returnValue), + }; + }); + global.$returnValue = undefined; + }); - it("should return gene list when background info exists", () => { - const backgroundInfo = { - gene_list: { - eBFP: { "Target position": 100 }, - ACTN3: { "Target position": 200 }, + it("populates gene dropdown with genes from background info", () => { + setTestState({ + gene_backgroundInfo: { + gene_list: { + eBFP: { "Target position": 100 }, + ACTN3: { "Target position": 200 }, + }, }, - }; + }); + + fillGeneList(); - const result = fillGeneList(backgroundInfo); - expect(result).toEqual(["eBFP", "ACTN3"]); + expect(global.$).toHaveBeenCalledWith("#gene_dropdown_selection"); + const calls = global.$.mock.results; + const emptyCall = calls.find((c) => c.value.empty); + expect(emptyCall).toBeDefined(); }); - it("should return empty array when no background info", () => { - const result = fillGeneList(null); - expect(result).toEqual([]); + it("does nothing when no background info", () => { + setTestState({ gene_backgroundInfo: null }); + fillGeneList(); + expect(global.$).not.toHaveBeenCalled(); }); - it("should return empty array when gene_list is undefined", () => { - const backgroundInfo = {}; - const result = fillGeneList(backgroundInfo); - expect(result).toEqual([]); + it("does nothing when gene_list is undefined", () => { + setTestState({ gene_backgroundInfo: {} }); + fillGeneList(); + expect(global.$).not.toHaveBeenCalled(); }); }); }); diff --git a/core/scripts/login.js b/core/scripts/login.js deleted file mode 100644 index be716f9..0000000 --- a/core/scripts/login.js +++ /dev/null @@ -1,272 +0,0 @@ -//= ================================ SciGrade ================================== -// -// Purpose: Login and registration for SciGrade -// -//= ============================================================================ - -let student_reg_information; - -/** Let users continue with practice application without logging in (default true) */ -let continueWithoutLogin = true; - -let checkStudentNum = false; -let studentNumber = 0; -let studentUmail; -let alreadyRegistered = false; -let classRegister; - -/** - * Checks whether a student number and email match the class roster. - * @param {number} student_num Student number - * @param {string} student_umail Student email/uMail - */ -function checkStudentNumber(student_num, student_umail) { - alreadyRegistered = false; - checkStudentNum = false; - let maxNum = 0; - const classList = student_reg_information?.[0]?.class_list; - if (classList) { - for (const key in classList) { - if ( - student_reg_information[0].student_list !== null && - student_reg_information[0].student_list.length > 0 - ) { - for (const student of student_reg_information[0].student_list) { - if (student.student_number === student_num && student.studentClass === key) { - alreadyRegistered = true; - } - } - } - } - } - - if (!alreadyRegistered) { - for (const key in classList) { - if (classList[key][student_num] === student_umail) { - checkStudentNum = true; - studentNumber = student_num; - studentUmail = student_umail; - studentParseNum = i; - classRegister = key; - break; - } else { - maxNum += 1; - } - } - } - if (checkStudentNum) { - addSecondSection(); - } else if (alreadyRegistered) { - showRegError(4); - } else if (!checkStudentNum && maxNum === student_reg_information?.[0]?.student_list.length) { - showRegError(1); - } else if (!checkStudentNum) { - showRegError(2); - } -} - -/** - * Checks whether a student number exists in the registration list. - * @param {number} student_NumVerify - Student number - */ -function loginVerify(student_NumVerify) { - alreadyRegistered = false; - let maxNum = 0; - checkStudentNum = false; - - if (student_reg_information?.[0]?.student_list && student_reg_information[0].student_list.length > 0) { - for (let i = 0; i < student_reg_information[0].student_list.length; i += 1) { - if (student_reg_information[0].student_list[i].student_number === student_NumVerify) { - if (student_reg_information[0].student_list[i].gmail !== "unregistered") { - alreadyRegistered = true; - checkStudentNum = true; - studentNumber = student_NumVerify; - studentParseNum = i; - } else if (student_reg_information[0].student_list[i].gmail === "unregistered") { - alreadyRegistered = false; - } - } else { - maxNum += 1; - } - } - } - - if (alreadyRegistered && checkStudentNum) { - $("#loginForm").empty(); - document.getElementById("loginP2").style.display = "block"; - } else if (!alreadyRegistered) { - showRegError(5); - } else if (maxNum === student_reg_information[0].student_list.length) { - showRegError(1); - } -} - -/** - * Shows a registration error message by code. - * @param {number} whichOne - Error code to display - */ -function showRegError(whichOne) { - if (whichOne === 1) { - // Not in our database - document.getElementById("errorRegContent").innerHTML = - "It appears that you are not a student in our database.\n Are you a UofT student? If so, contact your TA, Professor or Admin for further help."; - $("#errorRegButton").click(); - } else if (whichOne === 2) { - // Something went wrong with registration - document.getElementById("errorRegContent").innerHTML = - "Something went wrong. Please refresh the page and try again. If this persists, please contact your TA, Professor or Admin with the following error code: lgn83-85"; - $("#errorRegButton").click(); - } else if (whichOne === 3) { - // Verify ID was incorrect - document.getElementById("errorRegContent").innerHTML = - "It appears that your verification ID is incorrect. Please retype it and try again. If this persists, contact your TA, Professor or Admin for further help. NOTE: Only students of HMB311 can register for SciGrade."; - $("#errorRegButton").click(); - } else if (whichOne === 4) { - // Verify ID was incorrect - document.getElementById("errorRegContent").innerHTML = - "You are already registered for SciGrade, please navigate to the login tab instead of the register. If an issue arises, please contact your TA, Professor or Admin for further help."; - $("#errorRegButton").click(); - } else if (whichOne === 5) { - // Not yet registered for SciGrade - document.getElementById("errorRegContent").innerHTML = - "It appears that you have not yet registered for SciGrade, please navigate to the register tab and register first. If an issue arises, please contact your TA, Professor or Admin for further help."; - $("#errorRegButton").click(); - } else if (whichOne === 6) { - // Not yet registered for SciGrade - document.getElementById("errorRegContent").innerHTML = - "This is not the Google account associated with this student number. If an issue arises, please contact your TA, Professor or Admin for further help."; - $("#errorRegButton").click(); - } else if (whichOne === 7) { - // Restricted access to only TA's and Admins' - document.getElementById("errorRegContent").innerHTML = - "This feature is restricted to only TA's and admins. You do not have access. Please contact a TA or admin for access."; - $("#errorRegButton").click(); - } else if (whichOne === 8) { - // Unequal amount of student numbers and student emails - document.getElementById("errorRegContent").innerHTML = - "There is an unequal amount of student numbers and student uMails, please correct this to proceed"; - $("#errorRegButton").click(); - } -} - -/** - * Adds the verification student section to the login page - */ -function addSecondSection() { - $("#pP1").empty(); - $("#registerP1").empty(); - document.getElementById("pP3").innerHTML = - "Next, you will login through Google to register for SciGrade. This way you will never need to remember a username or password. Please click the button below to complete your registration."; - document.getElementById("registerP3").style.display = "block"; -} - -/** - * Signs the user out and returns screen back to login/register display - */ -function signOutDisplay() { - if (document.getElementById("accountIO")) { - document.getElementById("accountIO").setAttribute("hidden", true); - } - - if (document.getElementById("logIO")) { - document.getElementById("logIO").setAttribute("hidden", true); - document.getElementById("logIO").innerHTML = `${changeLogin} Login`; - document.getElementById("logIO").setAttribute("onclick"); - } - - $("#mainContainer").empty(); - - let append_str; - append_str = "
\n"; - // Pre-all - append_str += - '
\n'; - // Close - append_str += '
\n'; - append_str += "
\n"; - $("#mainContainer").append(append_str); -} - -/** - * Load the content fill section for a gene - * @example Use this function to call and load the gene in the gene dropdown - * loadGeneContent() - * // returns null (does not return anything but loads the gene content on the web app) - */ -function loadGeneContent() { - checkAnswers_executed = false; - possible_gene = document.getElementById("gene_dropdown_selection").value; - select_Gene(); -} - -const changeLogin = ''; - -/** - * Builds the selection UI and loads the reference data for the runtime page. - */ -async function redirectCRISPR() { - $("#mainContainer").empty(); - let append_str; - append_str = ` -
-
-
-
-
-

Please select the dry lab mode you would like to use:

-
- -
- Please select your gene: -
- -
-
- -
- -
-
- -
-
-
- -
-
- `; - - $("#mainContainer").append(append_str); - - await loadCRISPRJSON_Files(); - - fillGeneList(); - - if (!continueWithoutLogin) { - if (document.getElementById("accountIO")) { - document.getElementById("accountIO").removeAttribute("hidden"); - } - if (document.getElementById("logIO")) { - document.getElementById("logIO").innerHTML = `${changeLogin} Logout`; - document.getElementById("logIO").setAttribute("onclick", "signOutDisplay();"); - document.getElementById("logIO").removeAttribute("hidden"); - } - } -} - -$(document).ready(() => { - $("#loginTab").click(); -}); diff --git a/core/scripts/login.min.js b/core/scripts/login.min.js deleted file mode 100644 index d4a3092..0000000 --- a/core/scripts/login.min.js +++ /dev/null @@ -1 +0,0 @@ -let student_reg_information,studentUmail,classRegister,continueWithoutLogin=!0,checkStudentNum=!1,studentNumber=0,alreadyRegistered=!1;function checkStudentNumber(t,e){alreadyRegistered=!1,checkStudentNum=!1;let n=0;const o=student_reg_information?.[0]?.class_list;if(o)for(const i in o)if(null!==student_reg_information[0].student_list&&student_reg_information[0].student_list.length>0)for(const e of student_reg_information[0].student_list)e.student_number===t&&e.studentClass===i&&(alreadyRegistered=!0);if(!alreadyRegistered)for(const s in o){if(o[s][t]===e){checkStudentNum=!0,studentNumber=t,studentUmail=e,studentParseNum=i,classRegister=s;break}n+=1}checkStudentNum?addSecondSection():alreadyRegistered?showRegError(4):checkStudentNum||n!==student_reg_information?.[0]?.student_list.length?checkStudentNum||showRegError(2):showRegError(1)}function loginVerify(t){alreadyRegistered=!1;let e=0;if(checkStudentNum=!1,student_reg_information?.[0]?.student_list&&student_reg_information[0].student_list.length>0)for(let n=0;n\n
\n')}function loadGeneContent(){checkAnswers_executed=!1,possible_gene=document.getElementById("gene_dropdown_selection").value,select_Gene()}const changeLogin='';async function redirectCRISPR(){let t;$("#mainContainer").empty(),t="\n\t\t
\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t

Please select the dry lab mode you would like to use:

\n\t\t\t\t\t
\n\n\t\t\t\t\t
\n\t\t\t\t\t\tPlease select your gene:\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\tLoad Gene\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t
\n\t\t
\n\t",$("#mainContainer").append("\n\t\t
\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t

Please select the dry lab mode you would like to use:

\n\t\t\t\t\t
\n\n\t\t\t\t\t
\n\t\t\t\t\t\tPlease select your gene:\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\tLoad Gene\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t
\n\t\t
\n\t"),await loadCRISPRJSON_Files(),fillGeneList(),continueWithoutLogin||(document.getElementById("accountIO")&&document.getElementById("accountIO").removeAttribute("hidden"),document.getElementById("logIO")&&(document.getElementById("logIO").innerHTML=`${changeLogin} Logout`,document.getElementById("logIO").setAttribute("onclick","signOutDisplay();"),document.getElementById("logIO").removeAttribute("hidden")))}$(document).ready((()=>{$("#loginTab").click()})); \ No newline at end of file diff --git a/core/scripts/login.test.js b/core/scripts/login.test.js deleted file mode 100644 index 0c7a11a..0000000 --- a/core/scripts/login.test.js +++ /dev/null @@ -1,377 +0,0 @@ -describe("login.js - Utility Functions", () => { - // Test the checkStudentNumber function logic - describe("checkStudentNumber()", () => { - const validateStudentNumber = (studentNumber) => { - const studentNumberStr = String(studentNumber); - return ( - studentNumberStr.length === 6 && - /^\d+$/.test(studentNumberStr) && - studentNumber > 100000 && - studentNumber <= 999999 - ); - }; - - it("should return true for valid 6-digit student numbers", () => { - expect(validateStudentNumber(123456)).toBe(true); - expect(validateStudentNumber(999999)).toBe(true); - expect(validateStudentNumber(500000)).toBe(true); - }); - - it("should return false for numbers with less than 6 digits", () => { - expect(validateStudentNumber(12345)).toBe(false); - expect(validateStudentNumber(999)).toBe(false); - expect(validateStudentNumber(1)).toBe(false); - }); - - it("should return false for numbers with more than 6 digits", () => { - expect(validateStudentNumber(1234567)).toBe(false); - expect(validateStudentNumber(12345678)).toBe(false); - }); - - it("should return false for edge cases", () => { - expect(validateStudentNumber(100000)).toBe(false); // Too low - expect(validateStudentNumber(1000000)).toBe(false); // Too high - expect(validateStudentNumber(0)).toBe(false); - }); - - it("should handle string inputs", () => { - expect(validateStudentNumber("123456")).toBe(true); - expect(validateStudentNumber("999999")).toBe(true); - expect(validateStudentNumber("abc123")).toBe(false); - expect(validateStudentNumber("12345a")).toBe(false); - }); - }); - - // Test login verification logic - describe("loginVerify() logic", () => { - const verifyLogin = (studentNumber, password, correctPassword) => { - const isValidStudentNumber = - String(studentNumber).length === 6 && - /^\d+$/.test(String(studentNumber)) && - studentNumber > 100000 && - studentNumber <= 999999; - - const isValidPassword = password === correctPassword; - - return { - isValid: isValidStudentNumber && isValidPassword, - hasValidStudentNumber: isValidStudentNumber, - hasValidPassword: isValidPassword, - }; - }; - - it("should return true for valid credentials", () => { - const result = verifyLogin(123456, "correct", "correct"); - expect(result.isValid).toBe(true); - expect(result.hasValidStudentNumber).toBe(true); - expect(result.hasValidPassword).toBe(true); - }); - - it("should return false for invalid student number", () => { - const result = verifyLogin(12345, "correct", "correct"); - expect(result.isValid).toBe(false); - expect(result.hasValidStudentNumber).toBe(false); - expect(result.hasValidPassword).toBe(true); - }); - - it("should return false for invalid password", () => { - const result = verifyLogin(123456, "wrong", "correct"); - expect(result.isValid).toBe(false); - expect(result.hasValidStudentNumber).toBe(true); - expect(result.hasValidPassword).toBe(false); - }); - - it("should return false for both invalid", () => { - const result = verifyLogin(12345, "wrong", "correct"); - expect(result.isValid).toBe(false); - expect(result.hasValidStudentNumber).toBe(false); - expect(result.hasValidPassword).toBe(false); - }); - }); - - // Test error display logic - describe("showRegError() logic", () => { - const getErrorMessage = (studentNumberValid, passwordValid) => { - if (!studentNumberValid && !passwordValid) { - return "Student number and password are both incorrect"; - } else if (!studentNumberValid) { - return "Student number is not valid"; - } else if (!passwordValid) { - return "Password is incorrect"; - } - return ""; - }; - - it("should return error for invalid student number only", () => { - const message = getErrorMessage(false, true); - expect(message).toBe("Student number is not valid"); - }); - - it("should return error for invalid password only", () => { - const message = getErrorMessage(true, false); - expect(message).toBe("Password is incorrect"); - }); - - it("should return error for both invalid", () => { - const message = getErrorMessage(false, false); - expect(message).toBe("Student number and password are both incorrect"); - }); - - it("should return empty string for valid inputs", () => { - const message = getErrorMessage(true, true); - expect(message).toBe(""); - }); - }); - - // Test gene content loading logic - describe("loadGeneContent() logic", () => { - const generateGeneInfo = (gene, backgroundInfo) => { - const info = { - geneName: gene, - hasBackgroundInfo: false, - hasSequenceFile: false, - targetPosition: 0, - description: "", - }; - - if (backgroundInfo && backgroundInfo.gene_list && backgroundInfo.gene_list[gene]) { - info.hasBackgroundInfo = true; - const geneData = backgroundInfo.gene_list[gene]; - - if (geneData["Target position"]) { - info.targetPosition = geneData["Target position"]; - } - - if (geneData.Description) { - info.description = geneData.Description; - } - } - - // Check if sequence file would exist (simulation) - const validGenes = ["ACTN3", "APOE", "CCR5", "eBFP", "HBB"]; - if (validGenes.includes(gene)) { - info.hasSequenceFile = true; - } - - return info; - }; - - it("should generate correct info for valid gene with background", () => { - const backgroundInfo = { - gene_list: { - ACTN3: { - "Target position": 1858, - Description: "Alpha-actinin-3", - }, - }, - }; - - const result = generateGeneInfo("ACTN3", backgroundInfo); - expect(result.geneName).toBe("ACTN3"); - expect(result.hasBackgroundInfo).toBe(true); - expect(result.hasSequenceFile).toBe(true); - expect(result.targetPosition).toBe(1858); - expect(result.description).toBe("Alpha-actinin-3"); - }); - - it("should handle gene without background info", () => { - const result = generateGeneInfo("UNKNOWN", null); - expect(result.geneName).toBe("UNKNOWN"); - expect(result.hasBackgroundInfo).toBe(false); - expect(result.hasSequenceFile).toBe(false); - expect(result.targetPosition).toBe(0); - expect(result.description).toBe(""); - }); - - it("should handle valid gene without sequence file", () => { - const backgroundInfo = { - gene_list: { - TESTGENE: { - "Target position": 500, - Description: "Test gene", - }, - }, - }; - - const result = generateGeneInfo("TESTGENE", backgroundInfo); - expect(result.geneName).toBe("TESTGENE"); - expect(result.hasBackgroundInfo).toBe(true); - expect(result.hasSequenceFile).toBe(false); - expect(result.targetPosition).toBe(500); - }); - }); - - // Test redirect functionality - describe("redirectCRISPR() logic", () => { - const generateRedirectURL = (gene, studentNumber) => { - if (!gene || !studentNumber) { - return null; - } - - const validStudentNumber = - String(studentNumber).length === 6 && - /^\d+$/.test(String(studentNumber)) && - studentNumber > 100000 && - studentNumber <= 999999; - - if (!validStudentNumber) { - return null; - } - - return `core/systemrun.html?gene=${encodeURIComponent(gene)}&student=${studentNumber}`; - }; - - it("should generate correct URL for valid inputs", () => { - const url = generateRedirectURL("ACTN3", 123456); - expect(url).toBe("core/systemrun.html?gene=ACTN3&student=123456"); - }); - - it("should handle special characters in gene name", () => { - const url = generateRedirectURL("e BFP", 123456); - expect(url).toBe("core/systemrun.html?gene=e%20BFP&student=123456"); - }); - - it("should return null for invalid student number", () => { - const url = generateRedirectURL("ACTN3", 12345); - expect(url).toBe(null); - }); - - it("should return null for missing gene", () => { - const url = generateRedirectURL("", 123456); - expect(url).toBe(null); - }); - - it("should return null for missing student number", () => { - const url = generateRedirectURL("ACTN3", ""); - expect(url).toBe(null); - }); - }); - - // Test session management logic - describe("Session management logic", () => { - const createSessionData = (studentNumber, gene) => { - const sessionData = { - studentNumber: studentNumber, - selectedGene: gene, - loginTime: new Date().toISOString(), - isActive: true, - }; - - return sessionData; - }; - - const validateSession = (sessionData) => { - if (!sessionData) return false; - - const requiredFields = ["studentNumber", "selectedGene", "loginTime"]; - return requiredFields.every((field) => sessionData.hasOwnProperty(field)); - }; - - it("should create valid session data", () => { - const session = createSessionData(123456, "ACTN3"); - expect(session.studentNumber).toBe(123456); - expect(session.selectedGene).toBe("ACTN3"); - expect(session.isActive).toBe(true); - expect(session.loginTime).toBeDefined(); - }); - - it("should validate complete session data", () => { - const session = { - studentNumber: 123456, - selectedGene: "ACTN3", - loginTime: "2023-01-01T00:00:00.000Z", - }; - - expect(validateSession(session)).toBe(true); - }); - - it("should reject incomplete session data", () => { - const session = { - studentNumber: 123456, - // missing selectedGene and loginTime - }; - - expect(validateSession(session)).toBe(false); - }); - - it("should reject null session data", () => { - expect(validateSession(null)).toBe(false); - }); - }); - - // Test form validation logic - describe("Form validation logic", () => { - const validateLoginForm = (formData) => { - const errors = []; - const { studentNumber, password } = formData; - - // Validate student number - if (!studentNumber) { - errors.push("Student number is required"); - } else { - const studentNumberStr = String(studentNumber); - if (studentNumberStr.length !== 6) { - errors.push("Student number must be 6 digits"); - } else if (!/^\d+$/.test(studentNumberStr)) { - errors.push("Student number must contain only numbers"); - } else if (studentNumber <= 100000 || studentNumber > 999999) { - errors.push("Student number must be between 100001 and 999999"); - } - } - - // Validate password - if (!password) { - errors.push("Password is required"); - } else if (password.length < 1) { - errors.push("Password cannot be empty"); - } - - return { - isValid: errors.length === 0, - errors: errors, - }; - }; - - it("should validate complete form data", () => { - const formData = { - studentNumber: 123456, - password: "testpass", - }; - - const result = validateLoginForm(formData); - expect(result.isValid).toBe(true); - expect(result.errors).toHaveLength(0); - }); - - it("should reject form with missing student number", () => { - const formData = { - password: "testpass", - }; - - const result = validateLoginForm(formData); - expect(result.isValid).toBe(false); - expect(result.errors).toContain("Student number is required"); - }); - - it("should reject form with invalid student number", () => { - const formData = { - studentNumber: 12345, - password: "testpass", - }; - - const result = validateLoginForm(formData); - expect(result.isValid).toBe(false); - expect(result.errors).toContain("Student number must be 6 digits"); - }); - - it("should reject form with missing password", () => { - const formData = { - studentNumber: 123456, - }; - - const result = validateLoginForm(formData); - expect(result.isValid).toBe(false); - expect(result.errors).toContain("Password is required"); - }); - }); -}); diff --git a/core/scripts/runtime.js b/core/scripts/runtime.js new file mode 100644 index 0000000..297b9c8 --- /dev/null +++ b/core/scripts/runtime.js @@ -0,0 +1,75 @@ +//= ================================ SciGrade ================================== +// +// Purpose: Runtime flow helpers for the practice experience +// +//= ============================================================================ + +/** + * Load the content fill section for a gene. + * @example Use this function to call and load the gene in the gene dropdown + * loadGeneContent() + * // returns null (does not return anything but loads the gene content on the web app) + */ +function loadGeneContent() { + checkAnswers_executed = false; + possible_gene = document.getElementById("gene_dropdown_selection").value; + select_Gene(); +} + +/** Builds the selection UI and loads the reference data for the runtime page. */ +async function redirectCRISPR() { + $("#mainContainer").empty(); + let append_str; + append_str = ` +
+
+
+
+
+

Please select the dry lab mode you would like to use:

+
+ +
+ Please select your gene: +
+ +
+
+ +
+ +
+
+ +
+
+
+ +
+
+ `; + + $("#mainContainer").append(append_str); + + await loadCRISPRJSON_Files(); + + fillGeneList(); +} + +// Export for testing +if (typeof module !== "undefined" && module.exports) { + module.exports = { loadGeneContent, redirectCRISPR }; +} diff --git a/core/scripts/runtime.min.js b/core/scripts/runtime.min.js new file mode 100644 index 0000000..f0431b6 --- /dev/null +++ b/core/scripts/runtime.min.js @@ -0,0 +1,41 @@ +function loadGeneContent(){checkAnswers_executed=!1,possible_gene=document.getElementById("gene_dropdown_selection").value,select_Gene()}async function redirectCRISPR(){$("#mainContainer").empty();let e;e=` +
+
+
+
+
+

Please select the dry lab mode you would like to use:

+
+ +
+ Please select your gene: +
+ +
+
+ +
+ +
+
+ +
+
+
+ +
+
+ `,$("#mainContainer").append(e),await loadCRISPRJSON_Files(),fillGeneList()}typeof module<"u"&&module.exports&&(module.exports={loadGeneContent,redirectCRISPR}); diff --git a/core/scripts/runtime.test.js b/core/scripts/runtime.test.js new file mode 100644 index 0000000..f55172b --- /dev/null +++ b/core/scripts/runtime.test.js @@ -0,0 +1,199 @@ +const { loadGeneContent, redirectCRISPR } = require("./runtime"); + +describe("runtime.js - Runtime Flow Helpers", () => { + let mockDocument; + let mockElement; + let mockJQuery; + let jQueryMock; + + beforeEach(() => { + // Clear any existing mocks + jest.clearAllMocks(); + + // Mock DOM elements + mockElement = { + value: "", + id: "gene_dropdown_selection", + }; + + mockDocument = { + getElementById: jest.fn(() => mockElement), + }; + + mockJQuery = { + empty: jest.fn(function () { + return this; + }), + append: jest.fn(function () { + return this; + }), + }; + + // Create jQuery mock function + jQueryMock = jest.fn(() => mockJQuery); + jQueryMock.mockClear = jest.fn(); + + // Set up globals + global.document = mockDocument; + global.$ = jQueryMock; + global.checkAnswers_executed = true; + global.possible_gene = ""; + global.select_Gene = jest.fn(); + global.loadCRISPRJSON_Files = jest.fn(async () => {}); + global.fillGeneList = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + delete global.document; + delete global.$; + delete global.checkAnswers_executed; + delete global.possible_gene; + delete global.select_Gene; + delete global.loadCRISPRJSON_Files; + delete global.fillGeneList; + }); + + describe("loadGeneContent()", () => { + const cases = [ + { + name: "loads eBFP gene and resets flags", + geneValue: "eBFP", + expectedGeneValue: "eBFP", + expectedReset: true, + }, + { + name: "loads ACTN3 gene and resets flags", + geneValue: "ACTN3", + expectedGeneValue: "ACTN3", + expectedReset: true, + }, + { + name: "loads CCR5 gene and resets flags", + geneValue: "CCR5", + expectedGeneValue: "CCR5", + expectedReset: true, + }, + { + name: "handles empty selection gracefully", + geneValue: "", + expectedGeneValue: "", + expectedReset: true, + }, + ]; + + it.each(cases)("$name", async ({ geneValue, expectedGeneValue, expectedReset }) => { + // Setup + mockElement.value = geneValue; + global.checkAnswers_executed = true; + global.document.getElementById.mockReturnValue(mockElement); + + // Execute the ACTUAL imported function + await loadGeneContent(); + + // Verify + expect(global.possible_gene).toBe(expectedGeneValue); + if (expectedReset) { + expect(global.checkAnswers_executed).toBe(false); + } + expect(global.select_Gene).toHaveBeenCalled(); + expect(global.document.getElementById).toHaveBeenCalledWith("gene_dropdown_selection"); + }); + + it("triggers select_Gene callback with correct context", () => { + mockElement.value = "HBB"; + global.document.getElementById.mockReturnValue(mockElement); + + // Execute the ACTUAL imported function + loadGeneContent(); + + expect(global.select_Gene).toHaveBeenCalledTimes(1); + expect(global.possible_gene).toBe("HBB"); + }); + }); + + describe("redirectCRISPR()", () => { + const cases = [ + { + name: "clears and repopulates main container", + }, + { + name: "calls loadCRISPRJSON_Files before fillGeneList", + }, + ]; + + it.each(cases)("$name", async ({ name }) => { + // Execute the ACTUAL imported function + await redirectCRISPR(); + + // Verify + expect(mockJQuery.empty).toHaveBeenCalled(); + expect(global.loadCRISPRJSON_Files).toHaveBeenCalledTimes(1); + expect(global.fillGeneList).toHaveBeenCalledTimes(1); + }); + + it("ensures DOM elements are properly cleared before population", async () => { + // Execute the ACTUAL imported function + await redirectCRISPR(); + + expect(mockJQuery.empty).toHaveBeenCalled(); + expect(mockJQuery.append).toHaveBeenCalled(); + }); + + it("handles asynchronous JSON loading completion", async () => { + let jsonLoaded = false; + global.loadCRISPRJSON_Files = jest.fn(async () => { + jsonLoaded = true; + }); + + expect(jsonLoaded).toBe(false); + await redirectCRISPR(); + expect(jsonLoaded).toBe(true); + }); + + it("executes jQuery operations in correct sequence", async () => { + const executionSequence = []; + + mockJQuery.empty = jest.fn(() => { + executionSequence.push("empty"); + return mockJQuery; + }); + + mockJQuery.append = jest.fn(() => { + executionSequence.push("append"); + return mockJQuery; + }); + + global.loadCRISPRJSON_Files = jest.fn(async () => { + executionSequence.push("loadCRISPRJSON_Files"); + }); + + global.fillGeneList = jest.fn(() => { + executionSequence.push("fillGeneList"); + }); + + await redirectCRISPR(); + + expect(executionSequence).toEqual(["empty", "append", "loadCRISPRJSON_Files", "fillGeneList"]); + }); + }); + + describe("Integration: Runtime flow initialization", () => { + it("properly initializes runtime flow when both functions are called", async () => { + mockElement.value = "APOE"; + global.checkAnswers_executed = true; + global.document.getElementById.mockReturnValue(mockElement); + + // Execute ACTUAL imported functions + await redirectCRISPR(); + loadGeneContent(); + + // Verify complete state + expect(global.possible_gene).toBe("APOE"); + expect(global.checkAnswers_executed).toBe(false); + expect(mockJQuery.empty).toHaveBeenCalled(); + expect(global.loadCRISPRJSON_Files).toHaveBeenCalled(); + expect(global.fillGeneList).toHaveBeenCalled(); + }); + }); +}); diff --git a/core/scripts/serviceWorker/sw.js b/core/scripts/serviceWorker/sw.js index f8158ae..4006668 100644 --- a/core/scripts/serviceWorker/sw.js +++ b/core/scripts/serviceWorker/sw.js @@ -1,2 +1,2 @@ -if(!self.define){let e,r={};const c=(c,o)=>(c=new URL(c+".js",o).href,r[c]||new Promise((r=>{if("document"in self){const e=document.createElement("script");e.src=c,e.onload=r,document.head.appendChild(e);}else e=c,importScripts(c),r();})).then((()=>{let e=r[c];if(!e)throw new Error(`Module ${c} didn’t register its module`);return e;})));self.define=(o,i)=>{const a=e||("document"in self?document.currentScript.src:"")||location.href;if(r[a])return;let s={};const n=e=>c(e,a),d={module:{uri:a},exports:s,require:n};r[a]=Promise.all(o.map((e=>d[e]||n(e)))).then((e=>(i(...e),s)));};}define(["./workbox-d365970e"],(function(e){"use strict";self.addEventListener("message",(e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting();})),e.precacheAndRoute([{url:"../../../core/data/ACTN3/ACTN3.fasta",revision:"93e810b1ce0401a46e96ceaba5687a8f"},{url:"../../../core/data/ACTN3/Benchling_gRNA_Outputs.xlsx",revision:"9d9a05ae5858671f89b87ae185b4917b"},{url:"../../../core/data/APOE/APOE.fasta",revision:"483718a3f932ba66a063811986c9ff94"},{url:"../../../core/data/APOE/Benchling_gRNA_Outputs.xlsx",revision:"d728b00ab3559732abc114c43893ba7e"},{url:"../../../core/data/Background_info/gene_background_info.json",revision:"c949b514552cc7e229ae001f1fc2558f"},{url:"../../../core/data/Benchling_gRNA_Outputs.json",revision:"f16f964789ca6909ec2bbd40afc4a081"},{url:"../../../core/data/CCR5/Benchling_gRNA_Outputs.xlsx",revision:"d204b7de85c0def6a40789a3104dd975"},{url:"../../../core/data/CCR5/CCR5.fasta",revision:"312805e16f0bbaa7378ace3ab275d1b5"},{url:"../../../core/data/eBFP/Benchling_gRNA_outputs.xlsx",revision:"cb29f7f64c8c0f44cfe58c3504af0d2b"},{url:"../../../core/data/eBFP/eBFP.fasta",revision:"3a891a57f27b8180825de1a70974e0ad"},{url:"../../../core/data/HBB/Benchling_gRNA_Outputs.xlsx",revision:"571ef05c0a5fc985423f08ab13e3fc71"},{url:"../../../core/data/HBB/HBB.fasta",revision:"369111a8a700d52e494b75cffef452f9"},{url:"../../../core/icon/android-chrome-144x144.png",revision:"859cb36c4d6354a1d76e8d9692fdd88d"},{url:"../../../core/icon/android-chrome-192x192.png",revision:"f94fc51b10052dcc8f91e04945a618b8"},{url:"../../../core/icon/android-chrome-256x256.png",revision:"d80b94ad67334b9560e3cefaf8b04677"},{url:"../../../core/icon/android-chrome-36x36.png",revision:"c3603b9735bb8397b0ac5351da9e94ed"},{url:"../../../core/icon/android-chrome-384x384.png",revision:"45ea1a83b4d26a9224844344602ad69e"},{url:"../../../core/icon/android-chrome-48x48.png",revision:"7ac44965ab502f17205e2d048d3e1580"},{url:"../../../core/icon/android-chrome-512x512.png",revision:"d5a6869fb6a0d95e1c1398de5aac2278"},{url:"../../../core/icon/android-chrome-72x72.png",revision:"a2b1197f79059981d8a19ba65ce73e6d"},{url:"../../../core/icon/android-chrome-96x96.png",revision:"497e53074c189be631a8368192870f02"},{url:"../../../core/icon/apple-touch-icon-114x114-precomposed.png",revision:"7b0f0af98c190974af01f605b117fc18"},{url:"../../../core/icon/apple-touch-icon-114x114.png",revision:"7b0f0af98c190974af01f605b117fc18"},{url:"../../../core/icon/apple-touch-icon-120x120-precomposed.png",revision:"c61a5acca54b104d7fe15fbc473796b5"},{url:"../../../core/icon/apple-touch-icon-120x120.png",revision:"c61a5acca54b104d7fe15fbc473796b5"},{url:"../../../core/icon/apple-touch-icon-144x144-precomposed.png",revision:"52d597709f2268268fd4063d34bd0ce7"},{url:"../../../core/icon/apple-touch-icon-144x144.png",revision:"52d597709f2268268fd4063d34bd0ce7"},{url:"../../../core/icon/apple-touch-icon-152x152-precomposed.png",revision:"8d46a8911ef72a562c97570d452f83ac"},{url:"../../../core/icon/apple-touch-icon-152x152.png",revision:"8d46a8911ef72a562c97570d452f83ac"},{url:"../../../core/icon/apple-touch-icon-180x180-precomposed.png",revision:"ced32ca13a0c404c96fc081dca7473eb"},{url:"../../../core/icon/apple-touch-icon-180x180.png",revision:"ced32ca13a0c404c96fc081dca7473eb"},{url:"../../../core/icon/apple-touch-icon-57x57-precomposed.png",revision:"b3e1d7fc553b33f086791c138d69bba1"},{url:"../../../core/icon/apple-touch-icon-57x57.png",revision:"b3e1d7fc553b33f086791c138d69bba1"},{url:"../../../core/icon/apple-touch-icon-60x60-precomposed.png",revision:"37be5e105d7a21fc8f3c437508dca233"},{url:"../../../core/icon/apple-touch-icon-60x60.png",revision:"37be5e105d7a21fc8f3c437508dca233"},{url:"../../../core/icon/apple-touch-icon-72x72-precomposed.png",revision:"819a43b6e5dc4991e399ff9b5d9b34b3"},{url:"../../../core/icon/apple-touch-icon-72x72.png",revision:"819a43b6e5dc4991e399ff9b5d9b34b3"},{url:"../../../core/icon/apple-touch-icon-76x76-precomposed.png",revision:"448377ba1760c41142b83ffd0fa47163"},{url:"../../../core/icon/apple-touch-icon-76x76.png",revision:"448377ba1760c41142b83ffd0fa47163"},{url:"../../../core/icon/apple-touch-icon-precomposed.png",revision:"ced32ca13a0c404c96fc081dca7473eb"},{url:"../../../core/icon/apple-touch-icon.png",revision:"ced32ca13a0c404c96fc081dca7473eb"},{url:"../../../core/icon/favicon-16x16.png",revision:"f9e844d671e8e4b80174f643fe4dcf56"},{url:"../../../core/icon/favicon-32x32.png",revision:"a6296dcec5e5a6449807f753d885507d"},{url:"../../../core/icon/favicon.ico",revision:"ea57adfd4a62355e9f4c2b46ef21600f"},{url:"../../../core/icon/manifest.json",revision:"dd34856295abeac04c8578a56413e5ca"},{url:"../../../core/icon/maskable_icon_x128.png",revision:"cf5ea1b4cd68730505d36d59d3c8b04d"},{url:"../../../core/icon/maskable_icon_x192.png",revision:"bd977cf9153d6ebb3e8d8f79b4f100b6"},{url:"../../../core/icon/maskable_icon_x384.png",revision:"83527cdabf00617fb5b56df08a08b2a3"},{url:"../../../core/icon/maskable_icon_x48.png",revision:"c521515543d0ccc88a843690484dfefe"},{url:"../../../core/icon/maskable_icon_x512.png",revision:"499293f67750aa3b60815cb50d8e3b72"},{url:"../../../core/icon/maskable_icon_x72.png",revision:"0603508e30979836bd82b2d95dc0b1f4"},{url:"../../../core/icon/maskable_icon_x96.png",revision:"5269c50f0761a8aa50ae7201c28760c6"},{url:"../../../core/icon/maskable_icon.png",revision:"94a300afab67912c4aa267989a725155"},{url:"../../../core/icon/mstile-144x144.png",revision:"52d597709f2268268fd4063d34bd0ce7"},{url:"../../../core/icon/mstile-150x150.png",revision:"4a6b569778e6b9d81aaee10df7c65284"},{url:"../../../core/icon/mstile-310x150.png",revision:"7555e1760cd643b25105bfe4114eedfd"},{url:"../../../core/icon/mstile-310x310.png",revision:"39af54c06ac44ebe9312f682ddd3602c"},{url:"../../../core/icon/mstile-70x70.png",revision:"b2902f78c5c0344f8a0558a8f36b97bf"},{url:"../../../core/icon/resoc.png",revision:"4b94395e0972a350b0727a20e3d06baa"},{url:"../../../core/icon/safari-pinned-tab.svg",revision:"3a6f655958b21d384ae3dad886960d93"},{url:"../../../core/icon/screenshot1.webp",revision:"426fd7165422ee95968d181411229d95"},{url:"../../../core/icon/screenshot2.webp",revision:"87d56bb07395695fda9e04a30caf1672"},{url:"../../../core/icon/screenshot3.webp",revision:"19ef6219d8f3ba14ebf25b72e07240d8"},{url:"../../../core/images/BackgroundImage/grey.webp",revision:"44f3357b5a1f4d29ea3368de0f0f5279"},{url:"../../../core/images/BackgroundImage/homeBackground.webp",revision:"fcb1240061569d6c355261f8d75a0f1b"},{url:"../../../core/images/dna.png",revision:"d2778bf9fba581a4f4b8c2fca9abc9e8"},{url:"../../../core/images/dna.svg",revision:"7cf969d66011a6cddf39163fa202fa51"},{url:"../../../core/images/EDITmd/002_SciGradePracticeGene.png",revision:"a47cd17752a978f6e468985af11d5f3e"},{url:"../../../core/images/EDITmd/004_FeedbackPage.png",revision:"e4ffddaa8a91c2c5547a89a4c6f47794"},{url:"../../../core/images/EDITmd/005_Algorithm.png",revision:"934afcec1bad4f6cd1b39e799e6de5c2"},{url:"../../../core/images/logo_transparent-Dark.png",revision:"cec8eede6a0b9a7e6da788d86edf6b37"},{url:"../../../core/images/logo_transparent-Dark.svg",revision:"faef5b38e346a12888c11184c890292e"},{url:"../../../core/images/logo_transparent.png",revision:"86746c9170e4cc9fccb363d0c54b3497"},{url:"../../../core/images/logo_transparent.svg",revision:"d6abce258f77c56dfa6bb87765e3dfcf"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.css",revision:"c223f119ec9dea026126fc19efa1cda4"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.min.css",revision:"0bc3c052956530975a1406d6788a256e"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.rtl.css",revision:"8122a112a175dcfc1ce51596916dec94"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.rtl.min.css",revision:"76c20e07d9962cf2045b0a68e0c70172"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.css",revision:"89f8de928a633a7258c08ba409dc5413"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.min.css",revision:"05d3df42ebb67a65040216f50432680d"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.rtl.css",revision:"654a6734347a7a718ac6529411270276"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.rtl.min.css",revision:"c20056469f2d0f6bf6e88cc481d228ac"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.css",revision:"66ab28268efbc0dafdb53791d41e755e"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.min.css",revision:"08aded6a77f986e24299bf9a00f3791d"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.rtl.css",revision:"dfe0f0007ab21ab80c134ef0777af72f"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.rtl.min.css",revision:"8479d3eb9eb3e42703c2a46bd839281c"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap.css",revision:"e130b5189b00dbf9549803614a0c35d0"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap.min.css",revision:"1bcf7ee45ab9975f9b9eb016fb96771a"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap.rtl.css",revision:"554e153e05fe7b5c3cfd037b73997215"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap.rtl.min.css",revision:"e9066195b42ddded5976a1c61cd82e2a"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/js/bootstrap.bundle.js",revision:"6cf21db19808a582d229175c96f32bfc"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/js/bootstrap.bundle.min.js",revision:"9977a948cfde2c156160bf47d4d3dd5e"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/js/bootstrap.esm.js",revision:"2de4dc4acece93659c5cbd7abeb21566"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/js/bootstrap.esm.min.js",revision:"a740e6c7cc66aefbfa4b2bcb27a80c2f"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/js/bootstrap.js",revision:"ccd5967383a08b1b42f8dec4fabfa53e"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/js/bootstrap.min.js",revision:"eea75f8c53d120079f003f36ee698c5c"},{url:"../../../core/scripts/APIandLibraries/Fonts/MaterialIcon.css",revision:"85ee0e1867e1b9c1b01ed7e31023ae1f"},{url:"../../../core/scripts/APIandLibraries/jQuery/jquery.min.js",revision:"762e32e4cbf687a7fe34faf9bb0e511e"},{url:"../../../core/scripts/APIandLibraries/tabletoCSV/bower.json",revision:"0786a5142fef978932727a42dcf1f094"},{url:"../../../core/scripts/APIandLibraries/tabletoCSV/dist/jquery.tabletoCSV.js",revision:"38e3e2fcaff8b3a33f2e3bf01190e845"},{url:"../../../core/scripts/APIandLibraries/tabletoCSV/example.html",revision:"b278d32f287f55a0dcb008ead1d7109e"},{url:"../../../core/scripts/APIandLibraries/tabletoCSV/gulpfile.js",revision:"23f0528a75ea40b302fd34d1effe4334"},{url:"../../../core/scripts/APIandLibraries/tabletoCSV/jquery.tabletoCSV.js",revision:"3ab5e2ace2ab26ca4df0a1f06267a978"},{url:"../../../core/scripts/APIandLibraries/tabletoCSV/package.json",revision:"45f34680e60abfca81500be631358328"},{url:"../../../core/scripts/APIandLibraries/tabletoCSV/README.md",revision:"1375c24a3db5a713665c04e433244bf0"},{url:"../../../core/scripts/crispr_scripts.js",revision:"f34a277afa6473242860c4d6dd843f83"},{url:"../../../core/scripts/crispr_scripts.min.js",revision:"59b7cd41725dd02df07b7f2801cdff22"},{url:"../../../core/scripts/login.js",revision:"51d2323c07ba325cf472451668c6b2ac"},{url:"../../../core/scripts/login.min.js",revision:"a1940d1ab02755d049e02507fb0b8ba7"},{url:"../../../core/styling/style.css",revision:"ac7a0fac8e80497aa8332e4e9ae0d3d5"},{url:"../../../core/styling/style.min.css",revision:"65a608b7d010ea2b87bb7ea7584ab875"},{url:"../../../core/systemrun.html",revision:"3a598442c432e1a791a9148ddda6bbd1"}],{}),e.registerRoute(/\.(?:png|jpg|jpeg|webp|ico|svg)$/,new e.CacheFirst({cacheName:"images",plugins:[new e.ExpirationPlugin({maxEntries:10})]}),"GET"),e.registerRoute(/\.html$/,new e.NetworkFirst({cacheName:"html",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:50}),new e.CacheableResponsePlugin({statuses:[0,200]})]}),"GET"),e.registerRoute(/\.(?:css|js)$/,new e.NetworkFirst({cacheName:"assets",plugins:[new e.ExpirationPlugin({maxEntries:50}),new e.CacheableResponsePlugin({statuses:[0,200]})]}),"GET");})); +if(!self.define){let e,r={};const c=(c,o)=>(c=new URL(c+".js",o).href,r[c]||new Promise(r=>{if("document"in self){const e=document.createElement("script");e.src=c,e.onload=r,document.head.appendChild(e);}else e=c,importScripts(c),r();}).then(()=>{let e=r[c];if(!e)throw new Error(`Module ${c} didn’t register its module`);return e;}));self.define=(o,i)=>{const a=e||("document"in self?document.currentScript.src:"")||location.href;if(r[a])return;let s={};const n=e=>c(e,a),d={module:{uri:a},exports:s,require:n};r[a]=Promise.all(o.map(e=>d[e]||n(e))).then(e=>(i(...e),s));};}define(["./workbox-813eeb66"],function(e){"use strict";self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting();}),e.precacheAndRoute([{url:"../../../core/systemrun.html",revision:"61ec3960f05098c97d298ab90f475771"},{url:"../../../core/styling/style.min.css",revision:"65a608b7d010ea2b87bb7ea7584ab875"},{url:"../../../core/styling/style.css",revision:"b67cbfd3995e07bba50358eaa64009fe"},{url:"../../../core/scripts/runtime.min.js",revision:"6e4641db4ea78c758c56312b1cf2379f"},{url:"../../../core/scripts/runtime.js",revision:"1767d36bb43cfd630a1c01fa2c27f3a9"},{url:"../../../core/scripts/crispr_scripts.min.js",revision:"96fa88586a8017e47d57b94fd287ff3c"},{url:"../../../core/scripts/crispr_scripts.js",revision:"99224226d8d67fb8f654a917e9a2a29d"},{url:"../../../core/scripts/APIandLibraries/tabletoCSV/package.json",revision:"ef0258ebdf3f9d244d7d38c4c35ec723"},{url:"../../../core/scripts/APIandLibraries/tabletoCSV/jquery.tabletoCSV.js",revision:"1757e6cdbbbe5f5a1c330e2f182e24f2"},{url:"../../../core/scripts/APIandLibraries/tabletoCSV/gulpfile.js",revision:"c93880a7ac368dcd9b51d547948cbf8a"},{url:"../../../core/scripts/APIandLibraries/tabletoCSV/example.html",revision:"9b17ab0cdbf7acd33dfcfff87c410d2b"},{url:"../../../core/scripts/APIandLibraries/tabletoCSV/bower.json",revision:"79787e672bb7dc33e2a2e0bdc8467a38"},{url:"../../../core/scripts/APIandLibraries/tabletoCSV/README.md",revision:"d581efd7f3de48bdf77206307df41034"},{url:"../../../core/scripts/APIandLibraries/tabletoCSV/dist/jquery.tabletoCSV.js",revision:"38e3e2fcaff8b3a33f2e3bf01190e845"},{url:"../../../core/scripts/APIandLibraries/jQuery/jquery.min.js",revision:"a8e7cabd4d49dfaf0146678ee147dfc5"},{url:"../../../core/scripts/APIandLibraries/Fonts/MaterialIcon.css",revision:"c1cbea39f07b551807abd9967b809ffd"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/js/bootstrap.min.js",revision:"a92b3364fb0a7349f2a1c31a7ad5ed5e"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/js/bootstrap.js",revision:"52de67605c3d156f4958c83fa9cfc8d9"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/js/bootstrap.esm.min.js",revision:"6ccf144f123da79e62d5e46a06f133b9"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/js/bootstrap.esm.js",revision:"32e42b38a15f27b01f79bb13aad53714"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/js/bootstrap.bundle.min.js",revision:"5cc1b73e70520fa84b1846afe0ec8fb6"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/js/bootstrap.bundle.js",revision:"7d2c412531f05f39e97195f9b48a53dc"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap.rtl.min.css",revision:"53aa521e55523d4f35610e568c5991fd"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap.rtl.css",revision:"f50b588ab5764c08bb2e57da6bbe47c7"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap.min.css",revision:"1b1cb0e2be9a21f091a87691f20c6300"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap.css",revision:"32f9388da564ecf3f4f129021d3736a8"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.rtl.min.css",revision:"1282e3b4ef0226816da34cd92a7ae69b"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.rtl.css",revision:"95616dd04a2044a59dbefbc48c050891"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.min.css",revision:"20d013574c70b2032e408e8f81a8fec9"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.css",revision:"2609642eaa62f29a2275e0ced5a098fe"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.rtl.min.css",revision:"c01f229a610e6f96bdb57fd287f48fc9"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.rtl.css",revision:"0b4f19eb4846adc5a866b80c81bda434"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.min.css",revision:"7c821536802bd2fa35701c22f2e96d36"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.css",revision:"cb9d8d28a86d39db9b43030a8248b766"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.rtl.min.css",revision:"fc7e1eb57c409c379d21a8001a0f9b33"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.rtl.css",revision:"93f428651ed8b87b9986050f06e03cb2"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.min.css",revision:"895b9494c7178ac12ad9c56d81305665"},{url:"../../../core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.css",revision:"4dd0ce2aa69225d64aacbded692fe756"},{url:"../../../core/images/logo_transparent.svg",revision:"d6abce258f77c56dfa6bb87765e3dfcf"},{url:"../../../core/images/logo_transparent.png",revision:"86746c9170e4cc9fccb363d0c54b3497"},{url:"../../../core/images/logo_transparent-Dark.svg",revision:"faef5b38e346a12888c11184c890292e"},{url:"../../../core/images/logo_transparent-Dark.png",revision:"cec8eede6a0b9a7e6da788d86edf6b37"},{url:"../../../core/images/dna.svg",revision:"7cf969d66011a6cddf39163fa202fa51"},{url:"../../../core/images/dna.png",revision:"d2778bf9fba581a4f4b8c2fca9abc9e8"},{url:"../../../core/images/EDITmd/005_Algorithm.png",revision:"934afcec1bad4f6cd1b39e799e6de5c2"},{url:"../../../core/images/EDITmd/004_FeedbackPage.png",revision:"e4ffddaa8a91c2c5547a89a4c6f47794"},{url:"../../../core/images/EDITmd/002_SciGradePracticeGene.png",revision:"a47cd17752a978f6e468985af11d5f3e"},{url:"../../../core/images/BackgroundImage/homeBackground.webp",revision:"fcb1240061569d6c355261f8d75a0f1b"},{url:"../../../core/images/BackgroundImage/grey.webp",revision:"44f3357b5a1f4d29ea3368de0f0f5279"},{url:"../../../core/icon/screenshot3.webp",revision:"19ef6219d8f3ba14ebf25b72e07240d8"},{url:"../../../core/icon/screenshot2.webp",revision:"87d56bb07395695fda9e04a30caf1672"},{url:"../../../core/icon/screenshot1.webp",revision:"426fd7165422ee95968d181411229d95"},{url:"../../../core/icon/safari-pinned-tab.svg",revision:"3a6f655958b21d384ae3dad886960d93"},{url:"../../../core/icon/resoc.png",revision:"4b94395e0972a350b0727a20e3d06baa"},{url:"../../../core/icon/mstile-70x70.png",revision:"b2902f78c5c0344f8a0558a8f36b97bf"},{url:"../../../core/icon/mstile-310x310.png",revision:"39af54c06ac44ebe9312f682ddd3602c"},{url:"../../../core/icon/mstile-310x150.png",revision:"7555e1760cd643b25105bfe4114eedfd"},{url:"../../../core/icon/mstile-150x150.png",revision:"4a6b569778e6b9d81aaee10df7c65284"},{url:"../../../core/icon/mstile-144x144.png",revision:"52d597709f2268268fd4063d34bd0ce7"},{url:"../../../core/icon/maskable_icon_x96.png",revision:"5269c50f0761a8aa50ae7201c28760c6"},{url:"../../../core/icon/maskable_icon_x72.png",revision:"0603508e30979836bd82b2d95dc0b1f4"},{url:"../../../core/icon/maskable_icon_x512.png",revision:"499293f67750aa3b60815cb50d8e3b72"},{url:"../../../core/icon/maskable_icon_x48.png",revision:"c521515543d0ccc88a843690484dfefe"},{url:"../../../core/icon/maskable_icon_x384.png",revision:"83527cdabf00617fb5b56df08a08b2a3"},{url:"../../../core/icon/maskable_icon_x192.png",revision:"bd977cf9153d6ebb3e8d8f79b4f100b6"},{url:"../../../core/icon/maskable_icon_x128.png",revision:"cf5ea1b4cd68730505d36d59d3c8b04d"},{url:"../../../core/icon/maskable_icon.png",revision:"94a300afab67912c4aa267989a725155"},{url:"../../../core/icon/manifest.json",revision:"ec78f1b4aaf3e8be6d7db11ef45fbf72"},{url:"../../../core/icon/favicon.ico",revision:"ea57adfd4a62355e9f4c2b46ef21600f"},{url:"../../../core/icon/favicon-32x32.png",revision:"a6296dcec5e5a6449807f753d885507d"},{url:"../../../core/icon/favicon-16x16.png",revision:"f9e844d671e8e4b80174f643fe4dcf56"},{url:"../../../core/icon/apple-touch-icon.png",revision:"ced32ca13a0c404c96fc081dca7473eb"},{url:"../../../core/icon/apple-touch-icon-precomposed.png",revision:"ced32ca13a0c404c96fc081dca7473eb"},{url:"../../../core/icon/apple-touch-icon-76x76.png",revision:"448377ba1760c41142b83ffd0fa47163"},{url:"../../../core/icon/apple-touch-icon-76x76-precomposed.png",revision:"448377ba1760c41142b83ffd0fa47163"},{url:"../../../core/icon/apple-touch-icon-72x72.png",revision:"819a43b6e5dc4991e399ff9b5d9b34b3"},{url:"../../../core/icon/apple-touch-icon-72x72-precomposed.png",revision:"819a43b6e5dc4991e399ff9b5d9b34b3"},{url:"../../../core/icon/apple-touch-icon-60x60.png",revision:"37be5e105d7a21fc8f3c437508dca233"},{url:"../../../core/icon/apple-touch-icon-60x60-precomposed.png",revision:"37be5e105d7a21fc8f3c437508dca233"},{url:"../../../core/icon/apple-touch-icon-57x57.png",revision:"b3e1d7fc553b33f086791c138d69bba1"},{url:"../../../core/icon/apple-touch-icon-57x57-precomposed.png",revision:"b3e1d7fc553b33f086791c138d69bba1"},{url:"../../../core/icon/apple-touch-icon-180x180.png",revision:"ced32ca13a0c404c96fc081dca7473eb"},{url:"../../../core/icon/apple-touch-icon-180x180-precomposed.png",revision:"ced32ca13a0c404c96fc081dca7473eb"},{url:"../../../core/icon/apple-touch-icon-152x152.png",revision:"8d46a8911ef72a562c97570d452f83ac"},{url:"../../../core/icon/apple-touch-icon-152x152-precomposed.png",revision:"8d46a8911ef72a562c97570d452f83ac"},{url:"../../../core/icon/apple-touch-icon-144x144.png",revision:"52d597709f2268268fd4063d34bd0ce7"},{url:"../../../core/icon/apple-touch-icon-144x144-precomposed.png",revision:"52d597709f2268268fd4063d34bd0ce7"},{url:"../../../core/icon/apple-touch-icon-120x120.png",revision:"c61a5acca54b104d7fe15fbc473796b5"},{url:"../../../core/icon/apple-touch-icon-120x120-precomposed.png",revision:"c61a5acca54b104d7fe15fbc473796b5"},{url:"../../../core/icon/apple-touch-icon-114x114.png",revision:"7b0f0af98c190974af01f605b117fc18"},{url:"../../../core/icon/apple-touch-icon-114x114-precomposed.png",revision:"7b0f0af98c190974af01f605b117fc18"},{url:"../../../core/icon/android-chrome-96x96.png",revision:"497e53074c189be631a8368192870f02"},{url:"../../../core/icon/android-chrome-72x72.png",revision:"a2b1197f79059981d8a19ba65ce73e6d"},{url:"../../../core/icon/android-chrome-512x512.png",revision:"d5a6869fb6a0d95e1c1398de5aac2278"},{url:"../../../core/icon/android-chrome-48x48.png",revision:"7ac44965ab502f17205e2d048d3e1580"},{url:"../../../core/icon/android-chrome-384x384.png",revision:"45ea1a83b4d26a9224844344602ad69e"},{url:"../../../core/icon/android-chrome-36x36.png",revision:"c3603b9735bb8397b0ac5351da9e94ed"},{url:"../../../core/icon/android-chrome-256x256.png",revision:"d80b94ad67334b9560e3cefaf8b04677"},{url:"../../../core/icon/android-chrome-192x192.png",revision:"f94fc51b10052dcc8f91e04945a618b8"},{url:"../../../core/icon/android-chrome-144x144.png",revision:"859cb36c4d6354a1d76e8d9692fdd88d"},{url:"../../../core/data/Benchling_gRNA_Outputs.json",revision:"515b0830b197e126a294fb15cd6d8e89"},{url:"../../../core/data/eBFP/eBFP.fasta",revision:"a30dc131549dc5fbbaa59128830ca49c"},{url:"../../../core/data/eBFP/Benchling_gRNA_outputs.xlsx",revision:"cb29f7f64c8c0f44cfe58c3504af0d2b"},{url:"../../../core/data/HBB/HBB.fasta",revision:"0314a959f59b730536fddec088d4a6c5"},{url:"../../../core/data/HBB/Benchling_gRNA_Outputs.xlsx",revision:"571ef05c0a5fc985423f08ab13e3fc71"},{url:"../../../core/data/CCR5/CCR5.fasta",revision:"60232b31119c5c0dfd9c59216f46928a"},{url:"../../../core/data/CCR5/Benchling_gRNA_Outputs.xlsx",revision:"d204b7de85c0def6a40789a3104dd975"},{url:"../../../core/data/Background_info/gene_background_info.json",revision:"ebeaca222ceb2e2354f6b2bacb6d0b26"},{url:"../../../core/data/APOE/Benchling_gRNA_Outputs.xlsx",revision:"d728b00ab3559732abc114c43893ba7e"},{url:"../../../core/data/APOE/APOE.fasta",revision:"9955beb43e9fd5a6e506cb9853ef13f2"},{url:"../../../core/data/ACTN3/Benchling_gRNA_Outputs.xlsx",revision:"9d9a05ae5858671f89b87ae185b4917b"},{url:"../../../core/data/ACTN3/ACTN3.fasta",revision:"a5ad42f7acbecbc28bed864b8377c5d4"}],{}),e.registerRoute(/\.(?:png|jpg|jpeg|webp|ico|svg)$/,new e.CacheFirst({cacheName:"images",plugins:[new e.ExpirationPlugin({maxEntries:10})]}),"GET"),e.registerRoute(/\.html$/,new e.NetworkFirst({cacheName:"html",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:50}),new e.CacheableResponsePlugin({statuses:[0,200]})]}),"GET"),e.registerRoute(/\.(?:css|js)$/,new e.NetworkFirst({cacheName:"assets",plugins:[new e.ExpirationPlugin({maxEntries:50}),new e.CacheableResponsePlugin({statuses:[0,200]})]}),"GET");}); //# sourceMappingURL=sw.js.map diff --git a/core/scripts/serviceWorker/sw.js.map b/core/scripts/serviceWorker/sw.js.map index 170ea7e..1b7c92d 100644 --- a/core/scripts/serviceWorker/sw.js.map +++ b/core/scripts/serviceWorker/sw.js.map @@ -1 +1 @@ -{"version":3,"file":"sw.js","sources":["../../../AppData/Local/Temp/b093e3fb9858666c84dc914bd18943f3/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from 'C:/Users/alexa/Documents/code/SciGrade/node_modules/workbox-routing/registerRoute.mjs';\nimport {ExpirationPlugin as workbox_expiration_ExpirationPlugin} from 'C:/Users/alexa/Documents/code/SciGrade/node_modules/workbox-expiration/ExpirationPlugin.mjs';\nimport {CacheFirst as workbox_strategies_CacheFirst} from 'C:/Users/alexa/Documents/code/SciGrade/node_modules/workbox-strategies/CacheFirst.mjs';\nimport {CacheableResponsePlugin as workbox_cacheable_response_CacheableResponsePlugin} from 'C:/Users/alexa/Documents/code/SciGrade/node_modules/workbox-cacheable-response/CacheableResponsePlugin.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from 'C:/Users/alexa/Documents/code/SciGrade/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from 'C:/Users/alexa/Documents/code/SciGrade/node_modules/workbox-precaching/precacheAndRoute.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\n\n\n\n\nself.addEventListener('message', (event) => {\n if (event.data && event.data.type === 'SKIP_WAITING') {\n self.skipWaiting();\n }\n});\n\n\n\n\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"core/data/ACTN3/ACTN3.fasta\",\n \"revision\": \"93e810b1ce0401a46e96ceaba5687a8f\"\n },\n {\n \"url\": \"core/data/ACTN3/Benchling_gRNA_Outputs.xlsx\",\n \"revision\": \"9d9a05ae5858671f89b87ae185b4917b\"\n },\n {\n \"url\": \"core/data/APOE/APOE.fasta\",\n \"revision\": \"483718a3f932ba66a063811986c9ff94\"\n },\n {\n \"url\": \"core/data/APOE/Benchling_gRNA_Outputs.xlsx\",\n \"revision\": \"d728b00ab3559732abc114c43893ba7e\"\n },\n {\n \"url\": \"core/data/Background_info/gene_background_info.json\",\n \"revision\": \"c949b514552cc7e229ae001f1fc2558f\"\n },\n {\n \"url\": \"core/data/Benchling_gRNA_Outputs.json\",\n \"revision\": \"f16f964789ca6909ec2bbd40afc4a081\"\n },\n {\n \"url\": \"core/data/CCR5/Benchling_gRNA_Outputs.xlsx\",\n \"revision\": \"d204b7de85c0def6a40789a3104dd975\"\n },\n {\n \"url\": \"core/data/CCR5/CCR5.fasta\",\n \"revision\": \"312805e16f0bbaa7378ace3ab275d1b5\"\n },\n {\n \"url\": \"core/data/eBFP/Benchling_gRNA_outputs.xlsx\",\n \"revision\": \"cb29f7f64c8c0f44cfe58c3504af0d2b\"\n },\n {\n \"url\": \"core/data/eBFP/eBFP.fasta\",\n \"revision\": \"3a891a57f27b8180825de1a70974e0ad\"\n },\n {\n \"url\": \"core/data/HBB/Benchling_gRNA_Outputs.xlsx\",\n \"revision\": \"571ef05c0a5fc985423f08ab13e3fc71\"\n },\n {\n \"url\": \"core/data/HBB/HBB.fasta\",\n \"revision\": \"369111a8a700d52e494b75cffef452f9\"\n },\n {\n \"url\": \"core/icon/android-chrome-144x144.png\",\n \"revision\": \"859cb36c4d6354a1d76e8d9692fdd88d\"\n },\n {\n \"url\": \"core/icon/android-chrome-192x192.png\",\n \"revision\": \"f94fc51b10052dcc8f91e04945a618b8\"\n },\n {\n \"url\": \"core/icon/android-chrome-256x256.png\",\n \"revision\": \"d80b94ad67334b9560e3cefaf8b04677\"\n },\n {\n \"url\": \"core/icon/android-chrome-36x36.png\",\n \"revision\": \"c3603b9735bb8397b0ac5351da9e94ed\"\n },\n {\n \"url\": \"core/icon/android-chrome-384x384.png\",\n \"revision\": \"45ea1a83b4d26a9224844344602ad69e\"\n },\n {\n \"url\": \"core/icon/android-chrome-48x48.png\",\n \"revision\": \"7ac44965ab502f17205e2d048d3e1580\"\n },\n {\n \"url\": \"core/icon/android-chrome-512x512.png\",\n \"revision\": \"d5a6869fb6a0d95e1c1398de5aac2278\"\n },\n {\n \"url\": \"core/icon/android-chrome-72x72.png\",\n \"revision\": \"a2b1197f79059981d8a19ba65ce73e6d\"\n },\n {\n \"url\": \"core/icon/android-chrome-96x96.png\",\n \"revision\": \"497e53074c189be631a8368192870f02\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-114x114-precomposed.png\",\n \"revision\": \"7b0f0af98c190974af01f605b117fc18\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-114x114.png\",\n \"revision\": \"7b0f0af98c190974af01f605b117fc18\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-120x120-precomposed.png\",\n \"revision\": \"c61a5acca54b104d7fe15fbc473796b5\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-120x120.png\",\n \"revision\": \"c61a5acca54b104d7fe15fbc473796b5\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-144x144-precomposed.png\",\n \"revision\": \"52d597709f2268268fd4063d34bd0ce7\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-144x144.png\",\n \"revision\": \"52d597709f2268268fd4063d34bd0ce7\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-152x152-precomposed.png\",\n \"revision\": \"8d46a8911ef72a562c97570d452f83ac\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-152x152.png\",\n \"revision\": \"8d46a8911ef72a562c97570d452f83ac\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-180x180-precomposed.png\",\n \"revision\": \"ced32ca13a0c404c96fc081dca7473eb\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-180x180.png\",\n \"revision\": \"ced32ca13a0c404c96fc081dca7473eb\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-57x57-precomposed.png\",\n \"revision\": \"b3e1d7fc553b33f086791c138d69bba1\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-57x57.png\",\n \"revision\": \"b3e1d7fc553b33f086791c138d69bba1\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-60x60-precomposed.png\",\n \"revision\": \"37be5e105d7a21fc8f3c437508dca233\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-60x60.png\",\n \"revision\": \"37be5e105d7a21fc8f3c437508dca233\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-72x72-precomposed.png\",\n \"revision\": \"819a43b6e5dc4991e399ff9b5d9b34b3\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-72x72.png\",\n \"revision\": \"819a43b6e5dc4991e399ff9b5d9b34b3\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-76x76-precomposed.png\",\n \"revision\": \"448377ba1760c41142b83ffd0fa47163\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-76x76.png\",\n \"revision\": \"448377ba1760c41142b83ffd0fa47163\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-precomposed.png\",\n \"revision\": \"ced32ca13a0c404c96fc081dca7473eb\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon.png\",\n \"revision\": \"ced32ca13a0c404c96fc081dca7473eb\"\n },\n {\n \"url\": \"core/icon/favicon-16x16.png\",\n \"revision\": \"f9e844d671e8e4b80174f643fe4dcf56\"\n },\n {\n \"url\": \"core/icon/favicon-32x32.png\",\n \"revision\": \"a6296dcec5e5a6449807f753d885507d\"\n },\n {\n \"url\": \"core/icon/favicon.ico\",\n \"revision\": \"ea57adfd4a62355e9f4c2b46ef21600f\"\n },\n {\n \"url\": \"core/icon/manifest.json\",\n \"revision\": \"dd34856295abeac04c8578a56413e5ca\"\n },\n {\n \"url\": \"core/icon/maskable_icon_x128.png\",\n \"revision\": \"cf5ea1b4cd68730505d36d59d3c8b04d\"\n },\n {\n \"url\": \"core/icon/maskable_icon_x192.png\",\n \"revision\": \"bd977cf9153d6ebb3e8d8f79b4f100b6\"\n },\n {\n \"url\": \"core/icon/maskable_icon_x384.png\",\n \"revision\": \"83527cdabf00617fb5b56df08a08b2a3\"\n },\n {\n \"url\": \"core/icon/maskable_icon_x48.png\",\n \"revision\": \"c521515543d0ccc88a843690484dfefe\"\n },\n {\n \"url\": \"core/icon/maskable_icon_x512.png\",\n \"revision\": \"499293f67750aa3b60815cb50d8e3b72\"\n },\n {\n \"url\": \"core/icon/maskable_icon_x72.png\",\n \"revision\": \"0603508e30979836bd82b2d95dc0b1f4\"\n },\n {\n \"url\": \"core/icon/maskable_icon_x96.png\",\n \"revision\": \"5269c50f0761a8aa50ae7201c28760c6\"\n },\n {\n \"url\": \"core/icon/maskable_icon.png\",\n \"revision\": \"94a300afab67912c4aa267989a725155\"\n },\n {\n \"url\": \"core/icon/mstile-144x144.png\",\n \"revision\": \"52d597709f2268268fd4063d34bd0ce7\"\n },\n {\n \"url\": \"core/icon/mstile-150x150.png\",\n \"revision\": \"4a6b569778e6b9d81aaee10df7c65284\"\n },\n {\n \"url\": \"core/icon/mstile-310x150.png\",\n \"revision\": \"7555e1760cd643b25105bfe4114eedfd\"\n },\n {\n \"url\": \"core/icon/mstile-310x310.png\",\n \"revision\": \"39af54c06ac44ebe9312f682ddd3602c\"\n },\n {\n \"url\": \"core/icon/mstile-70x70.png\",\n \"revision\": \"b2902f78c5c0344f8a0558a8f36b97bf\"\n },\n {\n \"url\": \"core/icon/resoc.png\",\n \"revision\": \"4b94395e0972a350b0727a20e3d06baa\"\n },\n {\n \"url\": \"core/icon/safari-pinned-tab.svg\",\n \"revision\": \"3a6f655958b21d384ae3dad886960d93\"\n },\n {\n \"url\": \"core/icon/screenshot1.webp\",\n \"revision\": \"426fd7165422ee95968d181411229d95\"\n },\n {\n \"url\": \"core/icon/screenshot2.webp\",\n \"revision\": \"87d56bb07395695fda9e04a30caf1672\"\n },\n {\n \"url\": \"core/icon/screenshot3.webp\",\n \"revision\": \"19ef6219d8f3ba14ebf25b72e07240d8\"\n },\n {\n \"url\": \"core/images/BackgroundImage/grey.webp\",\n \"revision\": \"44f3357b5a1f4d29ea3368de0f0f5279\"\n },\n {\n \"url\": \"core/images/BackgroundImage/homeBackground.webp\",\n \"revision\": \"fcb1240061569d6c355261f8d75a0f1b\"\n },\n {\n \"url\": \"core/images/dna.png\",\n \"revision\": \"d2778bf9fba581a4f4b8c2fca9abc9e8\"\n },\n {\n \"url\": \"core/images/dna.svg\",\n \"revision\": \"7cf969d66011a6cddf39163fa202fa51\"\n },\n {\n \"url\": \"core/images/EDITmd/002_SciGradePracticeGene.png\",\n \"revision\": \"a47cd17752a978f6e468985af11d5f3e\"\n },\n {\n \"url\": \"core/images/EDITmd/004_FeedbackPage.png\",\n \"revision\": \"e4ffddaa8a91c2c5547a89a4c6f47794\"\n },\n {\n \"url\": \"core/images/EDITmd/005_Algorithm.png\",\n \"revision\": \"934afcec1bad4f6cd1b39e799e6de5c2\"\n },\n {\n \"url\": \"core/images/logo_transparent-Dark.png\",\n \"revision\": \"cec8eede6a0b9a7e6da788d86edf6b37\"\n },\n {\n \"url\": \"core/images/logo_transparent-Dark.svg\",\n \"revision\": \"faef5b38e346a12888c11184c890292e\"\n },\n {\n \"url\": \"core/images/logo_transparent.png\",\n \"revision\": \"86746c9170e4cc9fccb363d0c54b3497\"\n },\n {\n \"url\": \"core/images/logo_transparent.svg\",\n \"revision\": \"d6abce258f77c56dfa6bb87765e3dfcf\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.css\",\n \"revision\": \"c223f119ec9dea026126fc19efa1cda4\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.min.css\",\n \"revision\": \"0bc3c052956530975a1406d6788a256e\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.rtl.css\",\n \"revision\": \"8122a112a175dcfc1ce51596916dec94\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.rtl.min.css\",\n \"revision\": \"76c20e07d9962cf2045b0a68e0c70172\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.css\",\n \"revision\": \"89f8de928a633a7258c08ba409dc5413\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.min.css\",\n \"revision\": \"05d3df42ebb67a65040216f50432680d\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.rtl.css\",\n \"revision\": \"654a6734347a7a718ac6529411270276\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.rtl.min.css\",\n \"revision\": \"c20056469f2d0f6bf6e88cc481d228ac\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.css\",\n \"revision\": \"66ab28268efbc0dafdb53791d41e755e\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.min.css\",\n \"revision\": \"08aded6a77f986e24299bf9a00f3791d\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.rtl.css\",\n \"revision\": \"dfe0f0007ab21ab80c134ef0777af72f\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.rtl.min.css\",\n \"revision\": \"8479d3eb9eb3e42703c2a46bd839281c\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap.css\",\n \"revision\": \"e130b5189b00dbf9549803614a0c35d0\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap.min.css\",\n \"revision\": \"1bcf7ee45ab9975f9b9eb016fb96771a\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap.rtl.css\",\n \"revision\": \"554e153e05fe7b5c3cfd037b73997215\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap.rtl.min.css\",\n \"revision\": \"e9066195b42ddded5976a1c61cd82e2a\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/js/bootstrap.bundle.js\",\n \"revision\": \"6cf21db19808a582d229175c96f32bfc\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/js/bootstrap.bundle.min.js\",\n \"revision\": \"9977a948cfde2c156160bf47d4d3dd5e\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/js/bootstrap.esm.js\",\n \"revision\": \"2de4dc4acece93659c5cbd7abeb21566\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/js/bootstrap.esm.min.js\",\n \"revision\": \"a740e6c7cc66aefbfa4b2bcb27a80c2f\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/js/bootstrap.js\",\n \"revision\": \"ccd5967383a08b1b42f8dec4fabfa53e\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/js/bootstrap.min.js\",\n \"revision\": \"eea75f8c53d120079f003f36ee698c5c\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Fonts/MaterialIcon.css\",\n \"revision\": \"85ee0e1867e1b9c1b01ed7e31023ae1f\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/jQuery/jquery.min.js\",\n \"revision\": \"762e32e4cbf687a7fe34faf9bb0e511e\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/tabletoCSV/bower.json\",\n \"revision\": \"0786a5142fef978932727a42dcf1f094\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/tabletoCSV/dist/jquery.tabletoCSV.js\",\n \"revision\": \"38e3e2fcaff8b3a33f2e3bf01190e845\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/tabletoCSV/example.html\",\n \"revision\": \"b278d32f287f55a0dcb008ead1d7109e\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/tabletoCSV/gulpfile.js\",\n \"revision\": \"23f0528a75ea40b302fd34d1effe4334\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/tabletoCSV/jquery.tabletoCSV.js\",\n \"revision\": \"3ab5e2ace2ab26ca4df0a1f06267a978\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/tabletoCSV/package.json\",\n \"revision\": \"45f34680e60abfca81500be631358328\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/tabletoCSV/README.md\",\n \"revision\": \"1375c24a3db5a713665c04e433244bf0\"\n },\n {\n \"url\": \"core/scripts/crispr_scripts.js\",\n \"revision\": \"f34a277afa6473242860c4d6dd843f83\"\n },\n {\n \"url\": \"core/scripts/crispr_scripts.min.js\",\n \"revision\": \"59b7cd41725dd02df07b7f2801cdff22\"\n },\n {\n \"url\": \"core/scripts/login.js\",\n \"revision\": \"51d2323c07ba325cf472451668c6b2ac\"\n },\n {\n \"url\": \"core/scripts/login.min.js\",\n \"revision\": \"a1940d1ab02755d049e02507fb0b8ba7\"\n },\n {\n \"url\": \"core/styling/style.css\",\n \"revision\": \"ac7a0fac8e80497aa8332e4e9ae0d3d5\"\n },\n {\n \"url\": \"core/styling/style.min.css\",\n \"revision\": \"65a608b7d010ea2b87bb7ea7584ab875\"\n },\n {\n \"url\": \"core/systemrun.html\",\n \"revision\": \"3a598442c432e1a791a9148ddda6bbd1\"\n }\n], {});\n\n\n\n\nworkbox_routing_registerRoute(/\\.(?:png|jpg|jpeg|webp|ico|svg)$/, new workbox_strategies_CacheFirst({ \"cacheName\":\"images\", plugins: [new workbox_expiration_ExpirationPlugin({ maxEntries: 10 })] }), 'GET');\nworkbox_routing_registerRoute(/\\.html$/, new workbox_strategies_NetworkFirst({ \"cacheName\":\"html\",\"networkTimeoutSeconds\":10, plugins: [new workbox_expiration_ExpirationPlugin({ maxEntries: 50 }), new workbox_cacheable_response_CacheableResponsePlugin({ statuses: [ 0, 200 ] })] }), 'GET');\nworkbox_routing_registerRoute(/\\.(?:css|js)$/, new workbox_strategies_NetworkFirst({ \"cacheName\":\"assets\", plugins: [new workbox_expiration_ExpirationPlugin({ maxEntries: 50 }), new workbox_cacheable_response_CacheableResponsePlugin({ statuses: [ 0, 200 ] })] }), 'GET');\n\n\n\n\n"],"names":["self","addEventListener","event","data","type","skipWaiting","workbox_precaching_precacheAndRoute","url","revision","workbox_routing_registerRoute","workbox_strategies_CacheFirst","cacheName","plugins","workbox_expiration_ExpirationPlugin","maxEntries","workbox_strategies_NetworkFirst","networkTimeoutSeconds","workbox_cacheable_response_CacheableResponsePlugin","statuses"],"mappings":"0nBAwBAA,KAAKC,iBAAiB,WAAYC,IAC5BA,EAAMC,MAA4B,iBAApBD,EAAMC,KAAKC,MAC3BJ,KAAKK,aACP,IAWFC,EAAAA,iBAAoC,CAClC,CACEC,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,8CACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,6CACPC,SAAY,oCAEd,CACED,IAAO,sDACPC,SAAY,oCAEd,CACED,IAAO,wCACPC,SAAY,oCAEd,CACED,IAAO,6CACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,6CACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,4CACPC,SAAY,oCAEd,CACED,IAAO,0BACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,qCACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,qCACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,qCACPC,SAAY,oCAEd,CACED,IAAO,qCACPC,SAAY,oCAEd,CACED,IAAO,qDACPC,SAAY,oCAEd,CACED,IAAO,yCACPC,SAAY,oCAEd,CACED,IAAO,qDACPC,SAAY,oCAEd,CACED,IAAO,yCACPC,SAAY,oCAEd,CACED,IAAO,qDACPC,SAAY,oCAEd,CACED,IAAO,yCACPC,SAAY,oCAEd,CACED,IAAO,qDACPC,SAAY,oCAEd,CACED,IAAO,yCACPC,SAAY,oCAEd,CACED,IAAO,qDACPC,SAAY,oCAEd,CACED,IAAO,yCACPC,SAAY,oCAEd,CACED,IAAO,mDACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,mDACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,mDACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,mDACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,6CACPC,SAAY,oCAEd,CACED,IAAO,iCACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,wBACPC,SAAY,oCAEd,CACED,IAAO,0BACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,+BACPC,SAAY,oCAEd,CACED,IAAO,+BACPC,SAAY,oCAEd,CACED,IAAO,+BACPC,SAAY,oCAEd,CACED,IAAO,+BACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,wCACPC,SAAY,oCAEd,CACED,IAAO,kDACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,kDACPC,SAAY,oCAEd,CACED,IAAO,0CACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,wCACPC,SAAY,oCAEd,CACED,IAAO,wCACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,gEACPC,SAAY,oCAEd,CACED,IAAO,oEACPC,SAAY,oCAEd,CACED,IAAO,oEACPC,SAAY,oCAEd,CACED,IAAO,wEACPC,SAAY,oCAEd,CACED,IAAO,kEACPC,SAAY,oCAEd,CACED,IAAO,sEACPC,SAAY,oCAEd,CACED,IAAO,sEACPC,SAAY,oCAEd,CACED,IAAO,0EACPC,SAAY,oCAEd,CACED,IAAO,qEACPC,SAAY,oCAEd,CACED,IAAO,yEACPC,SAAY,oCAEd,CACED,IAAO,yEACPC,SAAY,oCAEd,CACED,IAAO,6EACPC,SAAY,oCAEd,CACED,IAAO,2DACPC,SAAY,oCAEd,CACED,IAAO,+DACPC,SAAY,oCAEd,CACED,IAAO,+DACPC,SAAY,oCAEd,CACED,IAAO,mEACPC,SAAY,oCAEd,CACED,IAAO,gEACPC,SAAY,oCAEd,CACED,IAAO,oEACPC,SAAY,oCAEd,CACED,IAAO,6DACPC,SAAY,oCAEd,CACED,IAAO,iEACPC,SAAY,oCAEd,CACED,IAAO,yDACPC,SAAY,oCAEd,CACED,IAAO,6DACPC,SAAY,oCAEd,CACED,IAAO,sDACPC,SAAY,oCAEd,CACED,IAAO,oDACPC,SAAY,oCAEd,CACED,IAAO,qDACPC,SAAY,oCAEd,CACED,IAAO,oEACPC,SAAY,oCAEd,CACED,IAAO,uDACPC,SAAY,oCAEd,CACED,IAAO,sDACPC,SAAY,oCAEd,CACED,IAAO,+DACPC,SAAY,oCAEd,CACED,IAAO,uDACPC,SAAY,oCAEd,CACED,IAAO,oDACPC,SAAY,oCAEd,CACED,IAAO,iCACPC,SAAY,oCAEd,CACED,IAAO,qCACPC,SAAY,oCAEd,CACED,IAAO,wBACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,yBACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,qCAEb,CAAE,GAKLC,EAAAA,cAA8B,mCAAoC,IAAIC,aAA8B,CAAEC,UAAY,SAAUC,QAAS,CAAC,IAAIC,mBAAoC,CAAEC,WAAY,QAAW,OACvML,EAAAA,cAA8B,UAAW,IAAIM,eAAgC,CAAEJ,UAAY,OAAOK,sBAAwB,GAAIJ,QAAS,CAAC,IAAIC,mBAAoC,CAAEC,WAAY,KAAO,IAAIG,EAAAA,wBAAmD,CAAEC,SAAU,CAAE,EAAG,UAAc,OAC3RT,EAAAA,cAA8B,gBAAiB,IAAIM,eAAgC,CAAEJ,UAAY,SAAUC,QAAS,CAAC,IAAIC,mBAAoC,CAAEC,WAAY,KAAO,IAAIG,EAAAA,wBAAmD,CAAEC,SAAU,CAAE,EAAG,UAAc"} \ No newline at end of file +{"version":3,"file":"sw.js","sources":["../../../../../private/var/folders/x_/xp67pwcd45d2bwd0h9y9cpbw0000gp/T/1764d5872f2a6373fcc69d4be0b40f76/sw.js"],"sourcesContent":["import {registerRoute as workbox_routing_registerRoute} from '/Users/joohyun/Documents/code/SciGrade/node_modules/workbox-routing/registerRoute.mjs';\nimport {ExpirationPlugin as workbox_expiration_ExpirationPlugin} from '/Users/joohyun/Documents/code/SciGrade/node_modules/workbox-expiration/ExpirationPlugin.mjs';\nimport {CacheFirst as workbox_strategies_CacheFirst} from '/Users/joohyun/Documents/code/SciGrade/node_modules/workbox-strategies/CacheFirst.mjs';\nimport {CacheableResponsePlugin as workbox_cacheable_response_CacheableResponsePlugin} from '/Users/joohyun/Documents/code/SciGrade/node_modules/workbox-cacheable-response/CacheableResponsePlugin.mjs';\nimport {NetworkFirst as workbox_strategies_NetworkFirst} from '/Users/joohyun/Documents/code/SciGrade/node_modules/workbox-strategies/NetworkFirst.mjs';\nimport {precacheAndRoute as workbox_precaching_precacheAndRoute} from '/Users/joohyun/Documents/code/SciGrade/node_modules/workbox-precaching/precacheAndRoute.mjs';/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n\n\n\n\n\n\n\nself.addEventListener('message', (event) => {\n if (event.data && event.data.type === 'SKIP_WAITING') {\n self.skipWaiting();\n }\n});\n\n\n\n\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\nworkbox_precaching_precacheAndRoute([\n {\n \"url\": \"core/systemrun.html\",\n \"revision\": \"61ec3960f05098c97d298ab90f475771\"\n },\n {\n \"url\": \"core/styling/style.min.css\",\n \"revision\": \"65a608b7d010ea2b87bb7ea7584ab875\"\n },\n {\n \"url\": \"core/styling/style.css\",\n \"revision\": \"b67cbfd3995e07bba50358eaa64009fe\"\n },\n {\n \"url\": \"core/scripts/runtime.min.js\",\n \"revision\": \"6e4641db4ea78c758c56312b1cf2379f\"\n },\n {\n \"url\": \"core/scripts/runtime.js\",\n \"revision\": \"1767d36bb43cfd630a1c01fa2c27f3a9\"\n },\n {\n \"url\": \"core/scripts/crispr_scripts.min.js\",\n \"revision\": \"96fa88586a8017e47d57b94fd287ff3c\"\n },\n {\n \"url\": \"core/scripts/crispr_scripts.js\",\n \"revision\": \"99224226d8d67fb8f654a917e9a2a29d\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/tabletoCSV/package.json\",\n \"revision\": \"ef0258ebdf3f9d244d7d38c4c35ec723\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/tabletoCSV/jquery.tabletoCSV.js\",\n \"revision\": \"1757e6cdbbbe5f5a1c330e2f182e24f2\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/tabletoCSV/gulpfile.js\",\n \"revision\": \"c93880a7ac368dcd9b51d547948cbf8a\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/tabletoCSV/example.html\",\n \"revision\": \"9b17ab0cdbf7acd33dfcfff87c410d2b\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/tabletoCSV/bower.json\",\n \"revision\": \"79787e672bb7dc33e2a2e0bdc8467a38\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/tabletoCSV/README.md\",\n \"revision\": \"d581efd7f3de48bdf77206307df41034\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/tabletoCSV/dist/jquery.tabletoCSV.js\",\n \"revision\": \"38e3e2fcaff8b3a33f2e3bf01190e845\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/jQuery/jquery.min.js\",\n \"revision\": \"a8e7cabd4d49dfaf0146678ee147dfc5\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Fonts/MaterialIcon.css\",\n \"revision\": \"c1cbea39f07b551807abd9967b809ffd\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/js/bootstrap.min.js\",\n \"revision\": \"a92b3364fb0a7349f2a1c31a7ad5ed5e\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/js/bootstrap.js\",\n \"revision\": \"52de67605c3d156f4958c83fa9cfc8d9\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/js/bootstrap.esm.min.js\",\n \"revision\": \"6ccf144f123da79e62d5e46a06f133b9\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/js/bootstrap.esm.js\",\n \"revision\": \"32e42b38a15f27b01f79bb13aad53714\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/js/bootstrap.bundle.min.js\",\n \"revision\": \"5cc1b73e70520fa84b1846afe0ec8fb6\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/js/bootstrap.bundle.js\",\n \"revision\": \"7d2c412531f05f39e97195f9b48a53dc\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap.rtl.min.css\",\n \"revision\": \"53aa521e55523d4f35610e568c5991fd\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap.rtl.css\",\n \"revision\": \"f50b588ab5764c08bb2e57da6bbe47c7\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap.min.css\",\n \"revision\": \"1b1cb0e2be9a21f091a87691f20c6300\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap.css\",\n \"revision\": \"32f9388da564ecf3f4f129021d3736a8\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.rtl.min.css\",\n \"revision\": \"1282e3b4ef0226816da34cd92a7ae69b\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.rtl.css\",\n \"revision\": \"95616dd04a2044a59dbefbc48c050891\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.min.css\",\n \"revision\": \"20d013574c70b2032e408e8f81a8fec9\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-utilities.css\",\n \"revision\": \"2609642eaa62f29a2275e0ced5a098fe\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.rtl.min.css\",\n \"revision\": \"c01f229a610e6f96bdb57fd287f48fc9\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.rtl.css\",\n \"revision\": \"0b4f19eb4846adc5a866b80c81bda434\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.min.css\",\n \"revision\": \"7c821536802bd2fa35701c22f2e96d36\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-reboot.css\",\n \"revision\": \"cb9d8d28a86d39db9b43030a8248b766\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.rtl.min.css\",\n \"revision\": \"fc7e1eb57c409c379d21a8001a0f9b33\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.rtl.css\",\n \"revision\": \"93f428651ed8b87b9986050f06e03cb2\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.min.css\",\n \"revision\": \"895b9494c7178ac12ad9c56d81305665\"\n },\n {\n \"url\": \"core/scripts/APIandLibraries/Bootstrap/css/bootstrap-grid.css\",\n \"revision\": \"4dd0ce2aa69225d64aacbded692fe756\"\n },\n {\n \"url\": \"core/images/logo_transparent.svg\",\n \"revision\": \"d6abce258f77c56dfa6bb87765e3dfcf\"\n },\n {\n \"url\": \"core/images/logo_transparent.png\",\n \"revision\": \"86746c9170e4cc9fccb363d0c54b3497\"\n },\n {\n \"url\": \"core/images/logo_transparent-Dark.svg\",\n \"revision\": \"faef5b38e346a12888c11184c890292e\"\n },\n {\n \"url\": \"core/images/logo_transparent-Dark.png\",\n \"revision\": \"cec8eede6a0b9a7e6da788d86edf6b37\"\n },\n {\n \"url\": \"core/images/dna.svg\",\n \"revision\": \"7cf969d66011a6cddf39163fa202fa51\"\n },\n {\n \"url\": \"core/images/dna.png\",\n \"revision\": \"d2778bf9fba581a4f4b8c2fca9abc9e8\"\n },\n {\n \"url\": \"core/images/EDITmd/005_Algorithm.png\",\n \"revision\": \"934afcec1bad4f6cd1b39e799e6de5c2\"\n },\n {\n \"url\": \"core/images/EDITmd/004_FeedbackPage.png\",\n \"revision\": \"e4ffddaa8a91c2c5547a89a4c6f47794\"\n },\n {\n \"url\": \"core/images/EDITmd/002_SciGradePracticeGene.png\",\n \"revision\": \"a47cd17752a978f6e468985af11d5f3e\"\n },\n {\n \"url\": \"core/images/BackgroundImage/homeBackground.webp\",\n \"revision\": \"fcb1240061569d6c355261f8d75a0f1b\"\n },\n {\n \"url\": \"core/images/BackgroundImage/grey.webp\",\n \"revision\": \"44f3357b5a1f4d29ea3368de0f0f5279\"\n },\n {\n \"url\": \"core/icon/screenshot3.webp\",\n \"revision\": \"19ef6219d8f3ba14ebf25b72e07240d8\"\n },\n {\n \"url\": \"core/icon/screenshot2.webp\",\n \"revision\": \"87d56bb07395695fda9e04a30caf1672\"\n },\n {\n \"url\": \"core/icon/screenshot1.webp\",\n \"revision\": \"426fd7165422ee95968d181411229d95\"\n },\n {\n \"url\": \"core/icon/safari-pinned-tab.svg\",\n \"revision\": \"3a6f655958b21d384ae3dad886960d93\"\n },\n {\n \"url\": \"core/icon/resoc.png\",\n \"revision\": \"4b94395e0972a350b0727a20e3d06baa\"\n },\n {\n \"url\": \"core/icon/mstile-70x70.png\",\n \"revision\": \"b2902f78c5c0344f8a0558a8f36b97bf\"\n },\n {\n \"url\": \"core/icon/mstile-310x310.png\",\n \"revision\": \"39af54c06ac44ebe9312f682ddd3602c\"\n },\n {\n \"url\": \"core/icon/mstile-310x150.png\",\n \"revision\": \"7555e1760cd643b25105bfe4114eedfd\"\n },\n {\n \"url\": \"core/icon/mstile-150x150.png\",\n \"revision\": \"4a6b569778e6b9d81aaee10df7c65284\"\n },\n {\n \"url\": \"core/icon/mstile-144x144.png\",\n \"revision\": \"52d597709f2268268fd4063d34bd0ce7\"\n },\n {\n \"url\": \"core/icon/maskable_icon_x96.png\",\n \"revision\": \"5269c50f0761a8aa50ae7201c28760c6\"\n },\n {\n \"url\": \"core/icon/maskable_icon_x72.png\",\n \"revision\": \"0603508e30979836bd82b2d95dc0b1f4\"\n },\n {\n \"url\": \"core/icon/maskable_icon_x512.png\",\n \"revision\": \"499293f67750aa3b60815cb50d8e3b72\"\n },\n {\n \"url\": \"core/icon/maskable_icon_x48.png\",\n \"revision\": \"c521515543d0ccc88a843690484dfefe\"\n },\n {\n \"url\": \"core/icon/maskable_icon_x384.png\",\n \"revision\": \"83527cdabf00617fb5b56df08a08b2a3\"\n },\n {\n \"url\": \"core/icon/maskable_icon_x192.png\",\n \"revision\": \"bd977cf9153d6ebb3e8d8f79b4f100b6\"\n },\n {\n \"url\": \"core/icon/maskable_icon_x128.png\",\n \"revision\": \"cf5ea1b4cd68730505d36d59d3c8b04d\"\n },\n {\n \"url\": \"core/icon/maskable_icon.png\",\n \"revision\": \"94a300afab67912c4aa267989a725155\"\n },\n {\n \"url\": \"core/icon/manifest.json\",\n \"revision\": \"ec78f1b4aaf3e8be6d7db11ef45fbf72\"\n },\n {\n \"url\": \"core/icon/favicon.ico\",\n \"revision\": \"ea57adfd4a62355e9f4c2b46ef21600f\"\n },\n {\n \"url\": \"core/icon/favicon-32x32.png\",\n \"revision\": \"a6296dcec5e5a6449807f753d885507d\"\n },\n {\n \"url\": \"core/icon/favicon-16x16.png\",\n \"revision\": \"f9e844d671e8e4b80174f643fe4dcf56\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon.png\",\n \"revision\": \"ced32ca13a0c404c96fc081dca7473eb\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-precomposed.png\",\n \"revision\": \"ced32ca13a0c404c96fc081dca7473eb\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-76x76.png\",\n \"revision\": \"448377ba1760c41142b83ffd0fa47163\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-76x76-precomposed.png\",\n \"revision\": \"448377ba1760c41142b83ffd0fa47163\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-72x72.png\",\n \"revision\": \"819a43b6e5dc4991e399ff9b5d9b34b3\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-72x72-precomposed.png\",\n \"revision\": \"819a43b6e5dc4991e399ff9b5d9b34b3\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-60x60.png\",\n \"revision\": \"37be5e105d7a21fc8f3c437508dca233\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-60x60-precomposed.png\",\n \"revision\": \"37be5e105d7a21fc8f3c437508dca233\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-57x57.png\",\n \"revision\": \"b3e1d7fc553b33f086791c138d69bba1\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-57x57-precomposed.png\",\n \"revision\": \"b3e1d7fc553b33f086791c138d69bba1\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-180x180.png\",\n \"revision\": \"ced32ca13a0c404c96fc081dca7473eb\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-180x180-precomposed.png\",\n \"revision\": \"ced32ca13a0c404c96fc081dca7473eb\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-152x152.png\",\n \"revision\": \"8d46a8911ef72a562c97570d452f83ac\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-152x152-precomposed.png\",\n \"revision\": \"8d46a8911ef72a562c97570d452f83ac\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-144x144.png\",\n \"revision\": \"52d597709f2268268fd4063d34bd0ce7\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-144x144-precomposed.png\",\n \"revision\": \"52d597709f2268268fd4063d34bd0ce7\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-120x120.png\",\n \"revision\": \"c61a5acca54b104d7fe15fbc473796b5\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-120x120-precomposed.png\",\n \"revision\": \"c61a5acca54b104d7fe15fbc473796b5\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-114x114.png\",\n \"revision\": \"7b0f0af98c190974af01f605b117fc18\"\n },\n {\n \"url\": \"core/icon/apple-touch-icon-114x114-precomposed.png\",\n \"revision\": \"7b0f0af98c190974af01f605b117fc18\"\n },\n {\n \"url\": \"core/icon/android-chrome-96x96.png\",\n \"revision\": \"497e53074c189be631a8368192870f02\"\n },\n {\n \"url\": \"core/icon/android-chrome-72x72.png\",\n \"revision\": \"a2b1197f79059981d8a19ba65ce73e6d\"\n },\n {\n \"url\": \"core/icon/android-chrome-512x512.png\",\n \"revision\": \"d5a6869fb6a0d95e1c1398de5aac2278\"\n },\n {\n \"url\": \"core/icon/android-chrome-48x48.png\",\n \"revision\": \"7ac44965ab502f17205e2d048d3e1580\"\n },\n {\n \"url\": \"core/icon/android-chrome-384x384.png\",\n \"revision\": \"45ea1a83b4d26a9224844344602ad69e\"\n },\n {\n \"url\": \"core/icon/android-chrome-36x36.png\",\n \"revision\": \"c3603b9735bb8397b0ac5351da9e94ed\"\n },\n {\n \"url\": \"core/icon/android-chrome-256x256.png\",\n \"revision\": \"d80b94ad67334b9560e3cefaf8b04677\"\n },\n {\n \"url\": \"core/icon/android-chrome-192x192.png\",\n \"revision\": \"f94fc51b10052dcc8f91e04945a618b8\"\n },\n {\n \"url\": \"core/icon/android-chrome-144x144.png\",\n \"revision\": \"859cb36c4d6354a1d76e8d9692fdd88d\"\n },\n {\n \"url\": \"core/data/Benchling_gRNA_Outputs.json\",\n \"revision\": \"515b0830b197e126a294fb15cd6d8e89\"\n },\n {\n \"url\": \"core/data/eBFP/eBFP.fasta\",\n \"revision\": \"a30dc131549dc5fbbaa59128830ca49c\"\n },\n {\n \"url\": \"core/data/eBFP/Benchling_gRNA_outputs.xlsx\",\n \"revision\": \"cb29f7f64c8c0f44cfe58c3504af0d2b\"\n },\n {\n \"url\": \"core/data/HBB/HBB.fasta\",\n \"revision\": \"0314a959f59b730536fddec088d4a6c5\"\n },\n {\n \"url\": \"core/data/HBB/Benchling_gRNA_Outputs.xlsx\",\n \"revision\": \"571ef05c0a5fc985423f08ab13e3fc71\"\n },\n {\n \"url\": \"core/data/CCR5/CCR5.fasta\",\n \"revision\": \"60232b31119c5c0dfd9c59216f46928a\"\n },\n {\n \"url\": \"core/data/CCR5/Benchling_gRNA_Outputs.xlsx\",\n \"revision\": \"d204b7de85c0def6a40789a3104dd975\"\n },\n {\n \"url\": \"core/data/Background_info/gene_background_info.json\",\n \"revision\": \"ebeaca222ceb2e2354f6b2bacb6d0b26\"\n },\n {\n \"url\": \"core/data/APOE/Benchling_gRNA_Outputs.xlsx\",\n \"revision\": \"d728b00ab3559732abc114c43893ba7e\"\n },\n {\n \"url\": \"core/data/APOE/APOE.fasta\",\n \"revision\": \"9955beb43e9fd5a6e506cb9853ef13f2\"\n },\n {\n \"url\": \"core/data/ACTN3/Benchling_gRNA_Outputs.xlsx\",\n \"revision\": \"9d9a05ae5858671f89b87ae185b4917b\"\n },\n {\n \"url\": \"core/data/ACTN3/ACTN3.fasta\",\n \"revision\": \"a5ad42f7acbecbc28bed864b8377c5d4\"\n }\n], {});\n\n\n\n\nworkbox_routing_registerRoute(/\\.(?:png|jpg|jpeg|webp|ico|svg)$/, new workbox_strategies_CacheFirst({ \"cacheName\":\"images\", plugins: [new workbox_expiration_ExpirationPlugin({ maxEntries: 10 })] }), 'GET');\nworkbox_routing_registerRoute(/\\.html$/, new workbox_strategies_NetworkFirst({ \"cacheName\":\"html\",\"networkTimeoutSeconds\":10, plugins: [new workbox_expiration_ExpirationPlugin({ maxEntries: 50 }), new workbox_cacheable_response_CacheableResponsePlugin({ statuses: [ 0, 200 ] })] }), 'GET');\nworkbox_routing_registerRoute(/\\.(?:css|js)$/, new workbox_strategies_NetworkFirst({ \"cacheName\":\"assets\", plugins: [new workbox_expiration_ExpirationPlugin({ maxEntries: 50 }), new workbox_cacheable_response_CacheableResponsePlugin({ statuses: [ 0, 200 ] })] }), 'GET');\n\n\n\n\n"],"names":["self","addEventListener","event","data","type","skipWaiting","workbox_precaching_precacheAndRoute","url","revision","workbox_routing_registerRoute","workbox_strategies_CacheFirst","cacheName","plugins","workbox_expiration_ExpirationPlugin","maxEntries","workbox_strategies_NetworkFirst","networkTimeoutSeconds","workbox_cacheable_response_CacheableResponsePlugin","statuses"],"mappings":"inBAwBAA,KAAKC,iBAAiB,UAAYC,IAC5BA,EAAMC,MAA4B,iBAApBD,EAAMC,KAAKC,MAC3BJ,KAAKK,gBAYTC,EAAAA,iBAAoC,CAClC,CACEC,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,yBACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,0BACPC,SAAY,oCAEd,CACED,IAAO,qCACPC,SAAY,oCAEd,CACED,IAAO,iCACPC,SAAY,oCAEd,CACED,IAAO,uDACPC,SAAY,oCAEd,CACED,IAAO,+DACPC,SAAY,oCAEd,CACED,IAAO,sDACPC,SAAY,oCAEd,CACED,IAAO,uDACPC,SAAY,oCAEd,CACED,IAAO,qDACPC,SAAY,oCAEd,CACED,IAAO,oDACPC,SAAY,oCAEd,CACED,IAAO,oEACPC,SAAY,oCAEd,CACED,IAAO,oDACPC,SAAY,oCAEd,CACED,IAAO,sDACPC,SAAY,oCAEd,CACED,IAAO,6DACPC,SAAY,oCAEd,CACED,IAAO,yDACPC,SAAY,oCAEd,CACED,IAAO,iEACPC,SAAY,oCAEd,CACED,IAAO,6DACPC,SAAY,oCAEd,CACED,IAAO,oEACPC,SAAY,oCAEd,CACED,IAAO,gEACPC,SAAY,oCAEd,CACED,IAAO,mEACPC,SAAY,oCAEd,CACED,IAAO,+DACPC,SAAY,oCAEd,CACED,IAAO,+DACPC,SAAY,oCAEd,CACED,IAAO,2DACPC,SAAY,oCAEd,CACED,IAAO,6EACPC,SAAY,oCAEd,CACED,IAAO,yEACPC,SAAY,oCAEd,CACED,IAAO,yEACPC,SAAY,oCAEd,CACED,IAAO,qEACPC,SAAY,oCAEd,CACED,IAAO,0EACPC,SAAY,oCAEd,CACED,IAAO,sEACPC,SAAY,oCAEd,CACED,IAAO,sEACPC,SAAY,oCAEd,CACED,IAAO,kEACPC,SAAY,oCAEd,CACED,IAAO,wEACPC,SAAY,oCAEd,CACED,IAAO,oEACPC,SAAY,oCAEd,CACED,IAAO,oEACPC,SAAY,oCAEd,CACED,IAAO,gEACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,wCACPC,SAAY,oCAEd,CACED,IAAO,wCACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,0CACPC,SAAY,oCAEd,CACED,IAAO,kDACPC,SAAY,oCAEd,CACED,IAAO,kDACPC,SAAY,oCAEd,CACED,IAAO,wCACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,sBACPC,SAAY,oCAEd,CACED,IAAO,6BACPC,SAAY,oCAEd,CACED,IAAO,+BACPC,SAAY,oCAEd,CACED,IAAO,+BACPC,SAAY,oCAEd,CACED,IAAO,+BACPC,SAAY,oCAEd,CACED,IAAO,+BACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,kCACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,mCACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,0BACPC,SAAY,oCAEd,CACED,IAAO,wBACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,oCAEd,CACED,IAAO,iCACPC,SAAY,oCAEd,CACED,IAAO,6CACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,mDACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,mDACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,mDACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,mDACPC,SAAY,oCAEd,CACED,IAAO,yCACPC,SAAY,oCAEd,CACED,IAAO,qDACPC,SAAY,oCAEd,CACED,IAAO,yCACPC,SAAY,oCAEd,CACED,IAAO,qDACPC,SAAY,oCAEd,CACED,IAAO,yCACPC,SAAY,oCAEd,CACED,IAAO,qDACPC,SAAY,oCAEd,CACED,IAAO,yCACPC,SAAY,oCAEd,CACED,IAAO,qDACPC,SAAY,oCAEd,CACED,IAAO,yCACPC,SAAY,oCAEd,CACED,IAAO,qDACPC,SAAY,oCAEd,CACED,IAAO,qCACPC,SAAY,oCAEd,CACED,IAAO,qCACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,qCACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,qCACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,uCACPC,SAAY,oCAEd,CACED,IAAO,wCACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,6CACPC,SAAY,oCAEd,CACED,IAAO,0BACPC,SAAY,oCAEd,CACED,IAAO,4CACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,6CACPC,SAAY,oCAEd,CACED,IAAO,sDACPC,SAAY,oCAEd,CACED,IAAO,6CACPC,SAAY,oCAEd,CACED,IAAO,4BACPC,SAAY,oCAEd,CACED,IAAO,8CACPC,SAAY,oCAEd,CACED,IAAO,8BACPC,SAAY,qCAEb,CAAE,GAKLC,EAAAA,cAA8B,mCAAoC,IAAIC,aAA8B,CAAEC,UAAY,SAAUC,QAAS,CAAC,IAAIC,mBAAoC,CAAEC,WAAY,QAAW,OACvML,EAAAA,cAA8B,UAAW,IAAIM,eAAgC,CAAEJ,UAAY,OAAOK,sBAAwB,GAAIJ,QAAS,CAAC,IAAIC,mBAAoC,CAAEC,WAAY,KAAO,IAAIG,EAAAA,wBAAmD,CAAEC,SAAU,CAAE,EAAG,UAAc,OAC3RT,EAAAA,cAA8B,gBAAiB,IAAIM,eAAgC,CAAEJ,UAAY,SAAUC,QAAS,CAAC,IAAIC,mBAAoC,CAAEC,WAAY,KAAO,IAAIG,EAAAA,wBAAmD,CAAEC,SAAU,CAAE,EAAG,UAAc"} \ No newline at end of file diff --git a/core/scripts/serviceWorker/workbox-813eeb66.js b/core/scripts/serviceWorker/workbox-813eeb66.js new file mode 100644 index 0000000..fa56bc9 --- /dev/null +++ b/core/scripts/serviceWorker/workbox-813eeb66.js @@ -0,0 +1,2 @@ +define(["exports"],function(t){"use strict";try{self["workbox:core:7.3.0"]&&_();}catch(t){}const e=(t,...e)=>{let s=t;return e.length>0&&(s+=` :: ${JSON.stringify(e)}`),s;};class s extends Error{constructor(t,s){super(e(t,s)),this.name=t,this.details=s;}}try{self["workbox:routing:7.3.0"]&&_();}catch(t){}const n=t=>t&&"object"==typeof t?t:{handle:t};class i{constructor(t,e,s="GET"){this.handler=n(e),this.match=t,this.method=s;}setCatchHandler(t){this.catchHandler=n(t);}}class r extends i{constructor(t,e,s){super(({url:e})=>{const s=t.exec(e.href);if(s&&(e.origin===location.origin||0===s.index))return s.slice(1);},e,s);}}class a{constructor(){this.t=new Map,this.i=new Map;}get routes(){return this.t;}addFetchListener(){self.addEventListener("fetch",t=>{const{request:e}=t,s=this.handleRequest({request:e,event:t});s&&t.respondWith(s);});}addCacheListener(){self.addEventListener("message",t=>{if(t.data&&"CACHE_URLS"===t.data.type){const{payload:e}=t.data,s=Promise.all(e.urlsToCache.map(e=>{"string"==typeof e&&(e=[e]);const s=new Request(...e);return this.handleRequest({request:s,event:t});}));t.waitUntil(s),t.ports&&t.ports[0]&&s.then(()=>t.ports[0].postMessage(!0));}});}handleRequest({request:t,event:e}){const s=new URL(t.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:i,route:r}=this.findMatchingRoute({event:e,request:t,sameOrigin:n,url:s});let a=r&&r.handler;const o=t.method;if(!a&&this.i.has(o)&&(a=this.i.get(o)),!a)return;let c;try{c=a.handle({url:s,request:t,event:e,params:i});}catch(t){c=Promise.reject(t);}const h=r&&r.catchHandler;return c instanceof Promise&&(this.o||h)&&(c=c.catch(async n=>{if(h)try{return await h.handle({url:s,request:t,event:e,params:i});}catch(t){t instanceof Error&&(n=t);}if(this.o)return this.o.handle({url:s,request:t,event:e});throw n;})),c;}findMatchingRoute({url:t,sameOrigin:e,request:s,event:n}){const i=this.t.get(s.method)||[];for(const r of i){let i;const a=r.match({url:t,sameOrigin:e,request:s,event:n});if(a)return i=a,(Array.isArray(i)&&0===i.length||a.constructor===Object&&0===Object.keys(a).length||"boolean"==typeof a)&&(i=void 0),{route:r,params:i};}return{};}setDefaultHandler(t,e="GET"){this.i.set(e,n(t));}setCatchHandler(t){this.o=n(t);}registerRoute(t){this.t.has(t.method)||this.t.set(t.method,[]),this.t.get(t.method).push(t);}unregisterRoute(t){if(!this.t.has(t.method))throw new s("unregister-route-but-not-found-with-method",{method:t.method});const e=this.t.get(t.method).indexOf(t);if(!(e>-1))throw new s("unregister-route-route-not-registered");this.t.get(t.method).splice(e,1);}}let o;const c=()=>(o||(o=new a,o.addFetchListener(),o.addCacheListener()),o);function h(t,e,n){let a;if("string"==typeof t){const s=new URL(t,location.href);a=new i(({url:t})=>t.href===s.href,e,n);}else if(t instanceof RegExp)a=new r(t,e,n);else if("function"==typeof t)a=new i(t,e,n);else{if(!(t instanceof i))throw new s("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});a=t;}return c().registerRoute(a),a;}const u={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},l=t=>[u.prefix,t,u.suffix].filter(t=>t&&t.length>0).join("-"),f=t=>t||l(u.precache),w=t=>t||l(u.runtime);function d(t){t.then(()=>{});}const p=new Set;function y(){return y=Object.assign?Object.assign.bind():function(t){for(var e=1;e(t[e]=s,!0),has:(t,e)=>t instanceof IDBTransaction&&("done"===e||"store"===e)||e in t};function x(t){return t!==IDBDatabase.prototype.transaction||"objectStoreNames"in IDBTransaction.prototype?(g||(g=[IDBCursor.prototype.advance,IDBCursor.prototype.continue,IDBCursor.prototype.continuePrimaryKey])).includes(t)?function(...e){return t.apply(E(this),e),L(R.get(this));}:function(...e){return L(t.apply(E(this),e));}:function(e,...s){const n=t.call(E(this),e,...s);return b.set(n,e.sort?e.sort():[e]),L(n);};}function I(t){return"function"==typeof t?x(t):(t instanceof IDBTransaction&&function(t){if(v.has(t))return;const e=new Promise((e,s)=>{const n=()=>{t.removeEventListener("complete",i),t.removeEventListener("error",r),t.removeEventListener("abort",r);},i=()=>{e(),n();},r=()=>{s(t.error||new DOMException("AbortError","AbortError")),n();};t.addEventListener("complete",i),t.addEventListener("error",r),t.addEventListener("abort",r);});v.set(t,e);}(t),e=t,(m||(m=[IDBDatabase,IDBObjectStore,IDBIndex,IDBCursor,IDBTransaction])).some(t=>e instanceof t)?new Proxy(t,U):t);var e;}function L(t){if(t instanceof IDBRequest)return function(t){const e=new Promise((e,s)=>{const n=()=>{t.removeEventListener("success",i),t.removeEventListener("error",r);},i=()=>{e(L(t.result)),n();},r=()=>{s(t.error),n();};t.addEventListener("success",i),t.addEventListener("error",r);});return e.then(e=>{e instanceof IDBCursor&&R.set(e,t);}).catch(()=>{}),D.set(e,t),e;}(t);if(q.has(t))return q.get(t);const e=I(t);return e!==t&&(q.set(t,e),D.set(e,t)),e;}const E=t=>D.get(t);const C=["get","getKey","getAll","getAllKeys","count"],N=["put","add","delete","clear"],O=new Map;function k(t,e){if(!(t instanceof IDBDatabase)||e in t||"string"!=typeof e)return;if(O.get(e))return O.get(e);const s=e.replace(/FromIndex$/,""),n=e!==s,i=N.includes(s);if(!(s in(n?IDBIndex:IDBObjectStore).prototype)||!i&&!C.includes(s))return;const r=async function(t,...e){const r=this.transaction(t,i?"readwrite":"readonly");let a=r.store;return n&&(a=a.index(e.shift())),(await Promise.all([a[s](...e),i&&r.done]))[0];};return O.set(e,r),r;}U=(t=>y({},t,{get:(e,s,n)=>k(e,s)||t.get(e,s,n),has:(e,s)=>!!k(e,s)||t.has(e,s)}))(U);try{self["workbox:expiration:7.3.0"]&&_();}catch(t){}const B="cache-entries",T=t=>{const e=new URL(t,location.href);return e.hash="",e.href;};class M{constructor(t){this.h=null,this.u=t;}l(t){const e=t.createObjectStore(B,{keyPath:"id"});e.createIndex("cacheName","cacheName",{unique:!1}),e.createIndex("timestamp","timestamp",{unique:!1});}p(t){this.l(t),this.u&&function(t,{blocked:e}={}){const s=indexedDB.deleteDatabase(t);e&&s.addEventListener("blocked",t=>e(t.oldVersion,t)),L(s).then(()=>{});}(this.u);}async setTimestamp(t,e){const s={url:t=T(t),timestamp:e,cacheName:this.u,id:this.m(t)},n=(await this.getDb()).transaction(B,"readwrite",{durability:"relaxed"});await n.store.put(s),await n.done;}async getTimestamp(t){const e=await this.getDb(),s=await e.get(B,this.m(t));return null==s?void 0:s.timestamp;}async expireEntries(t,e){const s=await this.getDb();let n=await s.transaction(B).store.index("timestamp").openCursor(null,"prev");const i=[];let r=0;for(;n;){const s=n.value;s.cacheName===this.u&&(t&&s.timestamp=e?i.push(n.value):r++),n=await n.continue();}const a=[];for(const t of i)await s.delete(B,t.id),a.push(t.url);return a;}m(t){return this.u+"|"+T(t);}async getDb(){return this.h||(this.h=await function(t,e,{blocked:s,upgrade:n,blocking:i,terminated:r}={}){const a=indexedDB.open(t,e),o=L(a);return n&&a.addEventListener("upgradeneeded",t=>{n(L(a.result),t.oldVersion,t.newVersion,L(a.transaction),t);}),s&&a.addEventListener("blocked",t=>s(t.oldVersion,t.newVersion,t)),o.then(t=>{r&&t.addEventListener("close",()=>r()),i&&t.addEventListener("versionchange",t=>i(t.oldVersion,t.newVersion,t));}).catch(()=>{}),o;}("workbox-expiration",1,{upgrade:this.p.bind(this)})),this.h;}}class P{constructor(t,e={}){this.R=!1,this.v=!1,this.q=e.maxEntries,this.D=e.maxAgeSeconds,this.U=e.matchOptions,this.u=t,this._=new M(t);}async expireEntries(){if(this.R)return void(this.v=!0);this.R=!0;const t=this.D?Date.now()-1e3*this.D:0,e=await this._.expireEntries(t,this.q),s=await self.caches.open(this.u);for(const t of e)await s.delete(t,this.U);this.R=!1,this.v&&(this.v=!1,d(this.expireEntries()));}async updateTimestamp(t){await this._.setTimestamp(t,Date.now());}async isURLExpired(t){if(this.D){const e=await this._.getTimestamp(t),s=Date.now()-1e3*this.D;return void 0===e||e{this.resolve=t,this.reject=e;});}}try{self["workbox:strategies:7.3.0"]&&_();}catch(t){}function S(t){return"string"==typeof t?new Request(t):t;}class K{constructor(t,e){this.I={},Object.assign(this,e),this.event=e.event,this.L=t,this.C=new W,this.N=[],this.O=[...t.plugins],this.k=new Map;for(const t of this.O)this.k.set(t,{});this.event.waitUntil(this.C.promise);}async fetch(t){const{event:e}=this;let n=S(t);if("navigate"===n.mode&&e instanceof FetchEvent&&e.preloadResponse){const t=await e.preloadResponse;if(t)return t;}const i=this.hasCallback("fetchDidFail")?n.clone():null;try{for(const t of this.iterateCallbacks("requestWillFetch"))n=await t({request:n.clone(),event:e});}catch(t){if(t instanceof Error)throw new s("plugin-error-request-will-fetch",{thrownErrorMessage:t.message});}const r=n.clone();try{let t;t=await fetch(n,"navigate"===n.mode?void 0:this.L.fetchOptions);for(const s of this.iterateCallbacks("fetchDidSucceed"))t=await s({event:e,request:r,response:t});return t;}catch(t){throw i&&await this.runCallbacks("fetchDidFail",{error:t,event:e,originalRequest:i.clone(),request:r.clone()}),t;}}async fetchAndCachePut(t){const e=await this.fetch(t),s=e.clone();return this.waitUntil(this.cachePut(t,s)),e;}async cacheMatch(t){const e=S(t);let s;const{cacheName:n,matchOptions:i}=this.L,r=await this.getCacheKey(e,"read"),a=Object.assign(Object.assign({},i),{cacheName:n});s=await caches.match(r,a);for(const t of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await t({cacheName:n,matchOptions:i,cachedResponse:s,request:r,event:this.event})||void 0;return s;}async cachePut(t,e){const n=S(t);var i;await(i=0,new Promise(t=>setTimeout(t,i)));const r=await this.getCacheKey(n,"write");if(!e)throw new s("cache-put-with-no-response",{url:(a=r.url,new URL(String(a),location.href).href.replace(new RegExp(`^${location.origin}`),""))});var a;const o=await this.B(e);if(!o)return!1;const{cacheName:c,matchOptions:h}=this.L,u=await self.caches.open(c),l=this.hasCallback("cacheDidUpdate"),f=l?await async function(t,e,s,n){const i=j(e.url,s);if(e.url===i)return t.match(e,n);const r=Object.assign(Object.assign({},n),{ignoreSearch:!0}),a=await t.keys(e,r);for(const e of a)if(i===j(e.url,s))return t.match(e,n);}(u,r.clone(),["__WB_REVISION__"],h):null;try{await u.put(r,l?o.clone():o);}catch(t){if(t instanceof Error)throw"QuotaExceededError"===t.name&&await async function(){for(const t of p)await t();}(),t;}for(const t of this.iterateCallbacks("cacheDidUpdate"))await t({cacheName:c,oldResponse:f,newResponse:o.clone(),request:r,event:this.event});return!0;}async getCacheKey(t,e){const s=`${t.url} | ${e}`;if(!this.I[s]){let n=t;for(const t of this.iterateCallbacks("cacheKeyWillBeUsed"))n=S(await t({mode:e,request:n,event:this.event,params:this.params}));this.I[s]=n;}return this.I[s];}hasCallback(t){for(const e of this.L.plugins)if(t in e)return!0;return!1;}async runCallbacks(t,e){for(const s of this.iterateCallbacks(t))await s(e);}*iterateCallbacks(t){for(const e of this.L.plugins)if("function"==typeof e[t]){const s=this.k.get(e),n=n=>{const i=Object.assign(Object.assign({},n),{state:s});return e[t](i);};yield n;}}waitUntil(t){return this.N.push(t),t;}async doneWaiting(){for(;this.N.length;){const t=this.N.splice(0),e=(await Promise.allSettled(t)).find(t=>"rejected"===t.status);if(e)throw e.reason;}}destroy(){this.C.resolve(null);}async B(t){let e=t,s=!1;for(const t of this.iterateCallbacks("cacheWillUpdate"))if(e=await t({request:this.request,response:e,event:this.event})||void 0,s=!0,!e)break;return s||e&&200!==e.status&&(e=void 0),e;}}class A{constructor(t={}){this.cacheName=w(t.cacheName),this.plugins=t.plugins||[],this.fetchOptions=t.fetchOptions,this.matchOptions=t.matchOptions;}handle(t){const[e]=this.handleAll(t);return e;}handleAll(t){t instanceof FetchEvent&&(t={event:t,request:t.request});const e=t.event,s="string"==typeof t.request?new Request(t.request):t.request,n="params"in t?t.params:void 0,i=new K(this,{event:e,request:s,params:n}),r=this.T(i,s,e);return[r,this.M(r,i,s,e)];}async T(t,e,n){let i;await t.runCallbacks("handlerWillStart",{event:n,request:e});try{if(i=await this.P(e,t),!i||"error"===i.type)throw new s("no-response",{url:e.url});}catch(s){if(s instanceof Error)for(const r of t.iterateCallbacks("handlerDidError"))if(i=await r({error:s,event:n,request:e}),i)break;if(!i)throw s;}for(const s of t.iterateCallbacks("handlerWillRespond"))i=await s({event:n,request:e,response:i});return i;}async M(t,e,s,n){let i,r;try{i=await t;}catch(r){}try{await e.runCallbacks("handlerDidRespond",{event:n,request:s,response:i}),await e.doneWaiting();}catch(t){t instanceof Error&&(r=t);}if(await e.runCallbacks("handlerDidComplete",{event:n,request:s,response:i,error:r}),e.destroy(),r)throw r;}}try{self["workbox:cacheable-response:7.3.0"]&&_();}catch(t){}class F{constructor(t={}){this.j=t.statuses,this.W=t.headers;}isResponseCacheable(t){let e=!0;return this.j&&(e=this.j.includes(t.status)),this.W&&e&&(e=Object.keys(this.W).some(e=>t.headers.get(e)===this.W[e])),e;}}const H={cacheWillUpdate:async({response:t})=>200===t.status||0===t.status?t:null};function $(t,e){const s=e();return t.waitUntil(s),s;}try{self["workbox:precaching:7.3.0"]&&_();}catch(t){}function G(t){if(!t)throw new s("add-to-cache-list-unexpected-type",{entry:t});if("string"==typeof t){const e=new URL(t,location.href);return{cacheKey:e.href,url:e.href};}const{revision:e,url:n}=t;if(!n)throw new s("add-to-cache-list-unexpected-type",{entry:t});if(!e){const t=new URL(n,location.href);return{cacheKey:t.href,url:t.href};}const i=new URL(n,location.href),r=new URL(n,location.href);return i.searchParams.set("__WB_REVISION__",e),{cacheKey:i.href,url:r.href};}class V{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:t,state:e})=>{e&&(e.originalRequest=t);},this.cachedResponseWillBeUsed=async({event:t,state:e,cachedResponse:s})=>{if("install"===t.type&&e&&e.originalRequest&&e.originalRequest instanceof Request){const t=e.originalRequest.url;s?this.notUpdatedURLs.push(t):this.updatedURLs.push(t);}return s;};}}class J{constructor({precacheController:t}){this.cacheKeyWillBeUsed=async({request:t,params:e})=>{const s=(null==e?void 0:e.cacheKey)||this.S.getCacheKeyForURL(t.url);return s?new Request(s,{headers:t.headers}):t;},this.S=t;}}let Q,z;async function X(t,e){let n=null;if(t.url){n=new URL(t.url).origin;}if(n!==self.location.origin)throw new s("cross-origin-copy-response",{origin:n});const i=t.clone(),r={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},a=e?e(r):r,o=function(){if(void 0===Q){const t=new Response("");if("body"in t)try{new Response(t.body),Q=!0;}catch(t){Q=!1;}Q=!1;}return Q;}()?i.body:await i.blob();return new Response(o,a);}class Y extends A{constructor(t={}){t.cacheName=f(t.cacheName),super(t),this.K=!1!==t.fallbackToNetwork,this.plugins.push(Y.copyRedirectedCacheableResponsesPlugin);}async P(t,e){const s=await e.cacheMatch(t);return s||(e.event&&"install"===e.event.type?await this.A(t,e):await this.F(t,e));}async F(t,e){let n;const i=e.params||{};if(!this.K)throw new s("missing-precache-entry",{cacheName:this.cacheName,url:t.url});{const s=i.integrity,r=t.integrity,a=!r||r===s;n=await e.fetch(new Request(t,{integrity:"no-cors"!==t.mode?r||s:void 0})),s&&a&&"no-cors"!==t.mode&&(this.H(),await e.cachePut(t,n.clone()));}return n;}async A(t,e){this.H();const n=await e.fetch(t);if(!await e.cachePut(t,n.clone()))throw new s("bad-precaching-response",{url:t.url,status:n.status});return n;}H(){let t=null,e=0;for(const[s,n]of this.plugins.entries())n!==Y.copyRedirectedCacheableResponsesPlugin&&(n===Y.defaultPrecacheCacheabilityPlugin&&(t=s),n.cacheWillUpdate&&e++);0===e?this.plugins.push(Y.defaultPrecacheCacheabilityPlugin):e>1&&null!==t&&this.plugins.splice(t,1);}}Y.defaultPrecacheCacheabilityPlugin={cacheWillUpdate:async({response:t})=>!t||t.status>=400?null:t},Y.copyRedirectedCacheableResponsesPlugin={cacheWillUpdate:async({response:t})=>t.redirected?await X(t):t};class Z{constructor({cacheName:t,plugins:e=[],fallbackToNetwork:s=!0}={}){this.$=new Map,this.G=new Map,this.V=new Map,this.L=new Y({cacheName:f(t),plugins:[...e,new J({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this);}get strategy(){return this.L;}precache(t){this.addToCacheList(t),this.J||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this.J=!0);}addToCacheList(t){const e=[];for(const n of t){"string"==typeof n?e.push(n):n&&void 0===n.revision&&e.push(n.url);const{cacheKey:t,url:i}=G(n),r="string"!=typeof n&&n.revision?"reload":"default";if(this.$.has(i)&&this.$.get(i)!==t)throw new s("add-to-cache-list-conflicting-entries",{firstEntry:this.$.get(i),secondEntry:t});if("string"!=typeof n&&n.integrity){if(this.V.has(t)&&this.V.get(t)!==n.integrity)throw new s("add-to-cache-list-conflicting-integrities",{url:i});this.V.set(t,n.integrity);}if(this.$.set(i,t),this.G.set(i,r),e.length>0){const t=`Workbox is precaching URLs without revision info: ${e.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(t);}}}install(t){return $(t,async()=>{const e=new V;this.strategy.plugins.push(e);for(const[e,s]of this.$){const n=this.V.get(s),i=this.G.get(e),r=new Request(e,{integrity:n,cache:i,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:s},request:r,event:t}));}const{updatedURLs:s,notUpdatedURLs:n}=e;return{updatedURLs:s,notUpdatedURLs:n};});}activate(t){return $(t,async()=>{const t=await self.caches.open(this.strategy.cacheName),e=await t.keys(),s=new Set(this.$.values()),n=[];for(const i of e)s.has(i.url)||(await t.delete(i),n.push(i.url));return{deletedURLs:n};});}getURLsToCacheKeys(){return this.$;}getCachedURLs(){return[...this.$.keys()];}getCacheKeyForURL(t){const e=new URL(t,location.href);return this.$.get(e.href);}getIntegrityForCacheKey(t){return this.V.get(t);}async matchPrecache(t){const e=t instanceof Request?t.url:t,s=this.getCacheKeyForURL(e);if(s){return(await self.caches.open(this.strategy.cacheName)).match(s);}}createHandlerBoundToURL(t){const e=this.getCacheKeyForURL(t);if(!e)throw new s("non-precached-url",{url:t});return s=>(s.request=new Request(t),s.params=Object.assign({cacheKey:e},s.params),this.strategy.handle(s));}}const tt=()=>(z||(z=new Z),z);class et extends i{constructor(t,e){super(({request:s})=>{const n=t.getURLsToCacheKeys();for(const i of function*(t,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:s="index.html",cleanURLs:n=!0,urlManipulation:i}={}){const r=new URL(t,location.href);r.hash="",yield r.href;const a=function(t,e=[]){for(const s of[...t.searchParams.keys()])e.some(t=>t.test(s))&&t.searchParams.delete(s);return t;}(r,e);if(yield a.href,s&&a.pathname.endsWith("/")){const t=new URL(a.href);t.pathname+=s,yield t.href;}if(n){const t=new URL(a.href);t.pathname+=".html",yield t.href;}if(i){const t=i({url:r});for(const e of t)yield e.href;}}(s.url,e)){const e=n.get(i);if(e){return{cacheKey:e,integrity:t.getIntegrityForCacheKey(e)};}}},t.strategy);}}t.CacheFirst=class extends A{async P(t,e){let n,i=await e.cacheMatch(t);if(!i)try{i=await e.fetchAndCachePut(t);}catch(t){t instanceof Error&&(n=t);}if(!i)throw new s("no-response",{url:t.url,error:n});return i;}},t.CacheableResponsePlugin=class{constructor(t){this.cacheWillUpdate=async({response:t})=>this.X.isResponseCacheable(t)?t:null,this.X=new F(t);}},t.ExpirationPlugin=class{constructor(t={}){this.cachedResponseWillBeUsed=async({event:t,request:e,cacheName:s,cachedResponse:n})=>{if(!n)return null;const i=this.Y(n),r=this.Z(s);d(r.expireEntries());const a=r.updateTimestamp(e.url);if(t)try{t.waitUntil(a);}catch(t){}return i?n:null;},this.cacheDidUpdate=async({cacheName:t,request:e})=>{const s=this.Z(t);await s.updateTimestamp(e.url),await s.expireEntries();},this.tt=t,this.D=t.maxAgeSeconds,this.et=new Map,t.purgeOnQuotaError&&function(t){p.add(t);}(()=>this.deleteCacheAndMetadata());}Z(t){if(t===w())throw new s("expire-custom-caches-only");let e=this.et.get(t);return e||(e=new P(t,this.tt),this.et.set(t,e)),e;}Y(t){if(!this.D)return!0;const e=this.st(t);if(null===e)return!0;return e>=Date.now()-1e3*this.D;}st(t){if(!t.headers.has("date"))return null;const e=t.headers.get("date"),s=new Date(e).getTime();return isNaN(s)?null:s;}async deleteCacheAndMetadata(){for(const[t,e]of this.et)await self.caches.delete(t),await e.delete();this.et=new Map;}},t.NetworkFirst=class extends A{constructor(t={}){super(t),this.plugins.some(t=>"cacheWillUpdate"in t)||this.plugins.unshift(H),this.nt=t.networkTimeoutSeconds||0;}async P(t,e){const n=[],i=[];let r;if(this.nt){const{id:s,promise:a}=this.it({request:t,logs:n,handler:e});r=s,i.push(a);}const a=this.rt({timeoutId:r,request:t,logs:n,handler:e});i.push(a);const o=await e.waitUntil((async()=>await e.waitUntil(Promise.race(i))||await a)());if(!o)throw new s("no-response",{url:t.url});return o;}it({request:t,logs:e,handler:s}){let n;return{promise:new Promise(e=>{n=setTimeout(async()=>{e(await s.cacheMatch(t));},1e3*this.nt);}),id:n};}async rt({timeoutId:t,request:e,logs:s,handler:n}){let i,r;try{r=await n.fetchAndCachePut(e);}catch(t){t instanceof Error&&(i=t);}return t&&clearTimeout(t),!i&&r||(r=await n.cacheMatch(e)),r;}},t.precacheAndRoute=function(t,e){!function(t){tt().precache(t);}(t),function(t){const e=tt();h(new et(e,t));}(e);},t.registerRoute=h;}); +//# sourceMappingURL=workbox-813eeb66.js.map diff --git a/core/scripts/serviceWorker/workbox-813eeb66.js.map b/core/scripts/serviceWorker/workbox-813eeb66.js.map new file mode 100644 index 0000000..cb44eef --- /dev/null +++ b/core/scripts/serviceWorker/workbox-813eeb66.js.map @@ -0,0 +1 @@ +{"version":3,"file":"workbox-813eeb66.js","sources":["node_modules/workbox-core/_version.js","node_modules/workbox-core/_private/logger.js","node_modules/workbox-core/models/messages/messageGenerator.js","node_modules/workbox-core/_private/WorkboxError.js","node_modules/workbox-routing/_version.js","node_modules/workbox-routing/utils/constants.js","node_modules/workbox-routing/utils/normalizeHandler.js","node_modules/workbox-routing/Route.js","node_modules/workbox-routing/RegExpRoute.js","node_modules/workbox-routing/Router.js","node_modules/workbox-routing/utils/getOrCreateDefaultRouter.js","node_modules/workbox-routing/registerRoute.js","node_modules/workbox-core/_private/cacheNames.js","node_modules/workbox-core/_private/dontWaitFor.js","node_modules/workbox-core/models/quotaErrorCallbacks.js","node_modules/idb/build/wrap-idb-value.js","node_modules/idb/build/index.js","node_modules/workbox-expiration/_version.js","node_modules/workbox-expiration/models/CacheTimestampsModel.js","node_modules/workbox-expiration/CacheExpiration.js","node_modules/workbox-core/_private/cacheMatchIgnoreParams.js","node_modules/workbox-core/_private/Deferred.js","node_modules/workbox-strategies/_version.js","node_modules/workbox-strategies/StrategyHandler.js","node_modules/workbox-core/_private/timeout.js","node_modules/workbox-core/_private/getFriendlyURL.js","node_modules/workbox-core/_private/executeQuotaErrorCallbacks.js","node_modules/workbox-strategies/Strategy.js","node_modules/workbox-cacheable-response/_version.js","node_modules/workbox-cacheable-response/CacheableResponse.js","node_modules/workbox-strategies/plugins/cacheOkAndOpaquePlugin.js","node_modules/workbox-core/_private/waitUntil.js","node_modules/workbox-precaching/_version.js","node_modules/workbox-precaching/utils/createCacheKey.js","node_modules/workbox-precaching/utils/PrecacheInstallReportPlugin.js","node_modules/workbox-precaching/utils/PrecacheCacheKeyPlugin.js","node_modules/workbox-core/_private/canConstructResponseFromBodyStream.js","node_modules/workbox-precaching/utils/getOrCreatePrecacheController.js","node_modules/workbox-core/copyResponse.js","node_modules/workbox-precaching/PrecacheStrategy.js","node_modules/workbox-precaching/PrecacheController.js","node_modules/workbox-precaching/PrecacheRoute.js","node_modules/workbox-precaching/utils/generateURLVariations.js","node_modules/workbox-precaching/utils/removeIgnoredSearchParams.js","node_modules/workbox-strategies/CacheFirst.js","node_modules/workbox-cacheable-response/CacheableResponsePlugin.js","node_modules/workbox-expiration/ExpirationPlugin.js","node_modules/workbox-core/registerQuotaErrorCallback.js","node_modules/workbox-strategies/NetworkFirst.js","node_modules/workbox-precaching/precacheAndRoute.js","node_modules/workbox-precaching/precache.js","node_modules/workbox-precaching/addRoute.js"],"sourcesContent":["\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:core:7.3.0'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2019 Google LLC\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nconst logger = (process.env.NODE_ENV === 'production'\n ? null\n : (() => {\n // Don't overwrite this value if it's already set.\n // See https://github.com/GoogleChrome/workbox/pull/2284#issuecomment-560470923\n if (!('__WB_DISABLE_DEV_LOGS' in globalThis)) {\n self.__WB_DISABLE_DEV_LOGS = false;\n }\n let inGroup = false;\n const methodToColorMap = {\n debug: `#7f8c8d`,\n log: `#2ecc71`,\n warn: `#f39c12`,\n error: `#c0392b`,\n groupCollapsed: `#3498db`,\n groupEnd: null, // No colored prefix on groupEnd\n };\n const print = function (method, args) {\n if (self.__WB_DISABLE_DEV_LOGS) {\n return;\n }\n if (method === 'groupCollapsed') {\n // Safari doesn't print all console.groupCollapsed() arguments:\n // https://bugs.webkit.org/show_bug.cgi?id=182754\n if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {\n console[method](...args);\n return;\n }\n }\n const styles = [\n `background: ${methodToColorMap[method]}`,\n `border-radius: 0.5em`,\n `color: white`,\n `font-weight: bold`,\n `padding: 2px 0.5em`,\n ];\n // When in a group, the workbox prefix is not displayed.\n const logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')];\n console[method](...logPrefix, ...args);\n if (method === 'groupCollapsed') {\n inGroup = true;\n }\n if (method === 'groupEnd') {\n inGroup = false;\n }\n };\n // eslint-disable-next-line @typescript-eslint/ban-types\n const api = {};\n const loggerMethods = Object.keys(methodToColorMap);\n for (const key of loggerMethods) {\n const method = key;\n api[method] = (...args) => {\n print(method, args);\n };\n }\n return api;\n })());\nexport { logger };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { messages } from './messages.js';\nimport '../../_version.js';\nconst fallback = (code, ...args) => {\n let msg = code;\n if (args.length > 0) {\n msg += ` :: ${JSON.stringify(args)}`;\n }\n return msg;\n};\nconst generatorFunction = (code, details = {}) => {\n const message = messages[code];\n if (!message) {\n throw new Error(`Unable to find message for code '${code}'.`);\n }\n return message(details);\n};\nexport const messageGenerator = process.env.NODE_ENV === 'production' ? fallback : generatorFunction;\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { messageGenerator } from '../models/messages/messageGenerator.js';\nimport '../_version.js';\n/**\n * Workbox errors should be thrown with this class.\n * This allows use to ensure the type easily in tests,\n * helps developers identify errors from workbox\n * easily and allows use to optimise error\n * messages correctly.\n *\n * @private\n */\nclass WorkboxError extends Error {\n /**\n *\n * @param {string} errorCode The error code that\n * identifies this particular error.\n * @param {Object=} details Any relevant arguments\n * that will help developers identify issues should\n * be added as a key on the context object.\n */\n constructor(errorCode, details) {\n const message = messageGenerator(errorCode, details);\n super(message);\n this.name = errorCode;\n this.details = details;\n }\n}\nexport { WorkboxError };\n","\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:routing:7.3.0'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * The default HTTP method, 'GET', used when there's no specific method\n * configured for a route.\n *\n * @type {string}\n *\n * @private\n */\nexport const defaultMethod = 'GET';\n/**\n * The list of valid HTTP methods associated with requests that could be routed.\n *\n * @type {Array}\n *\n * @private\n */\nexport const validMethods = [\n 'DELETE',\n 'GET',\n 'HEAD',\n 'PATCH',\n 'POST',\n 'PUT',\n];\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport '../_version.js';\n/**\n * @param {function()|Object} handler Either a function, or an object with a\n * 'handle' method.\n * @return {Object} An object with a handle method.\n *\n * @private\n */\nexport const normalizeHandler = (handler) => {\n if (handler && typeof handler === 'object') {\n if (process.env.NODE_ENV !== 'production') {\n assert.hasMethod(handler, 'handle', {\n moduleName: 'workbox-routing',\n className: 'Route',\n funcName: 'constructor',\n paramName: 'handler',\n });\n }\n return handler;\n }\n else {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(handler, 'function', {\n moduleName: 'workbox-routing',\n className: 'Route',\n funcName: 'constructor',\n paramName: 'handler',\n });\n }\n return { handle: handler };\n }\n};\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { defaultMethod, validMethods } from './utils/constants.js';\nimport { normalizeHandler } from './utils/normalizeHandler.js';\nimport './_version.js';\n/**\n * A `Route` consists of a pair of callback functions, \"match\" and \"handler\".\n * The \"match\" callback determine if a route should be used to \"handle\" a\n * request by returning a non-falsy value if it can. The \"handler\" callback\n * is called when there is a match and should return a Promise that resolves\n * to a `Response`.\n *\n * @memberof workbox-routing\n */\nclass Route {\n /**\n * Constructor for Route class.\n *\n * @param {workbox-routing~matchCallback} match\n * A callback function that determines whether the route matches a given\n * `fetch` event by returning a non-falsy value.\n * @param {workbox-routing~handlerCallback} handler A callback\n * function that returns a Promise resolving to a Response.\n * @param {string} [method='GET'] The HTTP method to match the Route\n * against.\n */\n constructor(match, handler, method = defaultMethod) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(match, 'function', {\n moduleName: 'workbox-routing',\n className: 'Route',\n funcName: 'constructor',\n paramName: 'match',\n });\n if (method) {\n assert.isOneOf(method, validMethods, { paramName: 'method' });\n }\n }\n // These values are referenced directly by Router so cannot be\n // altered by minificaton.\n this.handler = normalizeHandler(handler);\n this.match = match;\n this.method = method;\n }\n /**\n *\n * @param {workbox-routing-handlerCallback} handler A callback\n * function that returns a Promise resolving to a Response\n */\n setCatchHandler(handler) {\n this.catchHandler = normalizeHandler(handler);\n }\n}\nexport { Route };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { Route } from './Route.js';\nimport './_version.js';\n/**\n * RegExpRoute makes it easy to create a regular expression based\n * {@link workbox-routing.Route}.\n *\n * For same-origin requests the RegExp only needs to match part of the URL. For\n * requests against third-party servers, you must define a RegExp that matches\n * the start of the URL.\n *\n * @memberof workbox-routing\n * @extends workbox-routing.Route\n */\nclass RegExpRoute extends Route {\n /**\n * If the regular expression contains\n * [capture groups]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references},\n * the captured values will be passed to the\n * {@link workbox-routing~handlerCallback} `params`\n * argument.\n *\n * @param {RegExp} regExp The regular expression to match against URLs.\n * @param {workbox-routing~handlerCallback} handler A callback\n * function that returns a Promise resulting in a Response.\n * @param {string} [method='GET'] The HTTP method to match the Route\n * against.\n */\n constructor(regExp, handler, method) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(regExp, RegExp, {\n moduleName: 'workbox-routing',\n className: 'RegExpRoute',\n funcName: 'constructor',\n paramName: 'pattern',\n });\n }\n const match = ({ url }) => {\n const result = regExp.exec(url.href);\n // Return immediately if there's no match.\n if (!result) {\n return;\n }\n // Require that the match start at the first character in the URL string\n // if it's a cross-origin request.\n // See https://github.com/GoogleChrome/workbox/issues/281 for the context\n // behind this behavior.\n if (url.origin !== location.origin && result.index !== 0) {\n if (process.env.NODE_ENV !== 'production') {\n logger.debug(`The regular expression '${regExp.toString()}' only partially matched ` +\n `against the cross-origin URL '${url.toString()}'. RegExpRoute's will only ` +\n `handle cross-origin requests if they match the entire URL.`);\n }\n return;\n }\n // If the route matches, but there aren't any capture groups defined, then\n // this will return [], which is truthy and therefore sufficient to\n // indicate a match.\n // If there are capture groups, then it will return their values.\n return result.slice(1);\n };\n super(match, handler, method);\n }\n}\nexport { RegExpRoute };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { defaultMethod } from './utils/constants.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { normalizeHandler } from './utils/normalizeHandler.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport './_version.js';\n/**\n * The Router can be used to process a `FetchEvent` using one or more\n * {@link workbox-routing.Route}, responding with a `Response` if\n * a matching route exists.\n *\n * If no route matches a given a request, the Router will use a \"default\"\n * handler if one is defined.\n *\n * Should the matching Route throw an error, the Router will use a \"catch\"\n * handler if one is defined to gracefully deal with issues and respond with a\n * Request.\n *\n * If a request matches multiple routes, the **earliest** registered route will\n * be used to respond to the request.\n *\n * @memberof workbox-routing\n */\nclass Router {\n /**\n * Initializes a new Router.\n */\n constructor() {\n this._routes = new Map();\n this._defaultHandlerMap = new Map();\n }\n /**\n * @return {Map>} routes A `Map` of HTTP\n * method name ('GET', etc.) to an array of all the corresponding `Route`\n * instances that are registered.\n */\n get routes() {\n return this._routes;\n }\n /**\n * Adds a fetch event listener to respond to events when a route matches\n * the event's request.\n */\n addFetchListener() {\n // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705\n self.addEventListener('fetch', ((event) => {\n const { request } = event;\n const responsePromise = this.handleRequest({ request, event });\n if (responsePromise) {\n event.respondWith(responsePromise);\n }\n }));\n }\n /**\n * Adds a message event listener for URLs to cache from the window.\n * This is useful to cache resources loaded on the page prior to when the\n * service worker started controlling it.\n *\n * The format of the message data sent from the window should be as follows.\n * Where the `urlsToCache` array may consist of URL strings or an array of\n * URL string + `requestInit` object (the same as you'd pass to `fetch()`).\n *\n * ```\n * {\n * type: 'CACHE_URLS',\n * payload: {\n * urlsToCache: [\n * './script1.js',\n * './script2.js',\n * ['./script3.js', {mode: 'no-cors'}],\n * ],\n * },\n * }\n * ```\n */\n addCacheListener() {\n // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705\n self.addEventListener('message', ((event) => {\n // event.data is type 'any'\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n if (event.data && event.data.type === 'CACHE_URLS') {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const { payload } = event.data;\n if (process.env.NODE_ENV !== 'production') {\n logger.debug(`Caching URLs from the window`, payload.urlsToCache);\n }\n const requestPromises = Promise.all(payload.urlsToCache.map((entry) => {\n if (typeof entry === 'string') {\n entry = [entry];\n }\n const request = new Request(...entry);\n return this.handleRequest({ request, event });\n // TODO(philipwalton): TypeScript errors without this typecast for\n // some reason (probably a bug). The real type here should work but\n // doesn't: `Array | undefined>`.\n })); // TypeScript\n event.waitUntil(requestPromises);\n // If a MessageChannel was used, reply to the message on success.\n if (event.ports && event.ports[0]) {\n void requestPromises.then(() => event.ports[0].postMessage(true));\n }\n }\n }));\n }\n /**\n * Apply the routing rules to a FetchEvent object to get a Response from an\n * appropriate Route's handler.\n *\n * @param {Object} options\n * @param {Request} options.request The request to handle.\n * @param {ExtendableEvent} options.event The event that triggered the\n * request.\n * @return {Promise|undefined} A promise is returned if a\n * registered route can handle the request. If there is no matching\n * route and there's no `defaultHandler`, `undefined` is returned.\n */\n handleRequest({ request, event, }) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(request, Request, {\n moduleName: 'workbox-routing',\n className: 'Router',\n funcName: 'handleRequest',\n paramName: 'options.request',\n });\n }\n const url = new URL(request.url, location.href);\n if (!url.protocol.startsWith('http')) {\n if (process.env.NODE_ENV !== 'production') {\n logger.debug(`Workbox Router only supports URLs that start with 'http'.`);\n }\n return;\n }\n const sameOrigin = url.origin === location.origin;\n const { params, route } = this.findMatchingRoute({\n event,\n request,\n sameOrigin,\n url,\n });\n let handler = route && route.handler;\n const debugMessages = [];\n if (process.env.NODE_ENV !== 'production') {\n if (handler) {\n debugMessages.push([`Found a route to handle this request:`, route]);\n if (params) {\n debugMessages.push([\n `Passing the following params to the route's handler:`,\n params,\n ]);\n }\n }\n }\n // If we don't have a handler because there was no matching route, then\n // fall back to defaultHandler if that's defined.\n const method = request.method;\n if (!handler && this._defaultHandlerMap.has(method)) {\n if (process.env.NODE_ENV !== 'production') {\n debugMessages.push(`Failed to find a matching route. Falling ` +\n `back to the default handler for ${method}.`);\n }\n handler = this._defaultHandlerMap.get(method);\n }\n if (!handler) {\n if (process.env.NODE_ENV !== 'production') {\n // No handler so Workbox will do nothing. If logs is set of debug\n // i.e. verbose, we should print out this information.\n logger.debug(`No route found for: ${getFriendlyURL(url)}`);\n }\n return;\n }\n if (process.env.NODE_ENV !== 'production') {\n // We have a handler, meaning Workbox is going to handle the route.\n // print the routing details to the console.\n logger.groupCollapsed(`Router is responding to: ${getFriendlyURL(url)}`);\n debugMessages.forEach((msg) => {\n if (Array.isArray(msg)) {\n logger.log(...msg);\n }\n else {\n logger.log(msg);\n }\n });\n logger.groupEnd();\n }\n // Wrap in try and catch in case the handle method throws a synchronous\n // error. It should still callback to the catch handler.\n let responsePromise;\n try {\n responsePromise = handler.handle({ url, request, event, params });\n }\n catch (err) {\n responsePromise = Promise.reject(err);\n }\n // Get route's catch handler, if it exists\n const catchHandler = route && route.catchHandler;\n if (responsePromise instanceof Promise &&\n (this._catchHandler || catchHandler)) {\n responsePromise = responsePromise.catch(async (err) => {\n // If there's a route catch handler, process that first\n if (catchHandler) {\n if (process.env.NODE_ENV !== 'production') {\n // Still include URL here as it will be async from the console group\n // and may not make sense without the URL\n logger.groupCollapsed(`Error thrown when responding to: ` +\n ` ${getFriendlyURL(url)}. Falling back to route's Catch Handler.`);\n logger.error(`Error thrown by:`, route);\n logger.error(err);\n logger.groupEnd();\n }\n try {\n return await catchHandler.handle({ url, request, event, params });\n }\n catch (catchErr) {\n if (catchErr instanceof Error) {\n err = catchErr;\n }\n }\n }\n if (this._catchHandler) {\n if (process.env.NODE_ENV !== 'production') {\n // Still include URL here as it will be async from the console group\n // and may not make sense without the URL\n logger.groupCollapsed(`Error thrown when responding to: ` +\n ` ${getFriendlyURL(url)}. Falling back to global Catch Handler.`);\n logger.error(`Error thrown by:`, route);\n logger.error(err);\n logger.groupEnd();\n }\n return this._catchHandler.handle({ url, request, event });\n }\n throw err;\n });\n }\n return responsePromise;\n }\n /**\n * Checks a request and URL (and optionally an event) against the list of\n * registered routes, and if there's a match, returns the corresponding\n * route along with any params generated by the match.\n *\n * @param {Object} options\n * @param {URL} options.url\n * @param {boolean} options.sameOrigin The result of comparing `url.origin`\n * against the current origin.\n * @param {Request} options.request The request to match.\n * @param {Event} options.event The corresponding event.\n * @return {Object} An object with `route` and `params` properties.\n * They are populated if a matching route was found or `undefined`\n * otherwise.\n */\n findMatchingRoute({ url, sameOrigin, request, event, }) {\n const routes = this._routes.get(request.method) || [];\n for (const route of routes) {\n let params;\n // route.match returns type any, not possible to change right now.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n const matchResult = route.match({ url, sameOrigin, request, event });\n if (matchResult) {\n if (process.env.NODE_ENV !== 'production') {\n // Warn developers that using an async matchCallback is almost always\n // not the right thing to do.\n if (matchResult instanceof Promise) {\n logger.warn(`While routing ${getFriendlyURL(url)}, an async ` +\n `matchCallback function was used. Please convert the ` +\n `following route to use a synchronous matchCallback function:`, route);\n }\n }\n // See https://github.com/GoogleChrome/workbox/issues/2079\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n params = matchResult;\n if (Array.isArray(params) && params.length === 0) {\n // Instead of passing an empty array in as params, use undefined.\n params = undefined;\n }\n else if (matchResult.constructor === Object && // eslint-disable-line\n Object.keys(matchResult).length === 0) {\n // Instead of passing an empty object in as params, use undefined.\n params = undefined;\n }\n else if (typeof matchResult === 'boolean') {\n // For the boolean value true (rather than just something truth-y),\n // don't set params.\n // See https://github.com/GoogleChrome/workbox/pull/2134#issuecomment-513924353\n params = undefined;\n }\n // Return early if have a match.\n return { route, params };\n }\n }\n // If no match was found above, return and empty object.\n return {};\n }\n /**\n * Define a default `handler` that's called when no routes explicitly\n * match the incoming request.\n *\n * Each HTTP method ('GET', 'POST', etc.) gets its own default handler.\n *\n * Without a default handler, unmatched requests will go against the\n * network as if there were no service worker present.\n *\n * @param {workbox-routing~handlerCallback} handler A callback\n * function that returns a Promise resulting in a Response.\n * @param {string} [method='GET'] The HTTP method to associate with this\n * default handler. Each method has its own default.\n */\n setDefaultHandler(handler, method = defaultMethod) {\n this._defaultHandlerMap.set(method, normalizeHandler(handler));\n }\n /**\n * If a Route throws an error while handling a request, this `handler`\n * will be called and given a chance to provide a response.\n *\n * @param {workbox-routing~handlerCallback} handler A callback\n * function that returns a Promise resulting in a Response.\n */\n setCatchHandler(handler) {\n this._catchHandler = normalizeHandler(handler);\n }\n /**\n * Registers a route with the router.\n *\n * @param {workbox-routing.Route} route The route to register.\n */\n registerRoute(route) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(route, 'object', {\n moduleName: 'workbox-routing',\n className: 'Router',\n funcName: 'registerRoute',\n paramName: 'route',\n });\n assert.hasMethod(route, 'match', {\n moduleName: 'workbox-routing',\n className: 'Router',\n funcName: 'registerRoute',\n paramName: 'route',\n });\n assert.isType(route.handler, 'object', {\n moduleName: 'workbox-routing',\n className: 'Router',\n funcName: 'registerRoute',\n paramName: 'route',\n });\n assert.hasMethod(route.handler, 'handle', {\n moduleName: 'workbox-routing',\n className: 'Router',\n funcName: 'registerRoute',\n paramName: 'route.handler',\n });\n assert.isType(route.method, 'string', {\n moduleName: 'workbox-routing',\n className: 'Router',\n funcName: 'registerRoute',\n paramName: 'route.method',\n });\n }\n if (!this._routes.has(route.method)) {\n this._routes.set(route.method, []);\n }\n // Give precedence to all of the earlier routes by adding this additional\n // route to the end of the array.\n this._routes.get(route.method).push(route);\n }\n /**\n * Unregisters a route with the router.\n *\n * @param {workbox-routing.Route} route The route to unregister.\n */\n unregisterRoute(route) {\n if (!this._routes.has(route.method)) {\n throw new WorkboxError('unregister-route-but-not-found-with-method', {\n method: route.method,\n });\n }\n const routeIndex = this._routes.get(route.method).indexOf(route);\n if (routeIndex > -1) {\n this._routes.get(route.method).splice(routeIndex, 1);\n }\n else {\n throw new WorkboxError('unregister-route-route-not-registered');\n }\n }\n}\nexport { Router };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { Router } from '../Router.js';\nimport '../_version.js';\nlet defaultRouter;\n/**\n * Creates a new, singleton Router instance if one does not exist. If one\n * does already exist, that instance is returned.\n *\n * @private\n * @return {Router}\n */\nexport const getOrCreateDefaultRouter = () => {\n if (!defaultRouter) {\n defaultRouter = new Router();\n // The helpers that use the default Router assume these listeners exist.\n defaultRouter.addFetchListener();\n defaultRouter.addCacheListener();\n }\n return defaultRouter;\n};\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { Route } from './Route.js';\nimport { RegExpRoute } from './RegExpRoute.js';\nimport { getOrCreateDefaultRouter } from './utils/getOrCreateDefaultRouter.js';\nimport './_version.js';\n/**\n * Easily register a RegExp, string, or function with a caching\n * strategy to a singleton Router instance.\n *\n * This method will generate a Route for you if needed and\n * call {@link workbox-routing.Router#registerRoute}.\n *\n * @param {RegExp|string|workbox-routing.Route~matchCallback|workbox-routing.Route} capture\n * If the capture param is a `Route`, all other arguments will be ignored.\n * @param {workbox-routing~handlerCallback} [handler] A callback\n * function that returns a Promise resulting in a Response. This parameter\n * is required if `capture` is not a `Route` object.\n * @param {string} [method='GET'] The HTTP method to match the Route\n * against.\n * @return {workbox-routing.Route} The generated `Route`.\n *\n * @memberof workbox-routing\n */\nfunction registerRoute(capture, handler, method) {\n let route;\n if (typeof capture === 'string') {\n const captureUrl = new URL(capture, location.href);\n if (process.env.NODE_ENV !== 'production') {\n if (!(capture.startsWith('/') || capture.startsWith('http'))) {\n throw new WorkboxError('invalid-string', {\n moduleName: 'workbox-routing',\n funcName: 'registerRoute',\n paramName: 'capture',\n });\n }\n // We want to check if Express-style wildcards are in the pathname only.\n // TODO: Remove this log message in v4.\n const valueToCheck = capture.startsWith('http')\n ? captureUrl.pathname\n : capture;\n // See https://github.com/pillarjs/path-to-regexp#parameters\n const wildcards = '[*:?+]';\n if (new RegExp(`${wildcards}`).exec(valueToCheck)) {\n logger.debug(`The '$capture' parameter contains an Express-style wildcard ` +\n `character (${wildcards}). Strings are now always interpreted as ` +\n `exact matches; use a RegExp for partial or wildcard matches.`);\n }\n }\n const matchCallback = ({ url }) => {\n if (process.env.NODE_ENV !== 'production') {\n if (url.pathname === captureUrl.pathname &&\n url.origin !== captureUrl.origin) {\n logger.debug(`${capture} only partially matches the cross-origin URL ` +\n `${url.toString()}. This route will only handle cross-origin requests ` +\n `if they match the entire URL.`);\n }\n }\n return url.href === captureUrl.href;\n };\n // If `capture` is a string then `handler` and `method` must be present.\n route = new Route(matchCallback, handler, method);\n }\n else if (capture instanceof RegExp) {\n // If `capture` is a `RegExp` then `handler` and `method` must be present.\n route = new RegExpRoute(capture, handler, method);\n }\n else if (typeof capture === 'function') {\n // If `capture` is a function then `handler` and `method` must be present.\n route = new Route(capture, handler, method);\n }\n else if (capture instanceof Route) {\n route = capture;\n }\n else {\n throw new WorkboxError('unsupported-route-type', {\n moduleName: 'workbox-routing',\n funcName: 'registerRoute',\n paramName: 'capture',\n });\n }\n const defaultRouter = getOrCreateDefaultRouter();\n defaultRouter.registerRoute(route);\n return route;\n}\nexport { registerRoute };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nconst _cacheNameDetails = {\n googleAnalytics: 'googleAnalytics',\n precache: 'precache-v2',\n prefix: 'workbox',\n runtime: 'runtime',\n suffix: typeof registration !== 'undefined' ? registration.scope : '',\n};\nconst _createCacheName = (cacheName) => {\n return [_cacheNameDetails.prefix, cacheName, _cacheNameDetails.suffix]\n .filter((value) => value && value.length > 0)\n .join('-');\n};\nconst eachCacheNameDetail = (fn) => {\n for (const key of Object.keys(_cacheNameDetails)) {\n fn(key);\n }\n};\nexport const cacheNames = {\n updateDetails: (details) => {\n eachCacheNameDetail((key) => {\n if (typeof details[key] === 'string') {\n _cacheNameDetails[key] = details[key];\n }\n });\n },\n getGoogleAnalyticsName: (userCacheName) => {\n return userCacheName || _createCacheName(_cacheNameDetails.googleAnalytics);\n },\n getPrecacheName: (userCacheName) => {\n return userCacheName || _createCacheName(_cacheNameDetails.precache);\n },\n getPrefix: () => {\n return _cacheNameDetails.prefix;\n },\n getRuntimeName: (userCacheName) => {\n return userCacheName || _createCacheName(_cacheNameDetails.runtime);\n },\n getSuffix: () => {\n return _cacheNameDetails.suffix;\n },\n};\n","/*\n Copyright 2019 Google LLC\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * A helper function that prevents a promise from being flagged as unused.\n *\n * @private\n **/\nexport function dontWaitFor(promise) {\n // Effective no-op.\n void promise.then(() => { });\n}\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n// Callbacks to be executed whenever there's a quota error.\n// Can't change Function type right now.\n// eslint-disable-next-line @typescript-eslint/ban-types\nconst quotaErrorCallbacks = new Set();\nexport { quotaErrorCallbacks };\n","const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);\n\nlet idbProxyableTypes;\nlet cursorAdvanceMethods;\n// This is a function to prevent it throwing up in node environments.\nfunction getIdbProxyableTypes() {\n return (idbProxyableTypes ||\n (idbProxyableTypes = [\n IDBDatabase,\n IDBObjectStore,\n IDBIndex,\n IDBCursor,\n IDBTransaction,\n ]));\n}\n// This is a function to prevent it throwing up in node environments.\nfunction getCursorAdvanceMethods() {\n return (cursorAdvanceMethods ||\n (cursorAdvanceMethods = [\n IDBCursor.prototype.advance,\n IDBCursor.prototype.continue,\n IDBCursor.prototype.continuePrimaryKey,\n ]));\n}\nconst cursorRequestMap = new WeakMap();\nconst transactionDoneMap = new WeakMap();\nconst transactionStoreNamesMap = new WeakMap();\nconst transformCache = new WeakMap();\nconst reverseTransformCache = new WeakMap();\nfunction promisifyRequest(request) {\n const promise = new Promise((resolve, reject) => {\n const unlisten = () => {\n request.removeEventListener('success', success);\n request.removeEventListener('error', error);\n };\n const success = () => {\n resolve(wrap(request.result));\n unlisten();\n };\n const error = () => {\n reject(request.error);\n unlisten();\n };\n request.addEventListener('success', success);\n request.addEventListener('error', error);\n });\n promise\n .then((value) => {\n // Since cursoring reuses the IDBRequest (*sigh*), we cache it for later retrieval\n // (see wrapFunction).\n if (value instanceof IDBCursor) {\n cursorRequestMap.set(value, request);\n }\n // Catching to avoid \"Uncaught Promise exceptions\"\n })\n .catch(() => { });\n // This mapping exists in reverseTransformCache but doesn't doesn't exist in transformCache. This\n // is because we create many promises from a single IDBRequest.\n reverseTransformCache.set(promise, request);\n return promise;\n}\nfunction cacheDonePromiseForTransaction(tx) {\n // Early bail if we've already created a done promise for this transaction.\n if (transactionDoneMap.has(tx))\n return;\n const done = new Promise((resolve, reject) => {\n const unlisten = () => {\n tx.removeEventListener('complete', complete);\n tx.removeEventListener('error', error);\n tx.removeEventListener('abort', error);\n };\n const complete = () => {\n resolve();\n unlisten();\n };\n const error = () => {\n reject(tx.error || new DOMException('AbortError', 'AbortError'));\n unlisten();\n };\n tx.addEventListener('complete', complete);\n tx.addEventListener('error', error);\n tx.addEventListener('abort', error);\n });\n // Cache it for later retrieval.\n transactionDoneMap.set(tx, done);\n}\nlet idbProxyTraps = {\n get(target, prop, receiver) {\n if (target instanceof IDBTransaction) {\n // Special handling for transaction.done.\n if (prop === 'done')\n return transactionDoneMap.get(target);\n // Polyfill for objectStoreNames because of Edge.\n if (prop === 'objectStoreNames') {\n return target.objectStoreNames || transactionStoreNamesMap.get(target);\n }\n // Make tx.store return the only store in the transaction, or undefined if there are many.\n if (prop === 'store') {\n return receiver.objectStoreNames[1]\n ? undefined\n : receiver.objectStore(receiver.objectStoreNames[0]);\n }\n }\n // Else transform whatever we get back.\n return wrap(target[prop]);\n },\n set(target, prop, value) {\n target[prop] = value;\n return true;\n },\n has(target, prop) {\n if (target instanceof IDBTransaction &&\n (prop === 'done' || prop === 'store')) {\n return true;\n }\n return prop in target;\n },\n};\nfunction replaceTraps(callback) {\n idbProxyTraps = callback(idbProxyTraps);\n}\nfunction wrapFunction(func) {\n // Due to expected object equality (which is enforced by the caching in `wrap`), we\n // only create one new func per func.\n // Edge doesn't support objectStoreNames (booo), so we polyfill it here.\n if (func === IDBDatabase.prototype.transaction &&\n !('objectStoreNames' in IDBTransaction.prototype)) {\n return function (storeNames, ...args) {\n const tx = func.call(unwrap(this), storeNames, ...args);\n transactionStoreNamesMap.set(tx, storeNames.sort ? storeNames.sort() : [storeNames]);\n return wrap(tx);\n };\n }\n // Cursor methods are special, as the behaviour is a little more different to standard IDB. In\n // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the\n // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense\n // with real promises, so each advance methods returns a new promise for the cursor object, or\n // undefined if the end of the cursor has been reached.\n if (getCursorAdvanceMethods().includes(func)) {\n return function (...args) {\n // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use\n // the original object.\n func.apply(unwrap(this), args);\n return wrap(cursorRequestMap.get(this));\n };\n }\n return function (...args) {\n // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use\n // the original object.\n return wrap(func.apply(unwrap(this), args));\n };\n}\nfunction transformCachableValue(value) {\n if (typeof value === 'function')\n return wrapFunction(value);\n // This doesn't return, it just creates a 'done' promise for the transaction,\n // which is later returned for transaction.done (see idbObjectHandler).\n if (value instanceof IDBTransaction)\n cacheDonePromiseForTransaction(value);\n if (instanceOfAny(value, getIdbProxyableTypes()))\n return new Proxy(value, idbProxyTraps);\n // Return the same value back if we're not going to transform it.\n return value;\n}\nfunction wrap(value) {\n // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because\n // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.\n if (value instanceof IDBRequest)\n return promisifyRequest(value);\n // If we've already transformed this value before, reuse the transformed value.\n // This is faster, but it also provides object equality.\n if (transformCache.has(value))\n return transformCache.get(value);\n const newValue = transformCachableValue(value);\n // Not all types are transformed.\n // These may be primitive types, so they can't be WeakMap keys.\n if (newValue !== value) {\n transformCache.set(value, newValue);\n reverseTransformCache.set(newValue, value);\n }\n return newValue;\n}\nconst unwrap = (value) => reverseTransformCache.get(value);\n\nexport { reverseTransformCache as a, instanceOfAny as i, replaceTraps as r, unwrap as u, wrap as w };\n","import { w as wrap, r as replaceTraps } from './wrap-idb-value.js';\nexport { u as unwrap, w as wrap } from './wrap-idb-value.js';\n\n/**\n * Open a database.\n *\n * @param name Name of the database.\n * @param version Schema version.\n * @param callbacks Additional callbacks.\n */\nfunction openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) {\n const request = indexedDB.open(name, version);\n const openPromise = wrap(request);\n if (upgrade) {\n request.addEventListener('upgradeneeded', (event) => {\n upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);\n });\n }\n if (blocked) {\n request.addEventListener('blocked', (event) => blocked(\n // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405\n event.oldVersion, event.newVersion, event));\n }\n openPromise\n .then((db) => {\n if (terminated)\n db.addEventListener('close', () => terminated());\n if (blocking) {\n db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event));\n }\n })\n .catch(() => { });\n return openPromise;\n}\n/**\n * Delete a database.\n *\n * @param name Name of the database.\n */\nfunction deleteDB(name, { blocked } = {}) {\n const request = indexedDB.deleteDatabase(name);\n if (blocked) {\n request.addEventListener('blocked', (event) => blocked(\n // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405\n event.oldVersion, event));\n }\n return wrap(request).then(() => undefined);\n}\n\nconst readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];\nconst writeMethods = ['put', 'add', 'delete', 'clear'];\nconst cachedMethods = new Map();\nfunction getMethod(target, prop) {\n if (!(target instanceof IDBDatabase &&\n !(prop in target) &&\n typeof prop === 'string')) {\n return;\n }\n if (cachedMethods.get(prop))\n return cachedMethods.get(prop);\n const targetFuncName = prop.replace(/FromIndex$/, '');\n const useIndex = prop !== targetFuncName;\n const isWrite = writeMethods.includes(targetFuncName);\n if (\n // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.\n !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||\n !(isWrite || readMethods.includes(targetFuncName))) {\n return;\n }\n const method = async function (storeName, ...args) {\n // isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(\n const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');\n let target = tx.store;\n if (useIndex)\n target = target.index(args.shift());\n // Must reject if op rejects.\n // If it's a write operation, must reject if tx.done rejects.\n // Must reject with op rejection first.\n // Must resolve with op value.\n // Must handle both promises (no unhandled rejections)\n return (await Promise.all([\n target[targetFuncName](...args),\n isWrite && tx.done,\n ]))[0];\n };\n cachedMethods.set(prop, method);\n return method;\n}\nreplaceTraps((oldTraps) => ({\n ...oldTraps,\n get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),\n has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),\n}));\n\nexport { deleteDB, openDB };\n","\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:expiration:7.3.0'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { openDB, deleteDB } from 'idb';\nimport '../_version.js';\nconst DB_NAME = 'workbox-expiration';\nconst CACHE_OBJECT_STORE = 'cache-entries';\nconst normalizeURL = (unNormalizedUrl) => {\n const url = new URL(unNormalizedUrl, location.href);\n url.hash = '';\n return url.href;\n};\n/**\n * Returns the timestamp model.\n *\n * @private\n */\nclass CacheTimestampsModel {\n /**\n *\n * @param {string} cacheName\n *\n * @private\n */\n constructor(cacheName) {\n this._db = null;\n this._cacheName = cacheName;\n }\n /**\n * Performs an upgrade of indexedDB.\n *\n * @param {IDBPDatabase} db\n *\n * @private\n */\n _upgradeDb(db) {\n // TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we\n // have to use the `id` keyPath here and create our own values (a\n // concatenation of `url + cacheName`) instead of simply using\n // `keyPath: ['url', 'cacheName']`, which is supported in other browsers.\n const objStore = db.createObjectStore(CACHE_OBJECT_STORE, { keyPath: 'id' });\n // TODO(philipwalton): once we don't have to support EdgeHTML, we can\n // create a single index with the keyPath `['cacheName', 'timestamp']`\n // instead of doing both these indexes.\n objStore.createIndex('cacheName', 'cacheName', { unique: false });\n objStore.createIndex('timestamp', 'timestamp', { unique: false });\n }\n /**\n * Performs an upgrade of indexedDB and deletes deprecated DBs.\n *\n * @param {IDBPDatabase} db\n *\n * @private\n */\n _upgradeDbAndDeleteOldDbs(db) {\n this._upgradeDb(db);\n if (this._cacheName) {\n void deleteDB(this._cacheName);\n }\n }\n /**\n * @param {string} url\n * @param {number} timestamp\n *\n * @private\n */\n async setTimestamp(url, timestamp) {\n url = normalizeURL(url);\n const entry = {\n url,\n timestamp,\n cacheName: this._cacheName,\n // Creating an ID from the URL and cache name won't be necessary once\n // Edge switches to Chromium and all browsers we support work with\n // array keyPaths.\n id: this._getId(url),\n };\n const db = await this.getDb();\n const tx = db.transaction(CACHE_OBJECT_STORE, 'readwrite', {\n durability: 'relaxed',\n });\n await tx.store.put(entry);\n await tx.done;\n }\n /**\n * Returns the timestamp stored for a given URL.\n *\n * @param {string} url\n * @return {number | undefined}\n *\n * @private\n */\n async getTimestamp(url) {\n const db = await this.getDb();\n const entry = await db.get(CACHE_OBJECT_STORE, this._getId(url));\n return entry === null || entry === void 0 ? void 0 : entry.timestamp;\n }\n /**\n * Iterates through all the entries in the object store (from newest to\n * oldest) and removes entries once either `maxCount` is reached or the\n * entry's timestamp is less than `minTimestamp`.\n *\n * @param {number} minTimestamp\n * @param {number} maxCount\n * @return {Array}\n *\n * @private\n */\n async expireEntries(minTimestamp, maxCount) {\n const db = await this.getDb();\n let cursor = await db\n .transaction(CACHE_OBJECT_STORE)\n .store.index('timestamp')\n .openCursor(null, 'prev');\n const entriesToDelete = [];\n let entriesNotDeletedCount = 0;\n while (cursor) {\n const result = cursor.value;\n // TODO(philipwalton): once we can use a multi-key index, we\n // won't have to check `cacheName` here.\n if (result.cacheName === this._cacheName) {\n // Delete an entry if it's older than the max age or\n // if we already have the max number allowed.\n if ((minTimestamp && result.timestamp < minTimestamp) ||\n (maxCount && entriesNotDeletedCount >= maxCount)) {\n // TODO(philipwalton): we should be able to delete the\n // entry right here, but doing so causes an iteration\n // bug in Safari stable (fixed in TP). Instead we can\n // store the keys of the entries to delete, and then\n // delete the separate transactions.\n // https://github.com/GoogleChrome/workbox/issues/1978\n // cursor.delete();\n // We only need to return the URL, not the whole entry.\n entriesToDelete.push(cursor.value);\n }\n else {\n entriesNotDeletedCount++;\n }\n }\n cursor = await cursor.continue();\n }\n // TODO(philipwalton): once the Safari bug in the following issue is fixed,\n // we should be able to remove this loop and do the entry deletion in the\n // cursor loop above:\n // https://github.com/GoogleChrome/workbox/issues/1978\n const urlsDeleted = [];\n for (const entry of entriesToDelete) {\n await db.delete(CACHE_OBJECT_STORE, entry.id);\n urlsDeleted.push(entry.url);\n }\n return urlsDeleted;\n }\n /**\n * Takes a URL and returns an ID that will be unique in the object store.\n *\n * @param {string} url\n * @return {string}\n *\n * @private\n */\n _getId(url) {\n // Creating an ID from the URL and cache name won't be necessary once\n // Edge switches to Chromium and all browsers we support work with\n // array keyPaths.\n return this._cacheName + '|' + normalizeURL(url);\n }\n /**\n * Returns an open connection to the database.\n *\n * @private\n */\n async getDb() {\n if (!this._db) {\n this._db = await openDB(DB_NAME, 1, {\n upgrade: this._upgradeDbAndDeleteOldDbs.bind(this),\n });\n }\n return this._db;\n }\n}\nexport { CacheTimestampsModel };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { dontWaitFor } from 'workbox-core/_private/dontWaitFor.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { CacheTimestampsModel } from './models/CacheTimestampsModel.js';\nimport './_version.js';\n/**\n * The `CacheExpiration` class allows you define an expiration and / or\n * limit on the number of responses stored in a\n * [`Cache`](https://developer.mozilla.org/en-US/docs/Web/API/Cache).\n *\n * @memberof workbox-expiration\n */\nclass CacheExpiration {\n /**\n * To construct a new CacheExpiration instance you must provide at least\n * one of the `config` properties.\n *\n * @param {string} cacheName Name of the cache to apply restrictions to.\n * @param {Object} config\n * @param {number} [config.maxEntries] The maximum number of entries to cache.\n * Entries used the least will be removed as the maximum is reached.\n * @param {number} [config.maxAgeSeconds] The maximum age of an entry before\n * it's treated as stale and removed.\n * @param {Object} [config.matchOptions] The [`CacheQueryOptions`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/delete#Parameters)\n * that will be used when calling `delete()` on the cache.\n */\n constructor(cacheName, config = {}) {\n this._isRunning = false;\n this._rerunRequested = false;\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(cacheName, 'string', {\n moduleName: 'workbox-expiration',\n className: 'CacheExpiration',\n funcName: 'constructor',\n paramName: 'cacheName',\n });\n if (!(config.maxEntries || config.maxAgeSeconds)) {\n throw new WorkboxError('max-entries-or-age-required', {\n moduleName: 'workbox-expiration',\n className: 'CacheExpiration',\n funcName: 'constructor',\n });\n }\n if (config.maxEntries) {\n assert.isType(config.maxEntries, 'number', {\n moduleName: 'workbox-expiration',\n className: 'CacheExpiration',\n funcName: 'constructor',\n paramName: 'config.maxEntries',\n });\n }\n if (config.maxAgeSeconds) {\n assert.isType(config.maxAgeSeconds, 'number', {\n moduleName: 'workbox-expiration',\n className: 'CacheExpiration',\n funcName: 'constructor',\n paramName: 'config.maxAgeSeconds',\n });\n }\n }\n this._maxEntries = config.maxEntries;\n this._maxAgeSeconds = config.maxAgeSeconds;\n this._matchOptions = config.matchOptions;\n this._cacheName = cacheName;\n this._timestampModel = new CacheTimestampsModel(cacheName);\n }\n /**\n * Expires entries for the given cache and given criteria.\n */\n async expireEntries() {\n if (this._isRunning) {\n this._rerunRequested = true;\n return;\n }\n this._isRunning = true;\n const minTimestamp = this._maxAgeSeconds\n ? Date.now() - this._maxAgeSeconds * 1000\n : 0;\n const urlsExpired = await this._timestampModel.expireEntries(minTimestamp, this._maxEntries);\n // Delete URLs from the cache\n const cache = await self.caches.open(this._cacheName);\n for (const url of urlsExpired) {\n await cache.delete(url, this._matchOptions);\n }\n if (process.env.NODE_ENV !== 'production') {\n if (urlsExpired.length > 0) {\n logger.groupCollapsed(`Expired ${urlsExpired.length} ` +\n `${urlsExpired.length === 1 ? 'entry' : 'entries'} and removed ` +\n `${urlsExpired.length === 1 ? 'it' : 'them'} from the ` +\n `'${this._cacheName}' cache.`);\n logger.log(`Expired the following ${urlsExpired.length === 1 ? 'URL' : 'URLs'}:`);\n urlsExpired.forEach((url) => logger.log(` ${url}`));\n logger.groupEnd();\n }\n else {\n logger.debug(`Cache expiration ran and found no entries to remove.`);\n }\n }\n this._isRunning = false;\n if (this._rerunRequested) {\n this._rerunRequested = false;\n dontWaitFor(this.expireEntries());\n }\n }\n /**\n * Update the timestamp for the given URL. This ensures the when\n * removing entries based on maximum entries, most recently used\n * is accurate or when expiring, the timestamp is up-to-date.\n *\n * @param {string} url\n */\n async updateTimestamp(url) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(url, 'string', {\n moduleName: 'workbox-expiration',\n className: 'CacheExpiration',\n funcName: 'updateTimestamp',\n paramName: 'url',\n });\n }\n await this._timestampModel.setTimestamp(url, Date.now());\n }\n /**\n * Can be used to check if a URL has expired or not before it's used.\n *\n * This requires a look up from IndexedDB, so can be slow.\n *\n * Note: This method will not remove the cached entry, call\n * `expireEntries()` to remove indexedDB and Cache entries.\n *\n * @param {string} url\n * @return {boolean}\n */\n async isURLExpired(url) {\n if (!this._maxAgeSeconds) {\n if (process.env.NODE_ENV !== 'production') {\n throw new WorkboxError(`expired-test-without-max-age`, {\n methodName: 'isURLExpired',\n paramName: 'maxAgeSeconds',\n });\n }\n return false;\n }\n else {\n const timestamp = await this._timestampModel.getTimestamp(url);\n const expireOlderThan = Date.now() - this._maxAgeSeconds * 1000;\n return timestamp !== undefined ? timestamp < expireOlderThan : true;\n }\n }\n /**\n * Removes the IndexedDB object store used to keep track of cache expiration\n * metadata.\n */\n async delete() {\n // Make sure we don't attempt another rerun if we're called in the middle of\n // a cache expiration.\n this._rerunRequested = false;\n await this._timestampModel.expireEntries(Infinity); // Expires all.\n }\n}\nexport { CacheExpiration };\n","/*\n Copyright 2020 Google LLC\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nfunction stripParams(fullURL, ignoreParams) {\n const strippedURL = new URL(fullURL);\n for (const param of ignoreParams) {\n strippedURL.searchParams.delete(param);\n }\n return strippedURL.href;\n}\n/**\n * Matches an item in the cache, ignoring specific URL params. This is similar\n * to the `ignoreSearch` option, but it allows you to ignore just specific\n * params (while continuing to match on the others).\n *\n * @private\n * @param {Cache} cache\n * @param {Request} request\n * @param {Object} matchOptions\n * @param {Array} ignoreParams\n * @return {Promise}\n */\nasync function cacheMatchIgnoreParams(cache, request, ignoreParams, matchOptions) {\n const strippedRequestURL = stripParams(request.url, ignoreParams);\n // If the request doesn't include any ignored params, match as normal.\n if (request.url === strippedRequestURL) {\n return cache.match(request, matchOptions);\n }\n // Otherwise, match by comparing keys\n const keysOptions = Object.assign(Object.assign({}, matchOptions), { ignoreSearch: true });\n const cacheKeys = await cache.keys(request, keysOptions);\n for (const cacheKey of cacheKeys) {\n const strippedCacheKeyURL = stripParams(cacheKey.url, ignoreParams);\n if (strippedRequestURL === strippedCacheKeyURL) {\n return cache.match(cacheKey, matchOptions);\n }\n }\n return;\n}\nexport { cacheMatchIgnoreParams };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * The Deferred class composes Promises in a way that allows for them to be\n * resolved or rejected from outside the constructor. In most cases promises\n * should be used directly, but Deferreds can be necessary when the logic to\n * resolve a promise must be separate.\n *\n * @private\n */\nclass Deferred {\n /**\n * Creates a promise and exposes its resolve and reject functions as methods.\n */\n constructor() {\n this.promise = new Promise((resolve, reject) => {\n this.resolve = resolve;\n this.reject = reject;\n });\n }\n}\nexport { Deferred };\n","\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:strategies:7.3.0'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { cacheMatchIgnoreParams } from 'workbox-core/_private/cacheMatchIgnoreParams.js';\nimport { Deferred } from 'workbox-core/_private/Deferred.js';\nimport { executeQuotaErrorCallbacks } from 'workbox-core/_private/executeQuotaErrorCallbacks.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { timeout } from 'workbox-core/_private/timeout.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport './_version.js';\nfunction toRequest(input) {\n return typeof input === 'string' ? new Request(input) : input;\n}\n/**\n * A class created every time a Strategy instance calls\n * {@link workbox-strategies.Strategy~handle} or\n * {@link workbox-strategies.Strategy~handleAll} that wraps all fetch and\n * cache actions around plugin callbacks and keeps track of when the strategy\n * is \"done\" (i.e. all added `event.waitUntil()` promises have resolved).\n *\n * @memberof workbox-strategies\n */\nclass StrategyHandler {\n /**\n * Creates a new instance associated with the passed strategy and event\n * that's handling the request.\n *\n * The constructor also initializes the state that will be passed to each of\n * the plugins handling this request.\n *\n * @param {workbox-strategies.Strategy} strategy\n * @param {Object} options\n * @param {Request|string} options.request A request to run this strategy for.\n * @param {ExtendableEvent} options.event The event associated with the\n * request.\n * @param {URL} [options.url]\n * @param {*} [options.params] The return value from the\n * {@link workbox-routing~matchCallback} (if applicable).\n */\n constructor(strategy, options) {\n this._cacheKeys = {};\n /**\n * The request the strategy is performing (passed to the strategy's\n * `handle()` or `handleAll()` method).\n * @name request\n * @instance\n * @type {Request}\n * @memberof workbox-strategies.StrategyHandler\n */\n /**\n * The event associated with this request.\n * @name event\n * @instance\n * @type {ExtendableEvent}\n * @memberof workbox-strategies.StrategyHandler\n */\n /**\n * A `URL` instance of `request.url` (if passed to the strategy's\n * `handle()` or `handleAll()` method).\n * Note: the `url` param will be present if the strategy was invoked\n * from a workbox `Route` object.\n * @name url\n * @instance\n * @type {URL|undefined}\n * @memberof workbox-strategies.StrategyHandler\n */\n /**\n * A `param` value (if passed to the strategy's\n * `handle()` or `handleAll()` method).\n * Note: the `param` param will be present if the strategy was invoked\n * from a workbox `Route` object and the\n * {@link workbox-routing~matchCallback} returned\n * a truthy value (it will be that value).\n * @name params\n * @instance\n * @type {*|undefined}\n * @memberof workbox-strategies.StrategyHandler\n */\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(options.event, ExtendableEvent, {\n moduleName: 'workbox-strategies',\n className: 'StrategyHandler',\n funcName: 'constructor',\n paramName: 'options.event',\n });\n }\n Object.assign(this, options);\n this.event = options.event;\n this._strategy = strategy;\n this._handlerDeferred = new Deferred();\n this._extendLifetimePromises = [];\n // Copy the plugins list (since it's mutable on the strategy),\n // so any mutations don't affect this handler instance.\n this._plugins = [...strategy.plugins];\n this._pluginStateMap = new Map();\n for (const plugin of this._plugins) {\n this._pluginStateMap.set(plugin, {});\n }\n this.event.waitUntil(this._handlerDeferred.promise);\n }\n /**\n * Fetches a given request (and invokes any applicable plugin callback\n * methods) using the `fetchOptions` (for non-navigation requests) and\n * `plugins` defined on the `Strategy` object.\n *\n * The following plugin lifecycle methods are invoked when using this method:\n * - `requestWillFetch()`\n * - `fetchDidSucceed()`\n * - `fetchDidFail()`\n *\n * @param {Request|string} input The URL or request to fetch.\n * @return {Promise}\n */\n async fetch(input) {\n const { event } = this;\n let request = toRequest(input);\n if (request.mode === 'navigate' &&\n event instanceof FetchEvent &&\n event.preloadResponse) {\n const possiblePreloadResponse = (await event.preloadResponse);\n if (possiblePreloadResponse) {\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`Using a preloaded navigation response for ` +\n `'${getFriendlyURL(request.url)}'`);\n }\n return possiblePreloadResponse;\n }\n }\n // If there is a fetchDidFail plugin, we need to save a clone of the\n // original request before it's either modified by a requestWillFetch\n // plugin or before the original request's body is consumed via fetch().\n const originalRequest = this.hasCallback('fetchDidFail')\n ? request.clone()\n : null;\n try {\n for (const cb of this.iterateCallbacks('requestWillFetch')) {\n request = await cb({ request: request.clone(), event });\n }\n }\n catch (err) {\n if (err instanceof Error) {\n throw new WorkboxError('plugin-error-request-will-fetch', {\n thrownErrorMessage: err.message,\n });\n }\n }\n // The request can be altered by plugins with `requestWillFetch` making\n // the original request (most likely from a `fetch` event) different\n // from the Request we make. Pass both to `fetchDidFail` to aid debugging.\n const pluginFilteredRequest = request.clone();\n try {\n let fetchResponse;\n // See https://github.com/GoogleChrome/workbox/issues/1796\n fetchResponse = await fetch(request, request.mode === 'navigate' ? undefined : this._strategy.fetchOptions);\n if (process.env.NODE_ENV !== 'production') {\n logger.debug(`Network request for ` +\n `'${getFriendlyURL(request.url)}' returned a response with ` +\n `status '${fetchResponse.status}'.`);\n }\n for (const callback of this.iterateCallbacks('fetchDidSucceed')) {\n fetchResponse = await callback({\n event,\n request: pluginFilteredRequest,\n response: fetchResponse,\n });\n }\n return fetchResponse;\n }\n catch (error) {\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`Network request for ` +\n `'${getFriendlyURL(request.url)}' threw an error.`, error);\n }\n // `originalRequest` will only exist if a `fetchDidFail` callback\n // is being used (see above).\n if (originalRequest) {\n await this.runCallbacks('fetchDidFail', {\n error: error,\n event,\n originalRequest: originalRequest.clone(),\n request: pluginFilteredRequest.clone(),\n });\n }\n throw error;\n }\n }\n /**\n * Calls `this.fetch()` and (in the background) runs `this.cachePut()` on\n * the response generated by `this.fetch()`.\n *\n * The call to `this.cachePut()` automatically invokes `this.waitUntil()`,\n * so you do not have to manually call `waitUntil()` on the event.\n *\n * @param {Request|string} input The request or URL to fetch and cache.\n * @return {Promise}\n */\n async fetchAndCachePut(input) {\n const response = await this.fetch(input);\n const responseClone = response.clone();\n void this.waitUntil(this.cachePut(input, responseClone));\n return response;\n }\n /**\n * Matches a request from the cache (and invokes any applicable plugin\n * callback methods) using the `cacheName`, `matchOptions`, and `plugins`\n * defined on the strategy object.\n *\n * The following plugin lifecycle methods are invoked when using this method:\n * - cacheKeyWillBeUsed()\n * - cachedResponseWillBeUsed()\n *\n * @param {Request|string} key The Request or URL to use as the cache key.\n * @return {Promise} A matching response, if found.\n */\n async cacheMatch(key) {\n const request = toRequest(key);\n let cachedResponse;\n const { cacheName, matchOptions } = this._strategy;\n const effectiveRequest = await this.getCacheKey(request, 'read');\n const multiMatchOptions = Object.assign(Object.assign({}, matchOptions), { cacheName });\n cachedResponse = await caches.match(effectiveRequest, multiMatchOptions);\n if (process.env.NODE_ENV !== 'production') {\n if (cachedResponse) {\n logger.debug(`Found a cached response in '${cacheName}'.`);\n }\n else {\n logger.debug(`No cached response found in '${cacheName}'.`);\n }\n }\n for (const callback of this.iterateCallbacks('cachedResponseWillBeUsed')) {\n cachedResponse =\n (await callback({\n cacheName,\n matchOptions,\n cachedResponse,\n request: effectiveRequest,\n event: this.event,\n })) || undefined;\n }\n return cachedResponse;\n }\n /**\n * Puts a request/response pair in the cache (and invokes any applicable\n * plugin callback methods) using the `cacheName` and `plugins` defined on\n * the strategy object.\n *\n * The following plugin lifecycle methods are invoked when using this method:\n * - cacheKeyWillBeUsed()\n * - cacheWillUpdate()\n * - cacheDidUpdate()\n *\n * @param {Request|string} key The request or URL to use as the cache key.\n * @param {Response} response The response to cache.\n * @return {Promise} `false` if a cacheWillUpdate caused the response\n * not be cached, and `true` otherwise.\n */\n async cachePut(key, response) {\n const request = toRequest(key);\n // Run in the next task to avoid blocking other cache reads.\n // https://github.com/w3c/ServiceWorker/issues/1397\n await timeout(0);\n const effectiveRequest = await this.getCacheKey(request, 'write');\n if (process.env.NODE_ENV !== 'production') {\n if (effectiveRequest.method && effectiveRequest.method !== 'GET') {\n throw new WorkboxError('attempt-to-cache-non-get-request', {\n url: getFriendlyURL(effectiveRequest.url),\n method: effectiveRequest.method,\n });\n }\n // See https://github.com/GoogleChrome/workbox/issues/2818\n const vary = response.headers.get('Vary');\n if (vary) {\n logger.debug(`The response for ${getFriendlyURL(effectiveRequest.url)} ` +\n `has a 'Vary: ${vary}' header. ` +\n `Consider setting the {ignoreVary: true} option on your strategy ` +\n `to ensure cache matching and deletion works as expected.`);\n }\n }\n if (!response) {\n if (process.env.NODE_ENV !== 'production') {\n logger.error(`Cannot cache non-existent response for ` +\n `'${getFriendlyURL(effectiveRequest.url)}'.`);\n }\n throw new WorkboxError('cache-put-with-no-response', {\n url: getFriendlyURL(effectiveRequest.url),\n });\n }\n const responseToCache = await this._ensureResponseSafeToCache(response);\n if (!responseToCache) {\n if (process.env.NODE_ENV !== 'production') {\n logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' ` +\n `will not be cached.`, responseToCache);\n }\n return false;\n }\n const { cacheName, matchOptions } = this._strategy;\n const cache = await self.caches.open(cacheName);\n const hasCacheUpdateCallback = this.hasCallback('cacheDidUpdate');\n const oldResponse = hasCacheUpdateCallback\n ? await cacheMatchIgnoreParams(\n // TODO(philipwalton): the `__WB_REVISION__` param is a precaching\n // feature. Consider into ways to only add this behavior if using\n // precaching.\n cache, effectiveRequest.clone(), ['__WB_REVISION__'], matchOptions)\n : null;\n if (process.env.NODE_ENV !== 'production') {\n logger.debug(`Updating the '${cacheName}' cache with a new Response ` +\n `for ${getFriendlyURL(effectiveRequest.url)}.`);\n }\n try {\n await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache);\n }\n catch (error) {\n if (error instanceof Error) {\n // See https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-QuotaExceededError\n if (error.name === 'QuotaExceededError') {\n await executeQuotaErrorCallbacks();\n }\n throw error;\n }\n }\n for (const callback of this.iterateCallbacks('cacheDidUpdate')) {\n await callback({\n cacheName,\n oldResponse,\n newResponse: responseToCache.clone(),\n request: effectiveRequest,\n event: this.event,\n });\n }\n return true;\n }\n /**\n * Checks the list of plugins for the `cacheKeyWillBeUsed` callback, and\n * executes any of those callbacks found in sequence. The final `Request`\n * object returned by the last plugin is treated as the cache key for cache\n * reads and/or writes. If no `cacheKeyWillBeUsed` plugin callbacks have\n * been registered, the passed request is returned unmodified\n *\n * @param {Request} request\n * @param {string} mode\n * @return {Promise}\n */\n async getCacheKey(request, mode) {\n const key = `${request.url} | ${mode}`;\n if (!this._cacheKeys[key]) {\n let effectiveRequest = request;\n for (const callback of this.iterateCallbacks('cacheKeyWillBeUsed')) {\n effectiveRequest = toRequest(await callback({\n mode,\n request: effectiveRequest,\n event: this.event,\n // params has a type any can't change right now.\n params: this.params, // eslint-disable-line\n }));\n }\n this._cacheKeys[key] = effectiveRequest;\n }\n return this._cacheKeys[key];\n }\n /**\n * Returns true if the strategy has at least one plugin with the given\n * callback.\n *\n * @param {string} name The name of the callback to check for.\n * @return {boolean}\n */\n hasCallback(name) {\n for (const plugin of this._strategy.plugins) {\n if (name in plugin) {\n return true;\n }\n }\n return false;\n }\n /**\n * Runs all plugin callbacks matching the given name, in order, passing the\n * given param object (merged ith the current plugin state) as the only\n * argument.\n *\n * Note: since this method runs all plugins, it's not suitable for cases\n * where the return value of a callback needs to be applied prior to calling\n * the next callback. See\n * {@link workbox-strategies.StrategyHandler#iterateCallbacks}\n * below for how to handle that case.\n *\n * @param {string} name The name of the callback to run within each plugin.\n * @param {Object} param The object to pass as the first (and only) param\n * when executing each callback. This object will be merged with the\n * current plugin state prior to callback execution.\n */\n async runCallbacks(name, param) {\n for (const callback of this.iterateCallbacks(name)) {\n // TODO(philipwalton): not sure why `any` is needed. It seems like\n // this should work with `as WorkboxPluginCallbackParam[C]`.\n await callback(param);\n }\n }\n /**\n * Accepts a callback and returns an iterable of matching plugin callbacks,\n * where each callback is wrapped with the current handler state (i.e. when\n * you call each callback, whatever object parameter you pass it will\n * be merged with the plugin's current state).\n *\n * @param {string} name The name fo the callback to run\n * @return {Array}\n */\n *iterateCallbacks(name) {\n for (const plugin of this._strategy.plugins) {\n if (typeof plugin[name] === 'function') {\n const state = this._pluginStateMap.get(plugin);\n const statefulCallback = (param) => {\n const statefulParam = Object.assign(Object.assign({}, param), { state });\n // TODO(philipwalton): not sure why `any` is needed. It seems like\n // this should work with `as WorkboxPluginCallbackParam[C]`.\n return plugin[name](statefulParam);\n };\n yield statefulCallback;\n }\n }\n }\n /**\n * Adds a promise to the\n * [extend lifetime promises]{@link https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises}\n * of the event associated with the request being handled (usually a\n * `FetchEvent`).\n *\n * Note: you can await\n * {@link workbox-strategies.StrategyHandler~doneWaiting}\n * to know when all added promises have settled.\n *\n * @param {Promise} promise A promise to add to the extend lifetime promises\n * of the event that triggered the request.\n */\n waitUntil(promise) {\n this._extendLifetimePromises.push(promise);\n return promise;\n }\n /**\n * Returns a promise that resolves once all promises passed to\n * {@link workbox-strategies.StrategyHandler~waitUntil}\n * have settled.\n *\n * Note: any work done after `doneWaiting()` settles should be manually\n * passed to an event's `waitUntil()` method (not this handler's\n * `waitUntil()` method), otherwise the service worker thread may be killed\n * prior to your work completing.\n */\n async doneWaiting() {\n while (this._extendLifetimePromises.length) {\n const promises = this._extendLifetimePromises.splice(0);\n const result = await Promise.allSettled(promises);\n const firstRejection = result.find((i) => i.status === 'rejected');\n if (firstRejection) {\n throw firstRejection.reason;\n }\n }\n }\n /**\n * Stops running the strategy and immediately resolves any pending\n * `waitUntil()` promises.\n */\n destroy() {\n this._handlerDeferred.resolve(null);\n }\n /**\n * This method will call cacheWillUpdate on the available plugins (or use\n * status === 200) to determine if the Response is safe and valid to cache.\n *\n * @param {Request} options.request\n * @param {Response} options.response\n * @return {Promise}\n *\n * @private\n */\n async _ensureResponseSafeToCache(response) {\n let responseToCache = response;\n let pluginsUsed = false;\n for (const callback of this.iterateCallbacks('cacheWillUpdate')) {\n responseToCache =\n (await callback({\n request: this.request,\n response: responseToCache,\n event: this.event,\n })) || undefined;\n pluginsUsed = true;\n if (!responseToCache) {\n break;\n }\n }\n if (!pluginsUsed) {\n if (responseToCache && responseToCache.status !== 200) {\n responseToCache = undefined;\n }\n if (process.env.NODE_ENV !== 'production') {\n if (responseToCache) {\n if (responseToCache.status !== 200) {\n if (responseToCache.status === 0) {\n logger.warn(`The response for '${this.request.url}' ` +\n `is an opaque response. The caching strategy that you're ` +\n `using will not cache opaque responses by default.`);\n }\n else {\n logger.debug(`The response for '${this.request.url}' ` +\n `returned a status code of '${response.status}' and won't ` +\n `be cached as a result.`);\n }\n }\n }\n }\n }\n return responseToCache;\n }\n}\nexport { StrategyHandler };\n","/*\n Copyright 2019 Google LLC\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * Returns a promise that resolves and the passed number of milliseconds.\n * This utility is an async/await-friendly version of `setTimeout`.\n *\n * @param {number} ms\n * @return {Promise}\n * @private\n */\nexport function timeout(ms) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nconst getFriendlyURL = (url) => {\n const urlObj = new URL(String(url), location.href);\n // See https://github.com/GoogleChrome/workbox/issues/2323\n // We want to include everything, except for the origin if it's same-origin.\n return urlObj.href.replace(new RegExp(`^${location.origin}`), '');\n};\nexport { getFriendlyURL };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from '../_private/logger.js';\nimport { quotaErrorCallbacks } from '../models/quotaErrorCallbacks.js';\nimport '../_version.js';\n/**\n * Runs all of the callback functions, one at a time sequentially, in the order\n * in which they were registered.\n *\n * @memberof workbox-core\n * @private\n */\nasync function executeQuotaErrorCallbacks() {\n if (process.env.NODE_ENV !== 'production') {\n logger.log(`About to run ${quotaErrorCallbacks.size} ` +\n `callbacks to clean up caches.`);\n }\n for (const callback of quotaErrorCallbacks) {\n await callback();\n if (process.env.NODE_ENV !== 'production') {\n logger.log(callback, 'is complete.');\n }\n }\n if (process.env.NODE_ENV !== 'production') {\n logger.log('Finished running callbacks.');\n }\n}\nexport { executeQuotaErrorCallbacks };\n","/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { cacheNames } from 'workbox-core/_private/cacheNames.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { StrategyHandler } from './StrategyHandler.js';\nimport './_version.js';\n/**\n * An abstract base class that all other strategy classes must extend from:\n *\n * @memberof workbox-strategies\n */\nclass Strategy {\n /**\n * Creates a new instance of the strategy and sets all documented option\n * properties as public instance properties.\n *\n * Note: if a custom strategy class extends the base Strategy class and does\n * not need more than these properties, it does not need to define its own\n * constructor.\n *\n * @param {Object} [options]\n * @param {string} [options.cacheName] Cache name to store and retrieve\n * requests. Defaults to the cache names provided by\n * {@link workbox-core.cacheNames}.\n * @param {Array} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n * to use in conjunction with this caching strategy.\n * @param {Object} [options.fetchOptions] Values passed along to the\n * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)\n * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)\n * `fetch()` requests made by this strategy.\n * @param {Object} [options.matchOptions] The\n * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions}\n * for any `cache.match()` or `cache.put()` calls made by this strategy.\n */\n constructor(options = {}) {\n /**\n * Cache name to store and retrieve\n * requests. Defaults to the cache names provided by\n * {@link workbox-core.cacheNames}.\n *\n * @type {string}\n */\n this.cacheName = cacheNames.getRuntimeName(options.cacheName);\n /**\n * The list\n * [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n * used by this strategy.\n *\n * @type {Array}\n */\n this.plugins = options.plugins || [];\n /**\n * Values passed along to the\n * [`init`]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters}\n * of all fetch() requests made by this strategy.\n *\n * @type {Object}\n */\n this.fetchOptions = options.fetchOptions;\n /**\n * The\n * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions}\n * for any `cache.match()` or `cache.put()` calls made by this strategy.\n *\n * @type {Object}\n */\n this.matchOptions = options.matchOptions;\n }\n /**\n * Perform a request strategy and returns a `Promise` that will resolve with\n * a `Response`, invoking all relevant plugin callbacks.\n *\n * When a strategy instance is registered with a Workbox\n * {@link workbox-routing.Route}, this method is automatically\n * called when the route matches.\n *\n * Alternatively, this method can be used in a standalone `FetchEvent`\n * listener by passing it to `event.respondWith()`.\n *\n * @param {FetchEvent|Object} options A `FetchEvent` or an object with the\n * properties listed below.\n * @param {Request|string} options.request A request to run this strategy for.\n * @param {ExtendableEvent} options.event The event associated with the\n * request.\n * @param {URL} [options.url]\n * @param {*} [options.params]\n */\n handle(options) {\n const [responseDone] = this.handleAll(options);\n return responseDone;\n }\n /**\n * Similar to {@link workbox-strategies.Strategy~handle}, but\n * instead of just returning a `Promise` that resolves to a `Response` it\n * it will return an tuple of `[response, done]` promises, where the former\n * (`response`) is equivalent to what `handle()` returns, and the latter is a\n * Promise that will resolve once any promises that were added to\n * `event.waitUntil()` as part of performing the strategy have completed.\n *\n * You can await the `done` promise to ensure any extra work performed by\n * the strategy (usually caching responses) completes successfully.\n *\n * @param {FetchEvent|Object} options A `FetchEvent` or an object with the\n * properties listed below.\n * @param {Request|string} options.request A request to run this strategy for.\n * @param {ExtendableEvent} options.event The event associated with the\n * request.\n * @param {URL} [options.url]\n * @param {*} [options.params]\n * @return {Array} A tuple of [response, done]\n * promises that can be used to determine when the response resolves as\n * well as when the handler has completed all its work.\n */\n handleAll(options) {\n // Allow for flexible options to be passed.\n if (options instanceof FetchEvent) {\n options = {\n event: options,\n request: options.request,\n };\n }\n const event = options.event;\n const request = typeof options.request === 'string'\n ? new Request(options.request)\n : options.request;\n const params = 'params' in options ? options.params : undefined;\n const handler = new StrategyHandler(this, { event, request, params });\n const responseDone = this._getResponse(handler, request, event);\n const handlerDone = this._awaitComplete(responseDone, handler, request, event);\n // Return an array of promises, suitable for use with Promise.all().\n return [responseDone, handlerDone];\n }\n async _getResponse(handler, request, event) {\n await handler.runCallbacks('handlerWillStart', { event, request });\n let response = undefined;\n try {\n response = await this._handle(request, handler);\n // The \"official\" Strategy subclasses all throw this error automatically,\n // but in case a third-party Strategy doesn't, ensure that we have a\n // consistent failure when there's no response or an error response.\n if (!response || response.type === 'error') {\n throw new WorkboxError('no-response', { url: request.url });\n }\n }\n catch (error) {\n if (error instanceof Error) {\n for (const callback of handler.iterateCallbacks('handlerDidError')) {\n response = await callback({ error, event, request });\n if (response) {\n break;\n }\n }\n }\n if (!response) {\n throw error;\n }\n else if (process.env.NODE_ENV !== 'production') {\n logger.log(`While responding to '${getFriendlyURL(request.url)}', ` +\n `an ${error instanceof Error ? error.toString() : ''} error occurred. Using a fallback response provided by ` +\n `a handlerDidError plugin.`);\n }\n }\n for (const callback of handler.iterateCallbacks('handlerWillRespond')) {\n response = await callback({ event, request, response });\n }\n return response;\n }\n async _awaitComplete(responseDone, handler, request, event) {\n let response;\n let error;\n try {\n response = await responseDone;\n }\n catch (error) {\n // Ignore errors, as response errors should be caught via the `response`\n // promise above. The `done` promise will only throw for errors in\n // promises passed to `handler.waitUntil()`.\n }\n try {\n await handler.runCallbacks('handlerDidRespond', {\n event,\n request,\n response,\n });\n await handler.doneWaiting();\n }\n catch (waitUntilError) {\n if (waitUntilError instanceof Error) {\n error = waitUntilError;\n }\n }\n await handler.runCallbacks('handlerDidComplete', {\n event,\n request,\n response,\n error: error,\n });\n handler.destroy();\n if (error) {\n throw error;\n }\n }\n}\nexport { Strategy };\n/**\n * Classes extending the `Strategy` based class should implement this method,\n * and leverage the {@link workbox-strategies.StrategyHandler}\n * arg to perform all fetching and cache logic, which will ensure all relevant\n * cache, cache options, fetch options and plugins are used (per the current\n * strategy instance).\n *\n * @name _handle\n * @instance\n * @abstract\n * @function\n * @param {Request} request\n * @param {workbox-strategies.StrategyHandler} handler\n * @return {Promise}\n *\n * @memberof workbox-strategies.Strategy\n */\n","\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:cacheable-response:7.3.0'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport './_version.js';\n/**\n * This class allows you to set up rules determining what\n * status codes and/or headers need to be present in order for a\n * [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)\n * to be considered cacheable.\n *\n * @memberof workbox-cacheable-response\n */\nclass CacheableResponse {\n /**\n * To construct a new CacheableResponse instance you must provide at least\n * one of the `config` properties.\n *\n * If both `statuses` and `headers` are specified, then both conditions must\n * be met for the `Response` to be considered cacheable.\n *\n * @param {Object} config\n * @param {Array} [config.statuses] One or more status codes that a\n * `Response` can have and be considered cacheable.\n * @param {Object} [config.headers] A mapping of header names\n * and expected values that a `Response` can have and be considered cacheable.\n * If multiple headers are provided, only one needs to be present.\n */\n constructor(config = {}) {\n if (process.env.NODE_ENV !== 'production') {\n if (!(config.statuses || config.headers)) {\n throw new WorkboxError('statuses-or-headers-required', {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'constructor',\n });\n }\n if (config.statuses) {\n assert.isArray(config.statuses, {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'constructor',\n paramName: 'config.statuses',\n });\n }\n if (config.headers) {\n assert.isType(config.headers, 'object', {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'constructor',\n paramName: 'config.headers',\n });\n }\n }\n this._statuses = config.statuses;\n this._headers = config.headers;\n }\n /**\n * Checks a response to see whether it's cacheable or not, based on this\n * object's configuration.\n *\n * @param {Response} response The response whose cacheability is being\n * checked.\n * @return {boolean} `true` if the `Response` is cacheable, and `false`\n * otherwise.\n */\n isResponseCacheable(response) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(response, Response, {\n moduleName: 'workbox-cacheable-response',\n className: 'CacheableResponse',\n funcName: 'isResponseCacheable',\n paramName: 'response',\n });\n }\n let cacheable = true;\n if (this._statuses) {\n cacheable = this._statuses.includes(response.status);\n }\n if (this._headers && cacheable) {\n cacheable = Object.keys(this._headers).some((headerName) => {\n return response.headers.get(headerName) === this._headers[headerName];\n });\n }\n if (process.env.NODE_ENV !== 'production') {\n if (!cacheable) {\n logger.groupCollapsed(`The request for ` +\n `'${getFriendlyURL(response.url)}' returned a response that does ` +\n `not meet the criteria for being cached.`);\n logger.groupCollapsed(`View cacheability criteria here.`);\n logger.log(`Cacheable statuses: ` + JSON.stringify(this._statuses));\n logger.log(`Cacheable headers: ` + JSON.stringify(this._headers, null, 2));\n logger.groupEnd();\n const logFriendlyHeaders = {};\n response.headers.forEach((value, key) => {\n logFriendlyHeaders[key] = value;\n });\n logger.groupCollapsed(`View response status and headers here.`);\n logger.log(`Response status: ${response.status}`);\n logger.log(`Response headers: ` + JSON.stringify(logFriendlyHeaders, null, 2));\n logger.groupEnd();\n logger.groupCollapsed(`View full response details here.`);\n logger.log(response.headers);\n logger.log(response);\n logger.groupEnd();\n logger.groupEnd();\n }\n }\n return cacheable;\n }\n}\nexport { CacheableResponse };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nexport const cacheOkAndOpaquePlugin = {\n /**\n * Returns a valid response (to allow caching) if the status is 200 (OK) or\n * 0 (opaque).\n *\n * @param {Object} options\n * @param {Response} options.response\n * @return {Response|null}\n *\n * @private\n */\n cacheWillUpdate: async ({ response }) => {\n if (response.status === 200 || response.status === 0) {\n return response;\n }\n return null;\n },\n};\n","/*\n Copyright 2020 Google LLC\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * A utility method that makes it easier to use `event.waitUntil` with\n * async functions and return the result.\n *\n * @param {ExtendableEvent} event\n * @param {Function} asyncFn\n * @return {Function}\n * @private\n */\nfunction waitUntil(event, asyncFn) {\n const returnPromise = asyncFn();\n event.waitUntil(returnPromise);\n return returnPromise;\n}\nexport { waitUntil };\n","\"use strict\";\n// @ts-ignore\ntry {\n self['workbox:precaching:7.3.0'] && _();\n}\ncatch (e) { }\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport '../_version.js';\n// Name of the search parameter used to store revision info.\nconst REVISION_SEARCH_PARAM = '__WB_REVISION__';\n/**\n * Converts a manifest entry into a versioned URL suitable for precaching.\n *\n * @param {Object|string} entry\n * @return {string} A URL with versioning info.\n *\n * @private\n * @memberof workbox-precaching\n */\nexport function createCacheKey(entry) {\n if (!entry) {\n throw new WorkboxError('add-to-cache-list-unexpected-type', { entry });\n }\n // If a precache manifest entry is a string, it's assumed to be a versioned\n // URL, like '/app.abcd1234.js'. Return as-is.\n if (typeof entry === 'string') {\n const urlObject = new URL(entry, location.href);\n return {\n cacheKey: urlObject.href,\n url: urlObject.href,\n };\n }\n const { revision, url } = entry;\n if (!url) {\n throw new WorkboxError('add-to-cache-list-unexpected-type', { entry });\n }\n // If there's just a URL and no revision, then it's also assumed to be a\n // versioned URL.\n if (!revision) {\n const urlObject = new URL(url, location.href);\n return {\n cacheKey: urlObject.href,\n url: urlObject.href,\n };\n }\n // Otherwise, construct a properly versioned URL using the custom Workbox\n // search parameter along with the revision info.\n const cacheKeyURL = new URL(url, location.href);\n const originalURL = new URL(url, location.href);\n cacheKeyURL.searchParams.set(REVISION_SEARCH_PARAM, revision);\n return {\n cacheKey: cacheKeyURL.href,\n url: originalURL.href,\n };\n}\n","/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * A plugin, designed to be used with PrecacheController, to determine the\n * of assets that were updated (or not updated) during the install event.\n *\n * @private\n */\nclass PrecacheInstallReportPlugin {\n constructor() {\n this.updatedURLs = [];\n this.notUpdatedURLs = [];\n this.handlerWillStart = async ({ request, state, }) => {\n // TODO: `state` should never be undefined...\n if (state) {\n state.originalRequest = request;\n }\n };\n this.cachedResponseWillBeUsed = async ({ event, state, cachedResponse, }) => {\n if (event.type === 'install') {\n if (state &&\n state.originalRequest &&\n state.originalRequest instanceof Request) {\n // TODO: `state` should never be undefined...\n const url = state.originalRequest.url;\n if (cachedResponse) {\n this.notUpdatedURLs.push(url);\n }\n else {\n this.updatedURLs.push(url);\n }\n }\n }\n return cachedResponse;\n };\n }\n}\nexport { PrecacheInstallReportPlugin };\n","/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * A plugin, designed to be used with PrecacheController, to translate URLs into\n * the corresponding cache key, based on the current revision info.\n *\n * @private\n */\nclass PrecacheCacheKeyPlugin {\n constructor({ precacheController }) {\n this.cacheKeyWillBeUsed = async ({ request, params, }) => {\n // Params is type any, can't change right now.\n /* eslint-disable */\n const cacheKey = (params === null || params === void 0 ? void 0 : params.cacheKey) ||\n this._precacheController.getCacheKeyForURL(request.url);\n /* eslint-enable */\n return cacheKey\n ? new Request(cacheKey, { headers: request.headers })\n : request;\n };\n this._precacheController = precacheController;\n }\n}\nexport { PrecacheCacheKeyPlugin };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\nlet supportStatus;\n/**\n * A utility function that determines whether the current browser supports\n * constructing a new `Response` from a `response.body` stream.\n *\n * @return {boolean} `true`, if the current browser can successfully\n * construct a `Response` from a `response.body` stream, `false` otherwise.\n *\n * @private\n */\nfunction canConstructResponseFromBodyStream() {\n if (supportStatus === undefined) {\n const testResponse = new Response('');\n if ('body' in testResponse) {\n try {\n new Response(testResponse.body);\n supportStatus = true;\n }\n catch (error) {\n supportStatus = false;\n }\n }\n supportStatus = false;\n }\n return supportStatus;\n}\nexport { canConstructResponseFromBodyStream };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { PrecacheController } from '../PrecacheController.js';\nimport '../_version.js';\nlet precacheController;\n/**\n * @return {PrecacheController}\n * @private\n */\nexport const getOrCreatePrecacheController = () => {\n if (!precacheController) {\n precacheController = new PrecacheController();\n }\n return precacheController;\n};\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { canConstructResponseFromBodyStream } from './_private/canConstructResponseFromBodyStream.js';\nimport { WorkboxError } from './_private/WorkboxError.js';\nimport './_version.js';\n/**\n * Allows developers to copy a response and modify its `headers`, `status`,\n * or `statusText` values (the values settable via a\n * [`ResponseInit`]{@link https://developer.mozilla.org/en-US/docs/Web/API/Response/Response#Syntax}\n * object in the constructor).\n * To modify these values, pass a function as the second argument. That\n * function will be invoked with a single object with the response properties\n * `{headers, status, statusText}`. The return value of this function will\n * be used as the `ResponseInit` for the new `Response`. To change the values\n * either modify the passed parameter(s) and return it, or return a totally\n * new object.\n *\n * This method is intentionally limited to same-origin responses, regardless of\n * whether CORS was used or not.\n *\n * @param {Response} response\n * @param {Function} modifier\n * @memberof workbox-core\n */\nasync function copyResponse(response, modifier) {\n let origin = null;\n // If response.url isn't set, assume it's cross-origin and keep origin null.\n if (response.url) {\n const responseURL = new URL(response.url);\n origin = responseURL.origin;\n }\n if (origin !== self.location.origin) {\n throw new WorkboxError('cross-origin-copy-response', { origin });\n }\n const clonedResponse = response.clone();\n // Create a fresh `ResponseInit` object by cloning the headers.\n const responseInit = {\n headers: new Headers(clonedResponse.headers),\n status: clonedResponse.status,\n statusText: clonedResponse.statusText,\n };\n // Apply any user modifications.\n const modifiedResponseInit = modifier ? modifier(responseInit) : responseInit;\n // Create the new response from the body stream and `ResponseInit`\n // modifications. Note: not all browsers support the Response.body stream,\n // so fall back to reading the entire body into memory as a blob.\n const body = canConstructResponseFromBodyStream()\n ? clonedResponse.body\n : await clonedResponse.blob();\n return new Response(body, modifiedResponseInit);\n}\nexport { copyResponse };\n","/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { copyResponse } from 'workbox-core/copyResponse.js';\nimport { cacheNames } from 'workbox-core/_private/cacheNames.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { Strategy } from 'workbox-strategies/Strategy.js';\nimport './_version.js';\n/**\n * A {@link workbox-strategies.Strategy} implementation\n * specifically designed to work with\n * {@link workbox-precaching.PrecacheController}\n * to both cache and fetch precached assets.\n *\n * Note: an instance of this class is created automatically when creating a\n * `PrecacheController`; it's generally not necessary to create this yourself.\n *\n * @extends workbox-strategies.Strategy\n * @memberof workbox-precaching\n */\nclass PrecacheStrategy extends Strategy {\n /**\n *\n * @param {Object} [options]\n * @param {string} [options.cacheName] Cache name to store and retrieve\n * requests. Defaults to the cache names provided by\n * {@link workbox-core.cacheNames}.\n * @param {Array} [options.plugins] {@link https://developers.google.com/web/tools/workbox/guides/using-plugins|Plugins}\n * to use in conjunction with this caching strategy.\n * @param {Object} [options.fetchOptions] Values passed along to the\n * {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters|init}\n * of all fetch() requests made by this strategy.\n * @param {Object} [options.matchOptions] The\n * {@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions|CacheQueryOptions}\n * for any `cache.match()` or `cache.put()` calls made by this strategy.\n * @param {boolean} [options.fallbackToNetwork=true] Whether to attempt to\n * get the response from the network if there's a precache miss.\n */\n constructor(options = {}) {\n options.cacheName = cacheNames.getPrecacheName(options.cacheName);\n super(options);\n this._fallbackToNetwork =\n options.fallbackToNetwork === false ? false : true;\n // Redirected responses cannot be used to satisfy a navigation request, so\n // any redirected response must be \"copied\" rather than cloned, so the new\n // response doesn't contain the `redirected` flag. See:\n // https://bugs.chromium.org/p/chromium/issues/detail?id=669363&desc=2#c1\n this.plugins.push(PrecacheStrategy.copyRedirectedCacheableResponsesPlugin);\n }\n /**\n * @private\n * @param {Request|string} request A request to run this strategy for.\n * @param {workbox-strategies.StrategyHandler} handler The event that\n * triggered the request.\n * @return {Promise}\n */\n async _handle(request, handler) {\n const response = await handler.cacheMatch(request);\n if (response) {\n return response;\n }\n // If this is an `install` event for an entry that isn't already cached,\n // then populate the cache.\n if (handler.event && handler.event.type === 'install') {\n return await this._handleInstall(request, handler);\n }\n // Getting here means something went wrong. An entry that should have been\n // precached wasn't found in the cache.\n return await this._handleFetch(request, handler);\n }\n async _handleFetch(request, handler) {\n let response;\n const params = (handler.params || {});\n // Fall back to the network if we're configured to do so.\n if (this._fallbackToNetwork) {\n if (process.env.NODE_ENV !== 'production') {\n logger.warn(`The precached response for ` +\n `${getFriendlyURL(request.url)} in ${this.cacheName} was not ` +\n `found. Falling back to the network.`);\n }\n const integrityInManifest = params.integrity;\n const integrityInRequest = request.integrity;\n const noIntegrityConflict = !integrityInRequest || integrityInRequest === integrityInManifest;\n // Do not add integrity if the original request is no-cors\n // See https://github.com/GoogleChrome/workbox/issues/3096\n response = await handler.fetch(new Request(request, {\n integrity: request.mode !== 'no-cors'\n ? integrityInRequest || integrityInManifest\n : undefined,\n }));\n // It's only \"safe\" to repair the cache if we're using SRI to guarantee\n // that the response matches the precache manifest's expectations,\n // and there's either a) no integrity property in the incoming request\n // or b) there is an integrity, and it matches the precache manifest.\n // See https://github.com/GoogleChrome/workbox/issues/2858\n // Also if the original request users no-cors we don't use integrity.\n // See https://github.com/GoogleChrome/workbox/issues/3096\n if (integrityInManifest &&\n noIntegrityConflict &&\n request.mode !== 'no-cors') {\n this._useDefaultCacheabilityPluginIfNeeded();\n const wasCached = await handler.cachePut(request, response.clone());\n if (process.env.NODE_ENV !== 'production') {\n if (wasCached) {\n logger.log(`A response for ${getFriendlyURL(request.url)} ` +\n `was used to \"repair\" the precache.`);\n }\n }\n }\n }\n else {\n // This shouldn't normally happen, but there are edge cases:\n // https://github.com/GoogleChrome/workbox/issues/1441\n throw new WorkboxError('missing-precache-entry', {\n cacheName: this.cacheName,\n url: request.url,\n });\n }\n if (process.env.NODE_ENV !== 'production') {\n const cacheKey = params.cacheKey || (await handler.getCacheKey(request, 'read'));\n // Workbox is going to handle the route.\n // print the routing details to the console.\n logger.groupCollapsed(`Precaching is responding to: ` + getFriendlyURL(request.url));\n logger.log(`Serving the precached url: ${getFriendlyURL(cacheKey instanceof Request ? cacheKey.url : cacheKey)}`);\n logger.groupCollapsed(`View request details here.`);\n logger.log(request);\n logger.groupEnd();\n logger.groupCollapsed(`View response details here.`);\n logger.log(response);\n logger.groupEnd();\n logger.groupEnd();\n }\n return response;\n }\n async _handleInstall(request, handler) {\n this._useDefaultCacheabilityPluginIfNeeded();\n const response = await handler.fetch(request);\n // Make sure we defer cachePut() until after we know the response\n // should be cached; see https://github.com/GoogleChrome/workbox/issues/2737\n const wasCached = await handler.cachePut(request, response.clone());\n if (!wasCached) {\n // Throwing here will lead to the `install` handler failing, which\n // we want to do if *any* of the responses aren't safe to cache.\n throw new WorkboxError('bad-precaching-response', {\n url: request.url,\n status: response.status,\n });\n }\n return response;\n }\n /**\n * This method is complex, as there a number of things to account for:\n *\n * The `plugins` array can be set at construction, and/or it might be added to\n * to at any time before the strategy is used.\n *\n * At the time the strategy is used (i.e. during an `install` event), there\n * needs to be at least one plugin that implements `cacheWillUpdate` in the\n * array, other than `copyRedirectedCacheableResponsesPlugin`.\n *\n * - If this method is called and there are no suitable `cacheWillUpdate`\n * plugins, we need to add `defaultPrecacheCacheabilityPlugin`.\n *\n * - If this method is called and there is exactly one `cacheWillUpdate`, then\n * we don't have to do anything (this might be a previously added\n * `defaultPrecacheCacheabilityPlugin`, or it might be a custom plugin).\n *\n * - If this method is called and there is more than one `cacheWillUpdate`,\n * then we need to check if one is `defaultPrecacheCacheabilityPlugin`. If so,\n * we need to remove it. (This situation is unlikely, but it could happen if\n * the strategy is used multiple times, the first without a `cacheWillUpdate`,\n * and then later on after manually adding a custom `cacheWillUpdate`.)\n *\n * See https://github.com/GoogleChrome/workbox/issues/2737 for more context.\n *\n * @private\n */\n _useDefaultCacheabilityPluginIfNeeded() {\n let defaultPluginIndex = null;\n let cacheWillUpdatePluginCount = 0;\n for (const [index, plugin] of this.plugins.entries()) {\n // Ignore the copy redirected plugin when determining what to do.\n if (plugin === PrecacheStrategy.copyRedirectedCacheableResponsesPlugin) {\n continue;\n }\n // Save the default plugin's index, in case it needs to be removed.\n if (plugin === PrecacheStrategy.defaultPrecacheCacheabilityPlugin) {\n defaultPluginIndex = index;\n }\n if (plugin.cacheWillUpdate) {\n cacheWillUpdatePluginCount++;\n }\n }\n if (cacheWillUpdatePluginCount === 0) {\n this.plugins.push(PrecacheStrategy.defaultPrecacheCacheabilityPlugin);\n }\n else if (cacheWillUpdatePluginCount > 1 && defaultPluginIndex !== null) {\n // Only remove the default plugin; multiple custom plugins are allowed.\n this.plugins.splice(defaultPluginIndex, 1);\n }\n // Nothing needs to be done if cacheWillUpdatePluginCount is 1\n }\n}\nPrecacheStrategy.defaultPrecacheCacheabilityPlugin = {\n async cacheWillUpdate({ response }) {\n if (!response || response.status >= 400) {\n return null;\n }\n return response;\n },\n};\nPrecacheStrategy.copyRedirectedCacheableResponsesPlugin = {\n async cacheWillUpdate({ response }) {\n return response.redirected ? await copyResponse(response) : response;\n },\n};\nexport { PrecacheStrategy };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { cacheNames } from 'workbox-core/_private/cacheNames.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { waitUntil } from 'workbox-core/_private/waitUntil.js';\nimport { createCacheKey } from './utils/createCacheKey.js';\nimport { PrecacheInstallReportPlugin } from './utils/PrecacheInstallReportPlugin.js';\nimport { PrecacheCacheKeyPlugin } from './utils/PrecacheCacheKeyPlugin.js';\nimport { printCleanupDetails } from './utils/printCleanupDetails.js';\nimport { printInstallDetails } from './utils/printInstallDetails.js';\nimport { PrecacheStrategy } from './PrecacheStrategy.js';\nimport './_version.js';\n/**\n * Performs efficient precaching of assets.\n *\n * @memberof workbox-precaching\n */\nclass PrecacheController {\n /**\n * Create a new PrecacheController.\n *\n * @param {Object} [options]\n * @param {string} [options.cacheName] The cache to use for precaching.\n * @param {string} [options.plugins] Plugins to use when precaching as well\n * as responding to fetch events for precached assets.\n * @param {boolean} [options.fallbackToNetwork=true] Whether to attempt to\n * get the response from the network if there's a precache miss.\n */\n constructor({ cacheName, plugins = [], fallbackToNetwork = true, } = {}) {\n this._urlsToCacheKeys = new Map();\n this._urlsToCacheModes = new Map();\n this._cacheKeysToIntegrities = new Map();\n this._strategy = new PrecacheStrategy({\n cacheName: cacheNames.getPrecacheName(cacheName),\n plugins: [\n ...plugins,\n new PrecacheCacheKeyPlugin({ precacheController: this }),\n ],\n fallbackToNetwork,\n });\n // Bind the install and activate methods to the instance.\n this.install = this.install.bind(this);\n this.activate = this.activate.bind(this);\n }\n /**\n * @type {workbox-precaching.PrecacheStrategy} The strategy created by this controller and\n * used to cache assets and respond to fetch events.\n */\n get strategy() {\n return this._strategy;\n }\n /**\n * Adds items to the precache list, removing any duplicates and\n * stores the files in the\n * {@link workbox-core.cacheNames|\"precache cache\"} when the service\n * worker installs.\n *\n * This method can be called multiple times.\n *\n * @param {Array} [entries=[]] Array of entries to precache.\n */\n precache(entries) {\n this.addToCacheList(entries);\n if (!this._installAndActiveListenersAdded) {\n self.addEventListener('install', this.install);\n self.addEventListener('activate', this.activate);\n this._installAndActiveListenersAdded = true;\n }\n }\n /**\n * This method will add items to the precache list, removing duplicates\n * and ensuring the information is valid.\n *\n * @param {Array} entries\n * Array of entries to precache.\n */\n addToCacheList(entries) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isArray(entries, {\n moduleName: 'workbox-precaching',\n className: 'PrecacheController',\n funcName: 'addToCacheList',\n paramName: 'entries',\n });\n }\n const urlsToWarnAbout = [];\n for (const entry of entries) {\n // See https://github.com/GoogleChrome/workbox/issues/2259\n if (typeof entry === 'string') {\n urlsToWarnAbout.push(entry);\n }\n else if (entry && entry.revision === undefined) {\n urlsToWarnAbout.push(entry.url);\n }\n const { cacheKey, url } = createCacheKey(entry);\n const cacheMode = typeof entry !== 'string' && entry.revision ? 'reload' : 'default';\n if (this._urlsToCacheKeys.has(url) &&\n this._urlsToCacheKeys.get(url) !== cacheKey) {\n throw new WorkboxError('add-to-cache-list-conflicting-entries', {\n firstEntry: this._urlsToCacheKeys.get(url),\n secondEntry: cacheKey,\n });\n }\n if (typeof entry !== 'string' && entry.integrity) {\n if (this._cacheKeysToIntegrities.has(cacheKey) &&\n this._cacheKeysToIntegrities.get(cacheKey) !== entry.integrity) {\n throw new WorkboxError('add-to-cache-list-conflicting-integrities', {\n url,\n });\n }\n this._cacheKeysToIntegrities.set(cacheKey, entry.integrity);\n }\n this._urlsToCacheKeys.set(url, cacheKey);\n this._urlsToCacheModes.set(url, cacheMode);\n if (urlsToWarnAbout.length > 0) {\n const warningMessage = `Workbox is precaching URLs without revision ` +\n `info: ${urlsToWarnAbout.join(', ')}\\nThis is generally NOT safe. ` +\n `Learn more at https://bit.ly/wb-precache`;\n if (process.env.NODE_ENV === 'production') {\n // Use console directly to display this warning without bloating\n // bundle sizes by pulling in all of the logger codebase in prod.\n console.warn(warningMessage);\n }\n else {\n logger.warn(warningMessage);\n }\n }\n }\n }\n /**\n * Precaches new and updated assets. Call this method from the service worker\n * install event.\n *\n * Note: this method calls `event.waitUntil()` for you, so you do not need\n * to call it yourself in your event handlers.\n *\n * @param {ExtendableEvent} event\n * @return {Promise}\n */\n install(event) {\n // waitUntil returns Promise\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return waitUntil(event, async () => {\n const installReportPlugin = new PrecacheInstallReportPlugin();\n this.strategy.plugins.push(installReportPlugin);\n // Cache entries one at a time.\n // See https://github.com/GoogleChrome/workbox/issues/2528\n for (const [url, cacheKey] of this._urlsToCacheKeys) {\n const integrity = this._cacheKeysToIntegrities.get(cacheKey);\n const cacheMode = this._urlsToCacheModes.get(url);\n const request = new Request(url, {\n integrity,\n cache: cacheMode,\n credentials: 'same-origin',\n });\n await Promise.all(this.strategy.handleAll({\n params: { cacheKey },\n request,\n event,\n }));\n }\n const { updatedURLs, notUpdatedURLs } = installReportPlugin;\n if (process.env.NODE_ENV !== 'production') {\n printInstallDetails(updatedURLs, notUpdatedURLs);\n }\n return { updatedURLs, notUpdatedURLs };\n });\n }\n /**\n * Deletes assets that are no longer present in the current precache manifest.\n * Call this method from the service worker activate event.\n *\n * Note: this method calls `event.waitUntil()` for you, so you do not need\n * to call it yourself in your event handlers.\n *\n * @param {ExtendableEvent} event\n * @return {Promise}\n */\n activate(event) {\n // waitUntil returns Promise\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return waitUntil(event, async () => {\n const cache = await self.caches.open(this.strategy.cacheName);\n const currentlyCachedRequests = await cache.keys();\n const expectedCacheKeys = new Set(this._urlsToCacheKeys.values());\n const deletedURLs = [];\n for (const request of currentlyCachedRequests) {\n if (!expectedCacheKeys.has(request.url)) {\n await cache.delete(request);\n deletedURLs.push(request.url);\n }\n }\n if (process.env.NODE_ENV !== 'production') {\n printCleanupDetails(deletedURLs);\n }\n return { deletedURLs };\n });\n }\n /**\n * Returns a mapping of a precached URL to the corresponding cache key, taking\n * into account the revision information for the URL.\n *\n * @return {Map} A URL to cache key mapping.\n */\n getURLsToCacheKeys() {\n return this._urlsToCacheKeys;\n }\n /**\n * Returns a list of all the URLs that have been precached by the current\n * service worker.\n *\n * @return {Array} The precached URLs.\n */\n getCachedURLs() {\n return [...this._urlsToCacheKeys.keys()];\n }\n /**\n * Returns the cache key used for storing a given URL. If that URL is\n * unversioned, like `/index.html', then the cache key will be the original\n * URL with a search parameter appended to it.\n *\n * @param {string} url A URL whose cache key you want to look up.\n * @return {string} The versioned URL that corresponds to a cache key\n * for the original URL, or undefined if that URL isn't precached.\n */\n getCacheKeyForURL(url) {\n const urlObject = new URL(url, location.href);\n return this._urlsToCacheKeys.get(urlObject.href);\n }\n /**\n * @param {string} url A cache key whose SRI you want to look up.\n * @return {string} The subresource integrity associated with the cache key,\n * or undefined if it's not set.\n */\n getIntegrityForCacheKey(cacheKey) {\n return this._cacheKeysToIntegrities.get(cacheKey);\n }\n /**\n * This acts as a drop-in replacement for\n * [`cache.match()`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/match)\n * with the following differences:\n *\n * - It knows what the name of the precache is, and only checks in that cache.\n * - It allows you to pass in an \"original\" URL without versioning parameters,\n * and it will automatically look up the correct cache key for the currently\n * active revision of that URL.\n *\n * E.g., `matchPrecache('index.html')` will find the correct precached\n * response for the currently active service worker, even if the actual cache\n * key is `'/index.html?__WB_REVISION__=1234abcd'`.\n *\n * @param {string|Request} request The key (without revisioning parameters)\n * to look up in the precache.\n * @return {Promise}\n */\n async matchPrecache(request) {\n const url = request instanceof Request ? request.url : request;\n const cacheKey = this.getCacheKeyForURL(url);\n if (cacheKey) {\n const cache = await self.caches.open(this.strategy.cacheName);\n return cache.match(cacheKey);\n }\n return undefined;\n }\n /**\n * Returns a function that looks up `url` in the precache (taking into\n * account revision information), and returns the corresponding `Response`.\n *\n * @param {string} url The precached URL which will be used to lookup the\n * `Response`.\n * @return {workbox-routing~handlerCallback}\n */\n createHandlerBoundToURL(url) {\n const cacheKey = this.getCacheKeyForURL(url);\n if (!cacheKey) {\n throw new WorkboxError('non-precached-url', { url });\n }\n return (options) => {\n options.request = new Request(url);\n options.params = Object.assign({ cacheKey }, options.params);\n return this.strategy.handle(options);\n };\n }\n}\nexport { PrecacheController };\n","/*\n Copyright 2020 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { Route } from 'workbox-routing/Route.js';\nimport { generateURLVariations } from './utils/generateURLVariations.js';\nimport './_version.js';\n/**\n * A subclass of {@link workbox-routing.Route} that takes a\n * {@link workbox-precaching.PrecacheController}\n * instance and uses it to match incoming requests and handle fetching\n * responses from the precache.\n *\n * @memberof workbox-precaching\n * @extends workbox-routing.Route\n */\nclass PrecacheRoute extends Route {\n /**\n * @param {PrecacheController} precacheController A `PrecacheController`\n * instance used to both match requests and respond to fetch events.\n * @param {Object} [options] Options to control how requests are matched\n * against the list of precached URLs.\n * @param {string} [options.directoryIndex=index.html] The `directoryIndex` will\n * check cache entries for a URLs ending with '/' to see if there is a hit when\n * appending the `directoryIndex` value.\n * @param {Array} [options.ignoreURLParametersMatching=[/^utm_/, /^fbclid$/]] An\n * array of regex's to remove search params when looking for a cache match.\n * @param {boolean} [options.cleanURLs=true] The `cleanURLs` option will\n * check the cache for the URL with a `.html` added to the end of the end.\n * @param {workbox-precaching~urlManipulation} [options.urlManipulation]\n * This is a function that should take a URL and return an array of\n * alternative URLs that should be checked for precache matches.\n */\n constructor(precacheController, options) {\n const match = ({ request, }) => {\n const urlsToCacheKeys = precacheController.getURLsToCacheKeys();\n for (const possibleURL of generateURLVariations(request.url, options)) {\n const cacheKey = urlsToCacheKeys.get(possibleURL);\n if (cacheKey) {\n const integrity = precacheController.getIntegrityForCacheKey(cacheKey);\n return { cacheKey, integrity };\n }\n }\n if (process.env.NODE_ENV !== 'production') {\n logger.debug(`Precaching did not find a match for ` + getFriendlyURL(request.url));\n }\n return;\n };\n super(match, precacheController.strategy);\n }\n}\nexport { PrecacheRoute };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { removeIgnoredSearchParams } from './removeIgnoredSearchParams.js';\nimport '../_version.js';\n/**\n * Generator function that yields possible variations on the original URL to\n * check, one at a time.\n *\n * @param {string} url\n * @param {Object} options\n *\n * @private\n * @memberof workbox-precaching\n */\nexport function* generateURLVariations(url, { ignoreURLParametersMatching = [/^utm_/, /^fbclid$/], directoryIndex = 'index.html', cleanURLs = true, urlManipulation, } = {}) {\n const urlObject = new URL(url, location.href);\n urlObject.hash = '';\n yield urlObject.href;\n const urlWithoutIgnoredParams = removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching);\n yield urlWithoutIgnoredParams.href;\n if (directoryIndex && urlWithoutIgnoredParams.pathname.endsWith('/')) {\n const directoryURL = new URL(urlWithoutIgnoredParams.href);\n directoryURL.pathname += directoryIndex;\n yield directoryURL.href;\n }\n if (cleanURLs) {\n const cleanURL = new URL(urlWithoutIgnoredParams.href);\n cleanURL.pathname += '.html';\n yield cleanURL.href;\n }\n if (urlManipulation) {\n const additionalURLs = urlManipulation({ url: urlObject });\n for (const urlToAttempt of additionalURLs) {\n yield urlToAttempt.href;\n }\n }\n}\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport '../_version.js';\n/**\n * Removes any URL search parameters that should be ignored.\n *\n * @param {URL} urlObject The original URL.\n * @param {Array} ignoreURLParametersMatching RegExps to test against\n * each search parameter name. Matches mean that the search parameter should be\n * ignored.\n * @return {URL} The URL with any ignored search parameters removed.\n *\n * @private\n * @memberof workbox-precaching\n */\nexport function removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching = []) {\n // Convert the iterable into an array at the start of the loop to make sure\n // deletion doesn't mess up iteration.\n for (const paramName of [...urlObject.searchParams.keys()]) {\n if (ignoreURLParametersMatching.some((regExp) => regExp.test(paramName))) {\n urlObject.searchParams.delete(paramName);\n }\n }\n return urlObject;\n}\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { Strategy } from './Strategy.js';\nimport { messages } from './utils/messages.js';\nimport './_version.js';\n/**\n * An implementation of a [cache-first](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#cache-first-falling-back-to-network)\n * request strategy.\n *\n * A cache first strategy is useful for assets that have been revisioned,\n * such as URLs like `/styles/example.a8f5f1.css`, since they\n * can be cached for long periods of time.\n *\n * If the network request fails, and there is no cache match, this will throw\n * a `WorkboxError` exception.\n *\n * @extends workbox-strategies.Strategy\n * @memberof workbox-strategies\n */\nclass CacheFirst extends Strategy {\n /**\n * @private\n * @param {Request|string} request A request to run this strategy for.\n * @param {workbox-strategies.StrategyHandler} handler The event that\n * triggered the request.\n * @return {Promise}\n */\n async _handle(request, handler) {\n const logs = [];\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(request, Request, {\n moduleName: 'workbox-strategies',\n className: this.constructor.name,\n funcName: 'makeRequest',\n paramName: 'request',\n });\n }\n let response = await handler.cacheMatch(request);\n let error = undefined;\n if (!response) {\n if (process.env.NODE_ENV !== 'production') {\n logs.push(`No response found in the '${this.cacheName}' cache. ` +\n `Will respond with a network request.`);\n }\n try {\n response = await handler.fetchAndCachePut(request);\n }\n catch (err) {\n if (err instanceof Error) {\n error = err;\n }\n }\n if (process.env.NODE_ENV !== 'production') {\n if (response) {\n logs.push(`Got response from network.`);\n }\n else {\n logs.push(`Unable to get a response from the network.`);\n }\n }\n }\n else {\n if (process.env.NODE_ENV !== 'production') {\n logs.push(`Found a cached response in the '${this.cacheName}' cache.`);\n }\n }\n if (process.env.NODE_ENV !== 'production') {\n logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));\n for (const log of logs) {\n logger.log(log);\n }\n messages.printFinalResponse(response);\n logger.groupEnd();\n }\n if (!response) {\n throw new WorkboxError('no-response', { url: request.url, error });\n }\n return response;\n }\n}\nexport { CacheFirst };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { CacheableResponse, } from './CacheableResponse.js';\nimport './_version.js';\n/**\n * A class implementing the `cacheWillUpdate` lifecycle callback. This makes it\n * easier to add in cacheability checks to requests made via Workbox's built-in\n * strategies.\n *\n * @memberof workbox-cacheable-response\n */\nclass CacheableResponsePlugin {\n /**\n * To construct a new CacheableResponsePlugin instance you must provide at\n * least one of the `config` properties.\n *\n * If both `statuses` and `headers` are specified, then both conditions must\n * be met for the `Response` to be considered cacheable.\n *\n * @param {Object} config\n * @param {Array} [config.statuses] One or more status codes that a\n * `Response` can have and be considered cacheable.\n * @param {Object} [config.headers] A mapping of header names\n * and expected values that a `Response` can have and be considered cacheable.\n * If multiple headers are provided, only one needs to be present.\n */\n constructor(config) {\n /**\n * @param {Object} options\n * @param {Response} options.response\n * @return {Response|null}\n * @private\n */\n this.cacheWillUpdate = async ({ response }) => {\n if (this._cacheableResponse.isResponseCacheable(response)) {\n return response;\n }\n return null;\n };\n this._cacheableResponse = new CacheableResponse(config);\n }\n}\nexport { CacheableResponsePlugin };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { cacheNames } from 'workbox-core/_private/cacheNames.js';\nimport { dontWaitFor } from 'workbox-core/_private/dontWaitFor.js';\nimport { getFriendlyURL } from 'workbox-core/_private/getFriendlyURL.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { registerQuotaErrorCallback } from 'workbox-core/registerQuotaErrorCallback.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { CacheExpiration } from './CacheExpiration.js';\nimport './_version.js';\n/**\n * This plugin can be used in a `workbox-strategy` to regularly enforce a\n * limit on the age and / or the number of cached requests.\n *\n * It can only be used with `workbox-strategy` instances that have a\n * [custom `cacheName` property set](/web/tools/workbox/guides/configure-workbox#custom_cache_names_in_strategies).\n * In other words, it can't be used to expire entries in strategy that uses the\n * default runtime cache name.\n *\n * Whenever a cached response is used or updated, this plugin will look\n * at the associated cache and remove any old or extra responses.\n *\n * When using `maxAgeSeconds`, responses may be used *once* after expiring\n * because the expiration clean up will not have occurred until *after* the\n * cached response has been used. If the response has a \"Date\" header, then\n * a light weight expiration check is performed and the response will not be\n * used immediately.\n *\n * When using `maxEntries`, the entry least-recently requested will be removed\n * from the cache first.\n *\n * @memberof workbox-expiration\n */\nclass ExpirationPlugin {\n /**\n * @param {ExpirationPluginOptions} config\n * @param {number} [config.maxEntries] The maximum number of entries to cache.\n * Entries used the least will be removed as the maximum is reached.\n * @param {number} [config.maxAgeSeconds] The maximum age of an entry before\n * it's treated as stale and removed.\n * @param {Object} [config.matchOptions] The [`CacheQueryOptions`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/delete#Parameters)\n * that will be used when calling `delete()` on the cache.\n * @param {boolean} [config.purgeOnQuotaError] Whether to opt this cache in to\n * automatic deletion if the available storage quota has been exceeded.\n */\n constructor(config = {}) {\n /**\n * A \"lifecycle\" callback that will be triggered automatically by the\n * `workbox-strategies` handlers when a `Response` is about to be returned\n * from a [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to\n * the handler. It allows the `Response` to be inspected for freshness and\n * prevents it from being used if the `Response`'s `Date` header value is\n * older than the configured `maxAgeSeconds`.\n *\n * @param {Object} options\n * @param {string} options.cacheName Name of the cache the response is in.\n * @param {Response} options.cachedResponse The `Response` object that's been\n * read from a cache and whose freshness should be checked.\n * @return {Response} Either the `cachedResponse`, if it's\n * fresh, or `null` if the `Response` is older than `maxAgeSeconds`.\n *\n * @private\n */\n this.cachedResponseWillBeUsed = async ({ event, request, cacheName, cachedResponse, }) => {\n if (!cachedResponse) {\n return null;\n }\n const isFresh = this._isResponseDateFresh(cachedResponse);\n // Expire entries to ensure that even if the expiration date has\n // expired, it'll only be used once.\n const cacheExpiration = this._getCacheExpiration(cacheName);\n dontWaitFor(cacheExpiration.expireEntries());\n // Update the metadata for the request URL to the current timestamp,\n // but don't `await` it as we don't want to block the response.\n const updateTimestampDone = cacheExpiration.updateTimestamp(request.url);\n if (event) {\n try {\n event.waitUntil(updateTimestampDone);\n }\n catch (error) {\n if (process.env.NODE_ENV !== 'production') {\n // The event may not be a fetch event; only log the URL if it is.\n if ('request' in event) {\n logger.warn(`Unable to ensure service worker stays alive when ` +\n `updating cache entry for ` +\n `'${getFriendlyURL(event.request.url)}'.`);\n }\n }\n }\n }\n return isFresh ? cachedResponse : null;\n };\n /**\n * A \"lifecycle\" callback that will be triggered automatically by the\n * `workbox-strategies` handlers when an entry is added to a cache.\n *\n * @param {Object} options\n * @param {string} options.cacheName Name of the cache that was updated.\n * @param {string} options.request The Request for the cached entry.\n *\n * @private\n */\n this.cacheDidUpdate = async ({ cacheName, request, }) => {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(cacheName, 'string', {\n moduleName: 'workbox-expiration',\n className: 'Plugin',\n funcName: 'cacheDidUpdate',\n paramName: 'cacheName',\n });\n assert.isInstance(request, Request, {\n moduleName: 'workbox-expiration',\n className: 'Plugin',\n funcName: 'cacheDidUpdate',\n paramName: 'request',\n });\n }\n const cacheExpiration = this._getCacheExpiration(cacheName);\n await cacheExpiration.updateTimestamp(request.url);\n await cacheExpiration.expireEntries();\n };\n if (process.env.NODE_ENV !== 'production') {\n if (!(config.maxEntries || config.maxAgeSeconds)) {\n throw new WorkboxError('max-entries-or-age-required', {\n moduleName: 'workbox-expiration',\n className: 'Plugin',\n funcName: 'constructor',\n });\n }\n if (config.maxEntries) {\n assert.isType(config.maxEntries, 'number', {\n moduleName: 'workbox-expiration',\n className: 'Plugin',\n funcName: 'constructor',\n paramName: 'config.maxEntries',\n });\n }\n if (config.maxAgeSeconds) {\n assert.isType(config.maxAgeSeconds, 'number', {\n moduleName: 'workbox-expiration',\n className: 'Plugin',\n funcName: 'constructor',\n paramName: 'config.maxAgeSeconds',\n });\n }\n }\n this._config = config;\n this._maxAgeSeconds = config.maxAgeSeconds;\n this._cacheExpirations = new Map();\n if (config.purgeOnQuotaError) {\n registerQuotaErrorCallback(() => this.deleteCacheAndMetadata());\n }\n }\n /**\n * A simple helper method to return a CacheExpiration instance for a given\n * cache name.\n *\n * @param {string} cacheName\n * @return {CacheExpiration}\n *\n * @private\n */\n _getCacheExpiration(cacheName) {\n if (cacheName === cacheNames.getRuntimeName()) {\n throw new WorkboxError('expire-custom-caches-only');\n }\n let cacheExpiration = this._cacheExpirations.get(cacheName);\n if (!cacheExpiration) {\n cacheExpiration = new CacheExpiration(cacheName, this._config);\n this._cacheExpirations.set(cacheName, cacheExpiration);\n }\n return cacheExpiration;\n }\n /**\n * @param {Response} cachedResponse\n * @return {boolean}\n *\n * @private\n */\n _isResponseDateFresh(cachedResponse) {\n if (!this._maxAgeSeconds) {\n // We aren't expiring by age, so return true, it's fresh\n return true;\n }\n // Check if the 'date' header will suffice a quick expiration check.\n // See https://github.com/GoogleChromeLabs/sw-toolbox/issues/164 for\n // discussion.\n const dateHeaderTimestamp = this._getDateHeaderTimestamp(cachedResponse);\n if (dateHeaderTimestamp === null) {\n // Unable to parse date, so assume it's fresh.\n return true;\n }\n // If we have a valid headerTime, then our response is fresh iff the\n // headerTime plus maxAgeSeconds is greater than the current time.\n const now = Date.now();\n return dateHeaderTimestamp >= now - this._maxAgeSeconds * 1000;\n }\n /**\n * This method will extract the data header and parse it into a useful\n * value.\n *\n * @param {Response} cachedResponse\n * @return {number|null}\n *\n * @private\n */\n _getDateHeaderTimestamp(cachedResponse) {\n if (!cachedResponse.headers.has('date')) {\n return null;\n }\n const dateHeader = cachedResponse.headers.get('date');\n const parsedDate = new Date(dateHeader);\n const headerTime = parsedDate.getTime();\n // If the Date header was invalid for some reason, parsedDate.getTime()\n // will return NaN.\n if (isNaN(headerTime)) {\n return null;\n }\n return headerTime;\n }\n /**\n * This is a helper method that performs two operations:\n *\n * - Deletes *all* the underlying Cache instances associated with this plugin\n * instance, by calling caches.delete() on your behalf.\n * - Deletes the metadata from IndexedDB used to keep track of expiration\n * details for each Cache instance.\n *\n * When using cache expiration, calling this method is preferable to calling\n * `caches.delete()` directly, since this will ensure that the IndexedDB\n * metadata is also cleanly removed and open IndexedDB instances are deleted.\n *\n * Note that if you're *not* using cache expiration for a given cache, calling\n * `caches.delete()` and passing in the cache's name should be sufficient.\n * There is no Workbox-specific method needed for cleanup in that case.\n */\n async deleteCacheAndMetadata() {\n // Do this one at a time instead of all at once via `Promise.all()` to\n // reduce the chance of inconsistency if a promise rejects.\n for (const [cacheName, cacheExpiration] of this._cacheExpirations) {\n await self.caches.delete(cacheName);\n await cacheExpiration.delete();\n }\n // Reset this._cacheExpirations to its initial state.\n this._cacheExpirations = new Map();\n }\n}\nexport { ExpirationPlugin };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { logger } from './_private/logger.js';\nimport { assert } from './_private/assert.js';\nimport { quotaErrorCallbacks } from './models/quotaErrorCallbacks.js';\nimport './_version.js';\n/**\n * Adds a function to the set of quotaErrorCallbacks that will be executed if\n * there's a quota error.\n *\n * @param {Function} callback\n * @memberof workbox-core\n */\n// Can't change Function type\n// eslint-disable-next-line @typescript-eslint/ban-types\nfunction registerQuotaErrorCallback(callback) {\n if (process.env.NODE_ENV !== 'production') {\n assert.isType(callback, 'function', {\n moduleName: 'workbox-core',\n funcName: 'register',\n paramName: 'callback',\n });\n }\n quotaErrorCallbacks.add(callback);\n if (process.env.NODE_ENV !== 'production') {\n logger.log('Registered a callback to respond to quota errors.', callback);\n }\n}\nexport { registerQuotaErrorCallback };\n","/*\n Copyright 2018 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { assert } from 'workbox-core/_private/assert.js';\nimport { logger } from 'workbox-core/_private/logger.js';\nimport { WorkboxError } from 'workbox-core/_private/WorkboxError.js';\nimport { cacheOkAndOpaquePlugin } from './plugins/cacheOkAndOpaquePlugin.js';\nimport { Strategy } from './Strategy.js';\nimport { messages } from './utils/messages.js';\nimport './_version.js';\n/**\n * An implementation of a\n * [network first](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#network-first-falling-back-to-cache)\n * request strategy.\n *\n * By default, this strategy will cache responses with a 200 status code as\n * well as [opaque responses](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque-responses).\n * Opaque responses are are cross-origin requests where the response doesn't\n * support [CORS](https://enable-cors.org/).\n *\n * If the network request fails, and there is no cache match, this will throw\n * a `WorkboxError` exception.\n *\n * @extends workbox-strategies.Strategy\n * @memberof workbox-strategies\n */\nclass NetworkFirst extends Strategy {\n /**\n * @param {Object} [options]\n * @param {string} [options.cacheName] Cache name to store and retrieve\n * requests. Defaults to cache names provided by\n * {@link workbox-core.cacheNames}.\n * @param {Array} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n * to use in conjunction with this caching strategy.\n * @param {Object} [options.fetchOptions] Values passed along to the\n * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)\n * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)\n * `fetch()` requests made by this strategy.\n * @param {Object} [options.matchOptions] [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)\n * @param {number} [options.networkTimeoutSeconds] If set, any network requests\n * that fail to respond within the timeout will fallback to the cache.\n *\n * This option can be used to combat\n * \"[lie-fi]{@link https://developers.google.com/web/fundamentals/performance/poor-connectivity/#lie-fi}\"\n * scenarios.\n */\n constructor(options = {}) {\n super(options);\n // If this instance contains no plugins with a 'cacheWillUpdate' callback,\n // prepend the `cacheOkAndOpaquePlugin` plugin to the plugins list.\n if (!this.plugins.some((p) => 'cacheWillUpdate' in p)) {\n this.plugins.unshift(cacheOkAndOpaquePlugin);\n }\n this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;\n if (process.env.NODE_ENV !== 'production') {\n if (this._networkTimeoutSeconds) {\n assert.isType(this._networkTimeoutSeconds, 'number', {\n moduleName: 'workbox-strategies',\n className: this.constructor.name,\n funcName: 'constructor',\n paramName: 'networkTimeoutSeconds',\n });\n }\n }\n }\n /**\n * @private\n * @param {Request|string} request A request to run this strategy for.\n * @param {workbox-strategies.StrategyHandler} handler The event that\n * triggered the request.\n * @return {Promise}\n */\n async _handle(request, handler) {\n const logs = [];\n if (process.env.NODE_ENV !== 'production') {\n assert.isInstance(request, Request, {\n moduleName: 'workbox-strategies',\n className: this.constructor.name,\n funcName: 'handle',\n paramName: 'makeRequest',\n });\n }\n const promises = [];\n let timeoutId;\n if (this._networkTimeoutSeconds) {\n const { id, promise } = this._getTimeoutPromise({ request, logs, handler });\n timeoutId = id;\n promises.push(promise);\n }\n const networkPromise = this._getNetworkPromise({\n timeoutId,\n request,\n logs,\n handler,\n });\n promises.push(networkPromise);\n const response = await handler.waitUntil((async () => {\n // Promise.race() will resolve as soon as the first promise resolves.\n return ((await handler.waitUntil(Promise.race(promises))) ||\n // If Promise.race() resolved with null, it might be due to a network\n // timeout + a cache miss. If that were to happen, we'd rather wait until\n // the networkPromise resolves instead of returning null.\n // Note that it's fine to await an already-resolved promise, so we don't\n // have to check to see if it's still \"in flight\".\n (await networkPromise));\n })());\n if (process.env.NODE_ENV !== 'production') {\n logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));\n for (const log of logs) {\n logger.log(log);\n }\n messages.printFinalResponse(response);\n logger.groupEnd();\n }\n if (!response) {\n throw new WorkboxError('no-response', { url: request.url });\n }\n return response;\n }\n /**\n * @param {Object} options\n * @param {Request} options.request\n * @param {Array} options.logs A reference to the logs array\n * @param {Event} options.event\n * @return {Promise}\n *\n * @private\n */\n _getTimeoutPromise({ request, logs, handler, }) {\n let timeoutId;\n const timeoutPromise = new Promise((resolve) => {\n const onNetworkTimeout = async () => {\n if (process.env.NODE_ENV !== 'production') {\n logs.push(`Timing out the network response at ` +\n `${this._networkTimeoutSeconds} seconds.`);\n }\n resolve(await handler.cacheMatch(request));\n };\n timeoutId = setTimeout(onNetworkTimeout, this._networkTimeoutSeconds * 1000);\n });\n return {\n promise: timeoutPromise,\n id: timeoutId,\n };\n }\n /**\n * @param {Object} options\n * @param {number|undefined} options.timeoutId\n * @param {Request} options.request\n * @param {Array} options.logs A reference to the logs Array.\n * @param {Event} options.event\n * @return {Promise}\n *\n * @private\n */\n async _getNetworkPromise({ timeoutId, request, logs, handler, }) {\n let error;\n let response;\n try {\n response = await handler.fetchAndCachePut(request);\n }\n catch (fetchError) {\n if (fetchError instanceof Error) {\n error = fetchError;\n }\n }\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n if (process.env.NODE_ENV !== 'production') {\n if (response) {\n logs.push(`Got response from network.`);\n }\n else {\n logs.push(`Unable to get a response from the network. Will respond ` +\n `with a cached response.`);\n }\n }\n if (error || !response) {\n response = await handler.cacheMatch(request);\n if (process.env.NODE_ENV !== 'production') {\n if (response) {\n logs.push(`Found a cached response in the '${this.cacheName}'` + ` cache.`);\n }\n else {\n logs.push(`No response found in the '${this.cacheName}' cache.`);\n }\n }\n }\n return response;\n }\n}\nexport { NetworkFirst };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { addRoute } from './addRoute.js';\nimport { precache } from './precache.js';\nimport './_version.js';\n/**\n * This method will add entries to the precache list and add a route to\n * respond to fetch events.\n *\n * This is a convenience method that will call\n * {@link workbox-precaching.precache} and\n * {@link workbox-precaching.addRoute} in a single call.\n *\n * @param {Array} entries Array of entries to precache.\n * @param {Object} [options] See the\n * {@link workbox-precaching.PrecacheRoute} options.\n *\n * @memberof workbox-precaching\n */\nfunction precacheAndRoute(entries, options) {\n precache(entries);\n addRoute(options);\n}\nexport { precacheAndRoute };\n","/*\n Copyright 2019 Google LLC\n\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { getOrCreatePrecacheController } from './utils/getOrCreatePrecacheController.js';\nimport './_version.js';\n/**\n * Adds items to the precache list, removing any duplicates and\n * stores the files in the\n * {@link workbox-core.cacheNames|\"precache cache\"} when the service\n * worker installs.\n *\n * This method can be called multiple times.\n *\n * Please note: This method **will not** serve any of the cached files for you.\n * It only precaches files. To respond to a network request you call\n * {@link workbox-precaching.addRoute}.\n *\n * If you have a single array of files to precache, you can just call\n * {@link workbox-precaching.precacheAndRoute}.\n *\n * @param {Array} [entries=[]] Array of entries to precache.\n *\n * @memberof workbox-precaching\n */\nfunction precache(entries) {\n const precacheController = getOrCreatePrecacheController();\n precacheController.precache(entries);\n}\nexport { precache };\n","/*\n Copyright 2019 Google LLC\n Use of this source code is governed by an MIT-style\n license that can be found in the LICENSE file or at\n https://opensource.org/licenses/MIT.\n*/\nimport { registerRoute } from 'workbox-routing/registerRoute.js';\nimport { getOrCreatePrecacheController } from './utils/getOrCreatePrecacheController.js';\nimport { PrecacheRoute } from './PrecacheRoute.js';\nimport './_version.js';\n/**\n * Add a `fetch` listener to the service worker that will\n * respond to\n * [network requests]{@link https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#Custom_responses_to_requests}\n * with precached assets.\n *\n * Requests for assets that aren't precached, the `FetchEvent` will not be\n * responded to, allowing the event to fall through to other `fetch` event\n * listeners.\n *\n * @param {Object} [options] See the {@link workbox-precaching.PrecacheRoute}\n * options.\n *\n * @memberof workbox-precaching\n */\nfunction addRoute(options) {\n const precacheController = getOrCreatePrecacheController();\n const precacheRoute = new PrecacheRoute(precacheController, options);\n registerRoute(precacheRoute);\n}\nexport { addRoute };\n"],"names":["self","_","e","messageGenerator","fallback","code","args","msg","length","JSON","stringify","WorkboxError","Error","constructor","errorCode","details","super","this","name","normalizeHandler","handler","handle","Route","match","method","setCatchHandler","catchHandler","RegExpRoute","regExp","url","result","exec","href","origin","location","index","slice","Router","_routes","Map","_defaultHandlerMap","routes","addFetchListener","addEventListener","event","request","responsePromise","handleRequest","respondWith","addCacheListener","data","type","payload","requestPromises","Promise","all","urlsToCache","map","entry","Request","waitUntil","ports","then","postMessage","URL","protocol","startsWith","sameOrigin","params","route","findMatchingRoute","has","get","err","reject","_catchHandler","catch","async","catchErr","matchResult","Array","isArray","Object","keys","undefined","setDefaultHandler","set","registerRoute","push","unregisterRoute","routeIndex","indexOf","splice","defaultRouter","getOrCreateDefaultRouter","capture","captureUrl","matchCallback","RegExp","moduleName","funcName","paramName","_cacheNameDetails","googleAnalytics","precache","prefix","runtime","suffix","registration","scope","_createCacheName","cacheName","filter","value","join","cacheNames","userCacheName","dontWaitFor","promise","quotaErrorCallbacks","Set","idbProxyableTypes","cursorAdvanceMethods","cursorRequestMap","WeakMap","transactionDoneMap","transactionStoreNamesMap","transformCache","reverseTransformCache","idbProxyTraps","target","prop","receiver","IDBTransaction","objectStoreNames","objectStore","wrap","wrapFunction","func","IDBDatabase","prototype","transaction","IDBCursor","advance","continue","continuePrimaryKey","includes","apply","unwrap","storeNames","tx","call","sort","transformCachableValue","done","resolve","unlisten","removeEventListener","complete","error","DOMException","cacheDonePromiseForTransaction","object","IDBObjectStore","IDBIndex","some","c","Proxy","instanceOfAny","IDBRequest","success","promisifyRequest","newValue","readMethods","writeMethods","cachedMethods","getMethod","targetFuncName","replace","useIndex","isWrite","storeName","store","shift","oldTraps","_extends","callback","CACHE_OBJECT_STORE","normalizeURL","unNormalizedUrl","hash","CacheTimestampsModel","_db","_cacheName","_upgradeDb","db","objStore","createObjectStore","keyPath","createIndex","unique","_upgradeDbAndDeleteOldDbs","blocked","indexedDB","deleteDatabase","oldVersion","deleteDB","setTimestamp","timestamp","id","_getId","getDb","durability","put","getTimestamp","expireEntries","minTimestamp","maxCount","cursor","openCursor","entriesToDelete","entriesNotDeletedCount","urlsDeleted","delete","version","upgrade","blocking","terminated","open","openPromise","newVersion","openDB","bind","CacheExpiration","config","_isRunning","_rerunRequested","_maxEntries","maxEntries","_maxAgeSeconds","maxAgeSeconds","_matchOptions","matchOptions","_timestampModel","Date","now","urlsExpired","cache","caches","updateTimestamp","isURLExpired","expireOlderThan","Infinity","stripParams","fullURL","ignoreParams","strippedURL","param","searchParams","Deferred","toRequest","input","StrategyHandler","strategy","options","_cacheKeys","assign","_strategy","_handlerDeferred","_extendLifetimePromises","_plugins","plugins","_pluginStateMap","plugin","fetch","mode","FetchEvent","preloadResponse","possiblePreloadResponse","originalRequest","hasCallback","clone","cb","iterateCallbacks","thrownErrorMessage","message","pluginFilteredRequest","fetchResponse","fetchOptions","response","runCallbacks","fetchAndCachePut","responseClone","cachePut","cacheMatch","key","cachedResponse","effectiveRequest","getCacheKey","multiMatchOptions","ms","setTimeout","String","responseToCache","_ensureResponseSafeToCache","hasCacheUpdateCallback","oldResponse","strippedRequestURL","keysOptions","ignoreSearch","cacheKeys","cacheKey","cacheMatchIgnoreParams","executeQuotaErrorCallbacks","newResponse","state","statefulCallback","statefulParam","doneWaiting","promises","firstRejection","allSettled","find","i","status","reason","destroy","pluginsUsed","Strategy","responseDone","handleAll","_getResponse","_awaitComplete","_handle","waitUntilError","CacheableResponse","_statuses","statuses","_headers","headers","isResponseCacheable","cacheable","headerName","cacheOkAndOpaquePlugin","cacheWillUpdate","asyncFn","returnPromise","createCacheKey","urlObject","revision","cacheKeyURL","originalURL","PrecacheInstallReportPlugin","updatedURLs","notUpdatedURLs","handlerWillStart","cachedResponseWillBeUsed","PrecacheCacheKeyPlugin","precacheController","cacheKeyWillBeUsed","_precacheController","getCacheKeyForURL","supportStatus","copyResponse","modifier","clonedResponse","responseInit","Headers","statusText","modifiedResponseInit","body","testResponse","Response","canConstructResponseFromBodyStream","blob","PrecacheStrategy","_fallbackToNetwork","fallbackToNetwork","copyRedirectedCacheableResponsesPlugin","_handleInstall","_handleFetch","integrityInManifest","integrity","integrityInRequest","noIntegrityConflict","_useDefaultCacheabilityPluginIfNeeded","defaultPluginIndex","cacheWillUpdatePluginCount","entries","defaultPrecacheCacheabilityPlugin","redirected","PrecacheController","_urlsToCacheKeys","_urlsToCacheModes","_cacheKeysToIntegrities","install","activate","addToCacheList","_installAndActiveListenersAdded","urlsToWarnAbout","cacheMode","firstEntry","secondEntry","warningMessage","console","warn","installReportPlugin","credentials","currentlyCachedRequests","expectedCacheKeys","values","deletedURLs","getURLsToCacheKeys","getCachedURLs","getIntegrityForCacheKey","matchPrecache","createHandlerBoundToURL","getOrCreatePrecacheController","PrecacheRoute","urlsToCacheKeys","possibleURL","ignoreURLParametersMatching","directoryIndex","cleanURLs","urlManipulation","urlWithoutIgnoredParams","test","removeIgnoredSearchParams","pathname","endsWith","directoryURL","cleanURL","additionalURLs","urlToAttempt","generateURLVariations","_cacheableResponse","isFresh","_isResponseDateFresh","cacheExpiration","_getCacheExpiration","updateTimestampDone","cacheDidUpdate","_config","_cacheExpirations","purgeOnQuotaError","add","registerQuotaErrorCallback","deleteCacheAndMetadata","dateHeaderTimestamp","_getDateHeaderTimestamp","dateHeader","headerTime","getTime","isNaN","p","unshift","_networkTimeoutSeconds","networkTimeoutSeconds","logs","timeoutId","_getTimeoutPromise","networkPromise","_getNetworkPromise","race","fetchError","clearTimeout","addRoute"],"mappings":"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"} \ No newline at end of file diff --git a/core/systemrun.html b/core/systemrun.html index bcdc650..a71bc9f 100644 --- a/core/systemrun.html +++ b/core/systemrun.html @@ -69,14 +69,13 @@ crossorigin="anonymous" > - @@ -202,7 +150,7 @@ } - + @@ -235,62 +183,9 @@ >Application - -
- - - - - - - - - - - - diff --git a/docs/api/index.md b/docs/api/index.md index 6ac4618..bb97916 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -5,6 +5,7 @@ This document provides detailed function signatures and descriptions for the cor ## Core Modules - [crispr_scripts.js](#crispr_scriptsjs) - Main gRNA and primer validation logic +- [runtime.js](#runtimejs) - Runtime flow helpers ## crispr_scripts.js @@ -83,7 +84,7 @@ async function loadCRISPRJSON_Files() **Returns:** Promise (implicitly handled by async). -**Called By:** `redirectCRISPR()` in [core/scripts/login.js](../../core/scripts/login.js), which is triggered on page load from [core/systemrun.html](../../core/systemrun.html). +**Called By:** `redirectCRISPR()` in [core/scripts/runtime.js](../../core/scripts/runtime.js), which is triggered on page load from [core/systemrun.html](../../core/systemrun.html). **Example:** @@ -221,21 +222,40 @@ function checkOffTarget(score) **Parameters:** -- `score` {number} - Student's off-target score input +- `score` {number} - Reference specificity score from the matched gRNA entry **Behavior:** - Compares the student input to the floor/ceiling of the reference specificity score - Builds a score list within ±35 positions of the inputted cut position to determine an optimal threshold +- Uses `getOffTargetOptimalValue()` to determine the optimal threshold from the local max score - Sets `MAROffTarget` and `MAROffTarget_degree` based on the input and calculated thresholds - Degree values: - `0`: Wrong or below threshold - - `1`: At or above the optimal threshold - - `2`: At or above 35 but below the optimal threshold when the max reference score is at least 80 + - `1`: At or above the optimal threshold, or at or above 35 when the local max score is below 80 + - `2`: At or above 35 but below the optimal threshold when the local max score is at least 80 - `3`: Only available option when the local max score is below 35 **Used For:** Part of `checkAnswers()` validation. +#### getOffTargetOptimalValue(maxRange) + +```javascript +function getOffTargetOptimalValue(maxRange) +``` + +**Purpose:** Calculate the default optimal off-target threshold. + +**Parameters:** + +- `maxRange` {number} - Maximum specificity score in the local window + +**Behavior:** + +- Computes `minOptimal = maxRange - maxRange * 0.2` +- Returns `80` when `minOptimal` is greater than 80 or less than 35 +- Returns `minOptimal` otherwise + #### checkF1Primers(seq) ```javascript @@ -301,7 +321,6 @@ function markAnswers() - Assigns points for each correct component: - gRNA sequence - PAM - - Strand - Off-target score - F1 primer - R1 primer @@ -323,7 +342,7 @@ function showFeedback() **Renders:** -- Component-by-component results (✓ or ✗) +- Component-by-component results and marks - Explanatory text derived from the current marking state - Candidate primer lists derived from the submitted gRNA sequence @@ -398,33 +417,37 @@ function showNewInput(docCheck, checkFor, docDisplay) **Behavior:** Shows `docDisplay` when `docCheck` matches `checkFor`. -#### ChangeDOMInnerhtml(domID, changeTo) +## runtime.js + +**Location:** [core/scripts/runtime.js](../../core/scripts/runtime.js) + +Runtime flow helpers for the practice experience. + +### loadGeneContent() ```javascript -function ChangeDOMInnerhtml(domID, changeTo) +function loadGeneContent() ``` -**Purpose:** Update HTML element content. +**Purpose:** Read the selected gene and trigger gene loading. -**Parameters:** +**Behavior:** -- `domID` {string} - HTML element ID -- `changeTo` {string} - New HTML content +- Sets `possible_gene` from `#gene_dropdown_selection` +- Calls `select_Gene()` -#### changeInputClass(docCheck, checkFor, docChange, trueChangeValueTo) +### redirectCRISPR() ```javascript -function changeInputClass(docCheck, checkFor, docChange, trueChangeValueTo) +async function redirectCRISPR() ``` -**Purpose:** Update an input value when another input matches a target value. +**Purpose:** Build the runtime selection UI and load reference data. -**Parameters:** +**Behavior:** -- `docCheck` {string} - Element ID to check condition -- `checkFor` {string} - Value to match -- `docChange` {string} - Element ID to update -- `trueChangeValueTo` {string} - Value to apply when matched +- Builds the selection UI in `#mainContainer` +- Calls `loadCRISPRJSON_Files()` and `fillGeneList()` ## Bootstrap & jQuery diff --git a/docs/architecture/index.md b/docs/architecture/index.md index 06199e0..e597b0f 100644 --- a/docs/architecture/index.md +++ b/docs/architecture/index.md @@ -2,7 +2,7 @@ SciGrade is a client-side web application that dynamically renders the guide RNA (gRNA) and primer validation interface. This section covers the system design, data flow, and component relationships. -The landing page is [index.html](../../index.html), and the runtime page is [core/systemrun.html](../../core/systemrun.html). The runtime page loads the client scripts and initializes the UI flow defined in [core/scripts/login.js](../../core/scripts/login.js) and [core/scripts/crispr_scripts.js](../../core/scripts/crispr_scripts.js). +The landing page is [index.html](../../index.html), and the runtime page is [core/systemrun.html](../../core/systemrun.html). The runtime page loads the client scripts and initializes the UI flow defined in [core/scripts/runtime.js](../../core/scripts/runtime.js) and [core/scripts/crispr_scripts.js](../../core/scripts/crispr_scripts.js). ## System Overview @@ -40,18 +40,16 @@ Main application logic for gRNA and primer validation. - `gene_backgroundInfo` - Loaded gene reference data - `benchling_gRNA_outputs` - Loaded gRNA validation reference -#### [core/scripts/login.js](../../core/scripts/login.js) +#### [core/scripts/runtime.js](../../core/scripts/runtime.js) -UI bootstrap and account-management helpers used by the runtime page. +UI bootstrap helpers used by the runtime page. **Key Functions:** - `redirectCRISPR()` - Builds the selection UI and triggers data loading - `loadGeneContent()` - Reads the selected gene and calls `select_Gene()` -- `checkStudentNumber()` - Validates class membership against `student_reg_information` -- `loginVerify()` - Checks a student record for registration status -**Note:** The default runtime flow hides the account UI and initializes the practice flow on page load in [core/systemrun.html](../../core/systemrun.html). The deprecation of online account features is recorded in [CHANGELOG.md](../../CHANGELOG.md). +**Note:** The runtime flow initializes the practice flow on page load in [core/systemrun.html](../../core/systemrun.html). The deprecation of online account features is recorded in [CHANGELOG.md](../../CHANGELOG.md). ### Data Files @@ -109,7 +107,7 @@ Application styles covering: - Layout and responsive design - Form styling and validation states - Feedback page appearance -- Modal dialogs and account management UI +- Modal dialogs used by feedback flows Built with Bootstrap utilities integrated via [core/scripts/APIandLibraries/Bootstrap/](../../core/scripts/APIandLibraries/Bootstrap/). @@ -125,7 +123,7 @@ Built with Bootstrap utilities integrated via [core/scripts/APIandLibraries/Boot ### Initialization Flow -The initialization flow is driven by [index.html](../../index.html), [core/systemrun.html](../../core/systemrun.html), [core/scripts/login.js](../../core/scripts/login.js), and [core/scripts/crispr_scripts.js](../../core/scripts/crispr_scripts.js). +The initialization flow is driven by [index.html](../../index.html), [core/systemrun.html](../../core/systemrun.html), [core/scripts/runtime.js](../../core/scripts/runtime.js), and [core/scripts/crispr_scripts.js](../../core/scripts/crispr_scripts.js). ```mermaid sequenceDiagram @@ -180,7 +178,7 @@ flowchart TD ## Component Relationships -The landing page and runtime page are defined in [index.html](../../index.html) and [core/systemrun.html](../../core/systemrun.html), with scripts in [core/scripts/crispr_scripts.js](../../core/scripts/crispr_scripts.js) and [core/scripts/login.js](../../core/scripts/login.js). +The landing page and runtime page are defined in [index.html](../../index.html) and [core/systemrun.html](../../core/systemrun.html), with scripts in [core/scripts/crispr_scripts.js](../../core/scripts/crispr_scripts.js) and [core/scripts/runtime.js](../../core/scripts/runtime.js). ```mermaid graph LR @@ -200,7 +198,8 @@ graph LR ## Offline Support -Service workers are registered in [index.html](../../index.html) and [core/systemrun.html](../../core/systemrun.html). The generated worker [core/scripts/serviceWorker/sw.js](../../core/scripts/serviceWorker/sw.js) precaches the core assets listed in [workbox-config.js](../../workbox-config.js) and applies runtime caching for HTML, CSS, JavaScript, and image requests. +Service workers are registered in [index.html](../../index.html) and [core/systemrun.html](../../core/systemrun.html). The generated worker [core/scripts/serviceWorker/sw.js](../../core/scripts/serviceWorker/sw.js) precaches the core assets listed in [workbox-config.cjs](../../workbox-config.cjs) and applies runtime caching for HTML, CSS, JavaScript, and image requests. +Implementation: [index.html](../../index.html), [core/systemrun.html](../../core/systemrun.html), [core/scripts/serviceWorker/sw.js](../../core/scripts/serviceWorker/sw.js), [workbox-config.cjs](../../workbox-config.cjs) ## Dependencies @@ -221,7 +220,9 @@ From [package.json](../../package.json): - **Playwright** - E2E testing - **ESLint** - Code quality - **Prettier** - Code formatting +- **esbuild** - Script minification - **Workbox** - Service worker generation + Implementation: [package.json](../../package.json) ## Security Considerations @@ -231,7 +232,8 @@ From [package.json](../../package.json): ## Performance Optimizations -1. **Runtime Data Fetch** - Gene data is fetched when `redirectCRISPR()` runs in [core/scripts/login.js](../../core/scripts/login.js) +1. **Runtime Data Fetch** - Gene data is fetched when `redirectCRISPR()` runs in [core/scripts/runtime.js](../../core/scripts/runtime.js) 2. **Minified Assets** - Pre-built minified versions are available in [core/scripts/](../../core/scripts/) and [core/styling/](../../core/styling/) -3. **Service Worker Caching** - Runtime caching rules are defined in [workbox-config.js](../../workbox-config.js) +3. **Service Worker Caching** - Runtime caching rules are defined in [workbox-config.cjs](../../workbox-config.cjs) 4. **Client-side Rendering** - UI is generated in the browser by [core/scripts/crispr_scripts.js](../../core/scripts/crispr_scripts.js) + Implementation: [workbox-config.cjs](../../workbox-config.cjs), [core/scripts/crispr_scripts.js](../../core/scripts/crispr_scripts.js) diff --git a/docs/guides/data-structures.md b/docs/guides/data-structures.md index 8c42349..5e07e5f 100644 --- a/docs/guides/data-structures.md +++ b/docs/guides/data-structures.md @@ -4,7 +4,7 @@ This document describes the JSON data formats and data structures used throughou ## Overview -SciGrade uses JSON as its primary data format for gene information and guide RNA (gRNA) validation reference data. These files are loaded client-side during the runtime UI flow in [core/scripts/login.js](../../core/scripts/login.js) and [core/scripts/crispr_scripts.js](../../core/scripts/crispr_scripts.js). +SciGrade uses JSON as its primary data format for gene information and guide RNA (gRNA) validation reference data. These files are loaded client-side during the runtime UI flow in [core/scripts/runtime.js](../../core/scripts/runtime.js) and [core/scripts/crispr_scripts.js](../../core/scripts/crispr_scripts.js). ## Gene Background Information diff --git a/docs/guides/marking-algorithm.md b/docs/guides/marking-algorithm.md index 68a28d7..92dec88 100644 --- a/docs/guides/marking-algorithm.md +++ b/docs/guides/marking-algorithm.md @@ -4,7 +4,7 @@ This document explains how guide RNA (gRNA) sequences and primers are validated ## Overview -The marking system validates student submissions across five key components: +The marking system validates student submissions across the components below. Scoring uses the gRNA sequence, PAM, off-target score, and primer inputs. 1. **guide RNA (gRNA) Sequence** - Must match a reference sequence 2. **Protospacer Adjacent Motif (PAM) Sequence** - Must match the PAM of the validated gRNA @@ -115,7 +115,6 @@ PAM validation compares the student's input to the `PAM` value on the matched re ```javascript function checkOffTarget(score) { // Check if off-target score meets minimum threshold - // Threshold depends on marking mode settings } ``` @@ -125,13 +124,12 @@ The off-target score uses the following steps in [core/scripts/crispr_scripts.js 1. Build a list of reference scores within +/- 35 positions of the submitted cut position. 2. Compute `Max_range` from that list and `Min_optimal = Max_range - (Max_range * 0.2)`. -3. When the class marking mode is `Optimal`, use 80 as the threshold if `Min_optimal` is greater than 80 or less than 35. -4. When the class marking mode is `Custom`, use the class-specific value stored in `student_reg_information`. +3. Use 80 as the threshold if `Min_optimal` is greater than 80 or less than 35. **Pass Criteria:** - Input at or above the optimal threshold sets `MAROffTarget_degree` to `1`. -- Input at or above 35 sets `MAROffTarget_degree` to `2` when `Max_range` is at least 80. +- Input at or above 35 sets `MAROffTarget_degree` to `1` when the local max score is below 80, otherwise it sets `MAROffTarget_degree` to `2`. - If the local max score is below 35, the input is treated as the only option and sets `MAROffTarget_degree` to `3`. ### Step 7: Primer Validation @@ -194,7 +192,7 @@ sequenceDiagram **Feedback Includes:** -- Component-by-component results (✓ or ✗) +- Component-by-component results and marks - Explanatory text generated from the current marking state - Candidate primer lists derived from the submitted gRNA sequence @@ -202,17 +200,7 @@ sequenceDiagram ### Adjusting Off-target Threshold -In account management modal (opened via [openAccountManagement()](../../core/scripts/crispr_scripts.js#L853)): - -1. Select "Optimal" mode: - -- Calculates `Min_optimal = Max_range - (Max_range * 0.2)` from nearby scores -- Uses 80 as the threshold when `Min_optimal` is greater than 80 or less than 35 - -2. Select "Custom" mode: - - Enter specific value (0.01 - 100) - -- Stored in `student_reg_information[0].classMarkingMod` for the selected class +The practice flow uses the default threshold derived from nearby scores. To adjust it, update the logic in `getOffTargetOptimalValue()` inside [core/scripts/crispr_scripts.js](../../core/scripts/crispr_scripts.js). ### Adding Custom Marking Rules diff --git a/docs/guides/setup.md b/docs/guides/setup.md index 11f56f2..5df4a35 100644 --- a/docs/guides/setup.md +++ b/docs/guides/setup.md @@ -53,6 +53,7 @@ npm run test:playwright:ui # Run playwright tests in UI mode npm run validate # Run prettier, eslint, jest, and playwright tests # Build & Service Worker +npm run minify # Minify runtime and marking scripts npm run workbox # Generate service worker with workbox ``` @@ -66,7 +67,7 @@ npm run workbox # Generate service worker with workbox - [core/scripts/APIandLibraries/](../../core/scripts/APIandLibraries/) - Third-party libraries (jQuery, Bootstrap) - [core/scripts/serviceWorker/](../../core/scripts/serviceWorker/) - Service worker runtime - [core/scripts/crispr_scripts.js](../../core/scripts/crispr_scripts.js) - Main gRNA/primer validation logic -- [core/scripts/login.js](../../core/scripts/login.js) - Login and account helpers +- [core/scripts/runtime.js](../../core/scripts/runtime.js) - Runtime practice flow helpers - [core/styling/style.css](../../core/styling/style.css) - Application styles - [core/images/](../../core/images/) - SVG and PNG assets - [core/icon/](../../core/icon/) - PWA icons and manifest @@ -139,7 +140,7 @@ Structure: Test files: - [core/scripts/crispr_scripts.test.js](../../core/scripts/crispr_scripts.test.js) -- [core/scripts/login.test.js](../../core/scripts/login.test.js) +- [core/scripts/runtime.test.js](../../core/scripts/runtime.test.js) Run tests: @@ -171,14 +172,15 @@ Generate a service worker for offline support: npm run workbox ``` -This uses the configuration in [workbox-config.js](../../workbox-config.js). +This uses the configuration in [workbox-config.cjs](../../workbox-config.cjs). ### Code Minification -Minified versions are pre-built: +Generate minified scripts with `npm run minify` from [package.json](../../package.json). +This command creates the following bundled files used by the runtime page: - [core/scripts/crispr_scripts.min.js](../../core/scripts/crispr_scripts.min.js) -- [core/scripts/login.min.js](../../core/scripts/login.min.js) +- [core/scripts/runtime.min.js](../../core/scripts/runtime.min.js) - [core/styling/style.min.css](../../core/styling/style.min.css) ## Troubleshooting diff --git a/docs/index.md b/docs/index.md index b659bbb..54bf87c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,7 +6,7 @@ Welcome to the SciGrade documentation. This directory contains comprehensive gui [SciGrade](https://scigrade.com/) is a client-side web application that allows students and educators to practice and receive feedback on guide RNA (gRNA) design and F1/R1 primer entry for Clustered Regularly Interspaced Short Palindromic Repeats (CRISPR)-based workflows. -The landing page is [index.html](../index.html), which links to the application runtime page [core/systemrun.html](../core/systemrun.html). The runtime page loads the app scripts [core/scripts/login.min.js](../core/scripts/login.min.js) and [core/scripts/crispr_scripts.min.js](../core/scripts/crispr_scripts.min.js) and starts the UI flow on page load. +The landing page is [index.html](../index.html), which links to the application runtime page [core/systemrun.html](../core/systemrun.html). The runtime page loads [core/scripts/runtime.js](../core/scripts/runtime.js) and [core/scripts/crispr_scripts.js](../core/scripts/crispr_scripts.js) and starts the UI flow on page load. ## Documentation Structure @@ -18,7 +18,7 @@ The landing page is [index.html](../index.html), which links to the application ## Application Flow -The flow below reflects the landing page in [index.html](../index.html) and the runtime UI/data flow in [core/scripts/login.js](../core/scripts/login.js) and [core/scripts/crispr_scripts.js](../core/scripts/crispr_scripts.js). +The flow below reflects the landing page in [index.html](../index.html) and the runtime UI/data flow in [core/scripts/runtime.js](../core/scripts/runtime.js) and [core/scripts/crispr_scripts.js](../core/scripts/crispr_scripts.js). ```mermaid flowchart TD @@ -37,7 +37,7 @@ flowchart TD ## User Interaction Sequence -This sequence follows the runtime flow implemented in [core/scripts/login.js](../core/scripts/login.js) and [core/scripts/crispr_scripts.js](../core/scripts/crispr_scripts.js). +This sequence follows the runtime flow implemented in [core/scripts/runtime.js](../core/scripts/runtime.js) and [core/scripts/crispr_scripts.js](../core/scripts/crispr_scripts.js). ```mermaid sequenceDiagram @@ -76,21 +76,17 @@ SciGrade uses the gene list defined in [core/data/Background_info/gene_backgroun #### Practice Flow -- The runtime page initializes the UI without requiring authentication -- The runtime page initializes the UI without requiring authentication in [core/systemrun.html](../core/systemrun.html) +- The runtime page initializes the practice flow on page load - Feedback is generated after submission by [core/scripts/crispr_scripts.js](../core/scripts/crispr_scripts.js) -#### Account Management UI - -- Account management and class tooling are present in the client-side scripts and hidden by default in [core/systemrun.html](../core/systemrun.html) -- The [CHANGELOG.md](../CHANGELOG.md) records the deprecation of online account features +The [CHANGELOG.md](../CHANGELOG.md) records the deprecation of online account features. ## Technology Stack - **Frontend**: Vanilla JavaScript with jQuery and Bootstrap loaded from [core/scripts/APIandLibraries/](../core/scripts/APIandLibraries/) - **Data**: Client-side JSON data files loaded by [core/scripts/crispr_scripts.js](../core/scripts/crispr_scripts.js) -- **Build Tools**: Jest, Playwright, ESLint, and Prettier defined in [package.json](../package.json) -- **Service Worker**: Workbox configuration in [workbox-config.js](../workbox-config.js) and generated runtime in [core/scripts/serviceWorker/sw.js](../core/scripts/serviceWorker/sw.js) +- **Build Tools**: Jest, Playwright, ESLint, Prettier, and esbuild defined in [package.json](../package.json) +- **Service Worker**: Workbox configuration in [workbox-config.cjs](../workbox-config.cjs) and generated runtime in [core/scripts/serviceWorker/sw.js](../core/scripts/serviceWorker/sw.js) ## Development diff --git a/index.html b/index.html index 60e1adb..452aed9 100644 --- a/index.html +++ b/index.html @@ -100,13 +100,13 @@ crossorigin="anonymous" >