diff --git a/lib.php b/lib.php
index 36ea85a..65a3d37 100644
--- a/lib.php
+++ b/lib.php
@@ -719,7 +719,7 @@ function interactivevideo_displayinline(cm_info $cm) {
$OUTPUT->get_generated_image_for_id($cm->id) : $interactivevideo->posterimage; // Fallback to default image.
$duration = $interactivevideo->endtime - $interactivevideo->starttime;
// Convert to hh:mm:ss format.
- $duration = gmdate($duration > 3600 * 60 ? 'H:i:s' : 'i:s', (int) $duration);
+ $duration = gmdate($duration > 3600 ? 'H:i:s' : 'i:s', (int) $duration);
// Format the intro: keep text only and truncate it.
$datafortemplate = [
diff --git a/locallib.php b/locallib.php
index 23836b8..3e8eb5e 100644
--- a/locallib.php
+++ b/locallib.php
@@ -304,18 +304,15 @@ public static function get_report_data_by_group($interactivevideo, $group, $cont
// Get fields for userpicture.
$fields = \core_user\fields::get_picture_fields();
$fields = 'u.' . implode(', u.', $fields);
+ // Graded roles.
+ $roles = get_config('core', 'gradebookroles');
if ($group == 0) {
// Get all enrolled users (student only).
$sql = "SELECT " . $fields . ", ac.timecompleted, ac.timecreated,
ac.completionpercentage, ac.completeditems, ac.xp, ac.completiondetails, ac.id as completionid
FROM {user} u
LEFT JOIN {interactivevideo_completion} ac ON ac.userid = u.id AND ac.cmid = :cmid
- WHERE u.id IN (
- SELECT ra.userid
- FROM {role_assignments} ra
- JOIN {role} r ON ra.roleid = r.id
- WHERE ra.contextid = :contextid AND r.archetype = 'student'
- )
+ WHERE u.id IN (SELECT userid FROM {role_assignments} WHERE contextid = :contextid AND roleid IN (" . $roles . "))
ORDER BY u.lastname, u.firstname";
$records = $DB->get_records_sql($sql, ['cmid' => $interactivevideo, 'contextid' => $contextid]);
} else {
@@ -325,8 +322,9 @@ public static function get_report_data_by_group($interactivevideo, $group, $cont
FROM {user} u
LEFT JOIN {interactivevideo_completion} ac ON ac.userid = u.id AND ac.cmid = :cmid
WHERE u.id IN (SELECT userid FROM {groups_members} WHERE groupid = :groupid)
+ AND u.id IN (SELECT userid FROM {role_assignments} WHERE contextid = :contextid AND roleid IN (" . $roles . "))
ORDER BY u.lastname, u.firstname";
- $records = $DB->get_records_sql($sql, ['cmid' => $interactivevideo, 'groupid' => $group]);
+ $records = $DB->get_records_sql($sql, ['cmid' => $interactivevideo, 'groupid' => $group, 'contextid' => $contextid]);
}
// Render the photo of the user.
@@ -558,7 +556,7 @@ public static function encode_text($text) {
* @param string $field The field associated with the text.
* @param int $id The ID related to the text processing.
*
- * @return void
+ * @return string The processed text.
*/
public static function process_text($text, $contextid, $field, $id) {
if (!$text) {
@@ -650,7 +648,7 @@ public static function get_taught_courses($userid) {
if (!$userid) {
$userid = $USER->id;
}
- $PAGE->set_context(context_system::instance());
+ $PAGE->set_context(\context_system::instance());
// Get all courses where the user is a teacher.
$sql = "SELECT c.id, c.fullname, c.shortname FROM {course} c
JOIN {context} ctx ON c.id = ctx.instanceid AND ctx.contextlevel = 50
@@ -677,7 +675,7 @@ public static function get_taught_courses($userid) {
* Retrieves the course module by course ID.
*
* @param int $courseid The ID of the course.
- * @return object|null The course module object if found, null otherwise.
+ * @return array The course modules.
*/
public static function get_cm_by_courseid($courseid) {
global $DB, $PAGE;
@@ -751,7 +749,7 @@ public static function import_annotations($fromcourse, $tocourse, $module, $from
* @param int $userid The user ID.
* @param int $courseid The course ID.
* @param int $contextid The context ID.
- * @return string The completion information.
+ * @return array The completion information.
*/
public static function get_cm_completion($cmid, $userid, $courseid, $contextid) {
global $OUTPUT, $CFG, $PAGE, $USER;
diff --git a/plugins/chapter/amd/build/main.min.js.map b/plugins/chapter/amd/build/main.min.js.map
index 63aca5a..2295093 100644
--- a/plugins/chapter/amd/build/main.min.js.map
+++ b/plugins/chapter/amd/build/main.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"main.min.js","sources":["../src/main.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Interactive video chapter type script\n *\n * @module ivplugin_chapter/main\n * @copyright 2024 Sokunthearith Makara \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport $ from 'jquery';\nimport Base from 'mod_interactivevideo/type/base';\n\nexport default class Chapter extends Base {\n /**\n * Initialize the interaction type\n * @returns {void}\n */\n init() {\n if (this.isEditMode()) {\n return;\n }\n let self = this;\n let chapters = this.annotations.filter((annotation) => annotation.type == 'chapter');\n\n $(\"#chaptertoggle\").removeClass('d-none');\n // Order the chapters by timestamp.\n chapters.sort((a, b) => a.timestamp - b.timestamp);\n // If the first chapter doesn't start at the beginning, add a chapter at the beginning.\n if (chapters[0].timestamp > this.start) {\n chapters.unshift({\n id: 0,\n title: M.util.get_string('startchapter', 'ivplugin_chapter'),\n formattedtitle: M.util.get_string('startchapter', 'ivplugin_chapter'),\n timestamp: this.start\n });\n }\n // Calculate start and end time of each chapter.\n chapters.forEach((chapter, index) => {\n chapter.start = chapter.timestamp;\n if (index < chapters.length - 1) {\n chapter.end = chapters[index + 1].timestamp;\n } else {\n chapter.end = this.end;\n }\n });\n\n const convertSecondsToHMS = (seconds) => {\n const h = Math.floor(seconds / 3600);\n const m = Math.floor(seconds % 3600 / 60);\n const s = Math.floor(seconds % 3600 % 60);\n return (h > 0 ? h + ':' : '') + (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;\n };\n\n // Render the chapters.\n const $chapterlists = $('[data-region=chapterlists]');\n $chapterlists.empty();\n chapters.forEach((chapter) => {\n $chapterlists.append(`
`);\n // If the .chapter is in the left container, hide it.\n if ($(this).closest('#chapter-container-left').length > 0) {\n $('#chaptertoggle .btn').trigger('click');\n }\n });\n\n // Chapter toggle.\n $(document).on('click', '#chaptertoggle .btn', function(e) {\n e.preventDefault();\n $('#interactivevideo-container').toggleClass('chapter-open');\n $(this).find('i').toggleClass('bi-collection bi-collection-fill');\n });\n\n $(document).on('click', '#closechapter', function(e) {\n e.preventDefault();\n $('#chaptertoggle .btn').trigger('click');\n });\n\n // Collapse/Expand the chapters on click of the chevron.\n $(document).on('click', '.chapter i.toggle.bi', function(e) {\n e.preventDefault();\n $(this).closest('.chapter').find('.annolistinchapter').slideToggle(300);\n $(this).toggleClass('bi-chevron-down bi-chevron-right');\n });\n }\n\n /**\n * Renders the edit item for the annotations list.\n *\n * @param {Array} annotations - The list of annotations.\n * @param {jQuery} listItem - The jQuery object representing the list item.\n * @param {Object} item - The item to be rendered.\n * @param {number} item.timestamp - The timestamp of the item.\n * @returns {jQuery} The modified list item.\n */\n renderEditItem(annotations, listItem, item) {\n listItem = super.renderEditItem(annotations, listItem, item);\n listItem.find('.type-name').addClass('justify-content-center');\n listItem.find('.type-icon i').remove();\n if (Number(item.timestamp) > this.end || Number(item.timestamp) < this.start || this.isSkipped(item.timestamp)) {\n listItem.find('.title').addClass('text-muted');\n }\n return listItem;\n }\n\n /**\n * Render the annotation on the video navigation\n * @param {object} annotation The annotation object\n * @returns {void}\n */\n renderItemOnVideoNavigation(annotation) {\n if (annotation.hide) {\n return;\n }\n if (annotation.timestamp < this.start || annotation.timestamp > this.end) {\n return;\n }\n const percentage = ((Number(annotation.timestamp) - this.start) / this.totaltime) * 100;\n if (this.isVisible(annotation)) {\n let classes = annotation.type + ' annotation li-draggable ';\n if (annotation.completed) {\n classes += 'completed ';\n }\n if (!this.isClickable(annotation)) {\n classes += 'no-pointer-events ';\n }\n if (this.isSkipped(annotation.timestamp)) {\n classes += 'skipped ';\n }\n $(\"#video-nav ul\").append(`
\n
`);\n }\n }\n\n /**\n * Run the interaction\n * @param {object} annotation The annotation object\n */\n async runInteraction(annotation) {\n if (annotation.char1 != '1') {\n this.player.play();\n // Show the tooltip for 2 seconds.\n $('#video-nav ul li[data-id=' + annotation.id + '] .item').tooltip('show');\n setTimeout(() => {\n $('#video-nav ul li[data-id=' + annotation.id + '] .item').tooltip('hide');\n }, 2000);\n return;\n }\n\n await this.player.pause();\n $('#controler').addClass('no-pointer-events');\n $('#video-wrapper').append(`
${annotation.formattedtitle}
`);\n // Add a progress bar and load it for 3 seconds.\n $('#video-wrapper #message').append(`
\n
`);\n $('#message span').animate({\n 'top': '1em',\n }, 300, 'swing');\n $('#chapterprogress .progress-bar').animate({'width': '100%'}, 3000, 'linear', () => {\n if (!this.isEditMode()) {\n $('#message span').css('top', '0');\n this.player.play();\n $('#controler').removeClass('no-pointer-events');\n } else {\n $('h2#message').remove();\n }\n });\n }\n}"],"names":["Chapter","Base","init","this","isEditMode","self","chapters","annotations","filter","annotation","type","removeClass","sort","a","b","timestamp","start","unshift","id","title","M","util","get_string","formattedtitle","forEach","chapter","index","length","end","$chapterlists","empty","append","seconds","h","Math","floor","m","s","convertSecondsToHMS","document","on","async","currenttime","e","originalEvent","detail","time","currentchapter","find","addClass","text","preventDefault","fadeOut","starttime","closest","data","player","seek","play","percentage","totaltime","replaceWith","trigger","toggleClass","slideToggle","renderEditItem","listItem","item","super","remove","Number","isSkipped","renderItemOnVideoNavigation","hide","isVisible","classes","completed","isClickable","prop","icon","char1","tooltip","setTimeout","pause","animate","css"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"main.min.js","sources":["../src/main.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Interactive video chapter type script\n *\n * @module ivplugin_chapter/main\n * @copyright 2024 Sokunthearith Makara \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport $ from 'jquery';\nimport Base from 'mod_interactivevideo/type/base';\n\nexport default class Chapter extends Base {\n /**\n * Initialize the interaction type\n * @returns {void}\n */\n init() {\n if (this.isEditMode()) {\n return;\n }\n let self = this;\n let chapters = this.annotations.filter((annotation) => annotation.type == 'chapter');\n\n $(\"#chaptertoggle\").removeClass('d-none');\n // Order the chapters by timestamp.\n chapters.sort((a, b) => a.timestamp - b.timestamp);\n // If the first chapter doesn't start at the beginning, add a chapter at the beginning.\n if (chapters[0].timestamp > this.start) {\n chapters.unshift({\n id: 0,\n title: M.util.get_string('startchapter', 'ivplugin_chapter'),\n formattedtitle: M.util.get_string('startchapter', 'ivplugin_chapter'),\n timestamp: this.start\n });\n }\n // Calculate start and end time of each chapter.\n chapters.forEach((chapter, index) => {\n chapter.start = chapter.timestamp;\n if (index < chapters.length - 1) {\n chapter.end = chapters[index + 1].timestamp;\n } else {\n chapter.end = this.end;\n }\n });\n\n const convertSecondsToHMS = (seconds) => {\n const h = Math.floor(seconds / 3600);\n const m = Math.floor(seconds % 3600 / 60);\n const s = Math.floor(seconds % 3600 % 60);\n return (h > 0 ? h + ':' : '') + (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;\n };\n\n // Render the chapters.\n const $chapterlists = $('[data-region=chapterlists]');\n $chapterlists.empty();\n chapters.forEach((chapter) => {\n $chapterlists.append(`
")),void new Audio(M.cfg.wwwroot+"/mod/interactivevideo/sounds/pop.mp3").play();let complete=!1,textclass="";if(("completepass"==annotation.completiontracking&&event.data.statement.result&&event.data.statement.result.score.scaled>=.5||"completefull"==annotation.completiontracking&&event.data.statement.result&&1==event.data.statement.result.score.scaled||"complete"==annotation.completiontracking)&&(complete=!0),textclass=event.data.statement.result.score.scaled<.5?"fa fa-check text-danger":event.data.statement.result.score.scaled<1?"fa fa-check text-success":"bi bi-check2-all text-success",complete&&!annotation.completed){let details={};const completeTime=new Date;details.xp=annotation.xp,"1"==annotation.char1&&(details.xp=(event.data.statement.result.score.scaled*annotation.xp).toFixed(2)),details.duration=completeTime.getTime()-(0,_jquery.default)("#video-wrapper").data("timestamp"),details.timecompleted=completeTime.getTime();const completiontime=completeTime.toLocaleString();let duration=self.formatTime(details.duration/1e3);details.reportView='\n '.concat(completiontime,'').concat(duration,'\n \n ').concat(event.data.statement.result.score.raw,"/").concat(event.data.statement.result.score.max,"'>\n ').concat(Number(details.xp),""),details.details=statements,self.toggleCompletion(annoid,"mark-done","automatic",details)}}}))}catch(e){requestAnimationFrame(detectH5P)}}else requestAnimationFrame(detectH5P)};requestAnimationFrame(detectH5P)}(annotation,!annotation.completed&&"manual"!=annotation.completiontracking))}(annotation),this.enableManualCompletion(annotation),this.resizeIframe(annotation),"popup"==annotation.displayoptions&&(0,_jquery.default)("#annotation-modal").on("shown.bs.modal",(function(){self.setModalDraggable("#annotation-modal .modal-dialog")}))}}return _exports.default=ContentBank,_exports.default}));
//# sourceMappingURL=main.min.js.map
\ No newline at end of file
diff --git a/plugins/contentbank/amd/build/main.min.js.map b/plugins/contentbank/amd/build/main.min.js.map
index 9f67b5b..fc13f4e 100644
--- a/plugins/contentbank/amd/build/main.min.js.map
+++ b/plugins/contentbank/amd/build/main.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"main.min.js","sources":["../src/main.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Main class for content bank\n *\n * @module ivplugin_contentbank/main\n * @copyright 2024 Sokunthearith Makara \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport $ from 'jquery';\nimport contentbankutil from 'ivplugin_contentbank/util';\nimport ModalForm from 'core_form/modalform';\nimport Base from 'mod_interactivevideo/type/base';\n\nexport default class ContentBank extends Base {\n /**\n * Called when the edit form is loaded.\n * @param {Object} form The form object\n * @param {Event} event The event object\n * @return {void}\n */\n onEditFormLoaded(form, event) {\n let self = this;\n let body = form.modal.modal.find('.modal-body');\n contentbankutil.init(M.cfg.courseContextId);\n // Refresh the content from the content bank.\n body.off('click', '#refreshcontentbank').on('click', '#refreshcontentbank', function(e) {\n e.preventDefault();\n $(this).find('i').addClass('fa-spin');\n const currentid = $('[name=contentid]').val();\n $('.contentbank-container').html(`
\n Loading...
`);\n contentbankutil.refreshContentBank(currentid, M.cfg.courseContextId, $(this).data('editable'), function() {\n $('#refreshcontentbank i').removeClass('fa-spin');\n });\n });\n\n // Upload a new content.\n body.off('click', '#uploadcontentbank').on('click', '#uploadcontentbank', function(e) {\n e.preventDefault();\n const uploadForm = new ModalForm({\n formClass: \"core_contentbank\\\\form\\\\upload_files\",\n args: {\n contextid: M.cfg.courseContextId,\n },\n modalConfig: {\n title: M.util.get_string('uploadcontent', 'ivplugin_contentbank')\n }\n });\n\n uploadForm.addEventListener(uploadForm.events.FORM_SUBMITTED, (e) => {\n self.addNotification(M.util.get_string('contentuploaded', 'ivplugin_contentbank'), 'success');\n const returnurl = e.detail.returnurl;\n const contentid = returnurl.match(/id=(\\d+)/)[1];\n $('[name=contentid]').val(contentid);\n setTimeout(function() {\n $('#refreshcontentbank').trigger('click');\n }, 1000);\n });\n\n uploadForm.addEventListener(uploadForm.events.ERROR, () => {\n self.addNotification(M.util.get_string('contentuploaderror', 'ivplugin_contentbank'));\n });\n\n uploadForm.show();\n });\n return {form, event};\n }\n\n /**\n * Handles the rendering of content annotations and applies specific classes and conditions.\n *\n * @param {Object} annotation - The annotation object containing details about the content.\n * @param {Function} callback - The callback function to be executed if certain conditions are met.\n * @returns {boolean|Function} - Returns true if the annotation does not meet the conditions for completion tracking,\n * otherwise returns the callback function.\n */\n postContentRender(annotation, callback) {\n $(`#message[data-id='${annotation.id}']`).addClass('hascontentbank');\n if (annotation.hascompletion == 1\n && annotation.completiontracking != 'manual' && !annotation.completed) {\n return callback;\n }\n return true;\n }\n\n /**\n * Initialize the container to display the annotation\n * @param {Object} annotation The annotation object\n * @returns {void}\n */\n renderContainer(annotation) {\n super.renderContainer(annotation);\n let $message = $(`#message[data-id='${annotation.id}']`);\n $message.find('.modal-body').addClass('p-0');\n let $completiontoggle = $message.find('#completiontoggle');\n $message.find('#title .info').remove();\n switch (annotation.completiontracking) {\n case 'complete':\n $completiontoggle.before(``);\n break;\n case 'completepass':\n $completiontoggle.before(``);\n break;\n case 'completefull':\n $completiontoggle.before(``);\n break;\n }\n $message.find('[data-toggle=\"tooltip\"]').tooltip();\n return $message;\n }\n\n /**\n * Resizes the iframe within a modal body based on the height of the iframe content.\n *\n * @param {Object} annotation - The annotation object containing the id.\n */\n resizeIframe(annotation) {\n const modalbody = document.querySelector(`#message[data-id='${annotation.id}'] .modal-body`);\n const resizeObserver = new ResizeObserver(() => {\n const iframe = modalbody.querySelector('iframe.h5p-player');\n if (iframe) {\n const height = iframe.scrollHeight;\n modalbody.style.height = `${height + 2000}px`;\n }\n });\n\n resizeObserver.observe(modalbody);\n }\n\n /**\n * Run the interaction\n * @param {Object} annotation The annotation object\n * @returns {void}\n */\n async runInteraction(annotation) {\n await this.player.pause();\n const annoid = annotation.id;\n let self = this;\n let $message;\n\n const xAPICheck = (annotation, listenToEvents = true) => {\n const detectH5P = () => {\n let H5P;\n try { // Try to get the H5P object.\n H5P = document.querySelector(`#message[data-id='${annoid}'] iframe`).contentWindow.H5P;\n } catch (e) {\n H5P = null;\n }\n if (typeof H5P !== 'undefined' && H5P !== null) {\n\n if (!listenToEvents) {\n return;\n }\n if (self.isEditMode()) {\n $message.find(`#title .btns .xapi`).remove();\n $message.find(`#title .btns`)\n .prepend(`
`);\n const audio = new Audio(M.cfg.wwwroot + '/mod/interactivevideo/sounds/pop.mp3');\n audio.play();\n return;\n }\n let complete = false;\n let textclass = '';\n if (annotation.completiontracking == 'completepass'\n && event.data.statement.result && event.data.statement.result.score.scaled >= 0.5) {\n complete = true;\n } else if (annotation.completiontracking == 'completefull'\n && event.data.statement.result && event.data.statement.result.score.scaled == 1) {\n complete = true;\n } else if (annotation.completiontracking == 'complete') {\n complete = true;\n }\n if (event.data.statement.result.score.scaled < 0.5) {\n textclass = 'fa fa-check text-danger';\n } else if (event.data.statement.result.score.scaled < 1) {\n textclass = 'fa fa-check text-success';\n } else {\n textclass = 'bi bi-check2-all text-success';\n }\n if (complete && !annotation.completed) {\n let details = {};\n const completeTime = new Date();\n details.xp = annotation.xp;\n if (annotation.char1 == '1') { // Partial points.\n details.xp = (event.data.statement.result.score.scaled * annotation.xp).toFixed(2);\n }\n details.duration = completeTime.getTime() - $('#video-wrapper').data('timestamp');\n details.timecompleted = completeTime.getTime();\n const completiontime = completeTime.toLocaleString();\n let duration = self.formatTime(details.duration / 1000);\n details.reportView = `\n ${Number(details.xp)}`;\n details.details = statements;\n self.toggleCompletion(annoid, 'mark-done', 'automatic', details);\n }\n }\n });\n } catch (e) {\n requestAnimationFrame(detectH5P);\n }\n } else {\n requestAnimationFrame(detectH5P);\n }\n };\n requestAnimationFrame(detectH5P);\n };\n\n const applyContent = async function(annotation) {\n const data = await self.render(annotation);\n $message.find(`.modal-body`).html(data).attr('id', 'content').fadeIn(300);\n if (annotation.hascompletion != 1 || self.isEditMode()) {\n return;\n }\n if (!annotation.completed && annotation.completiontracking == 'view') {\n self.toggleCompletion(annoid, 'mark-done', 'automatic');\n }\n xAPICheck(annotation, !annotation.completed && annotation.completiontracking != 'manual');\n };\n\n\n await this.renderViewer(annotation);\n $message = this.renderContainer(annotation);\n applyContent(annotation);\n\n this.enableManualCompletion(annotation);\n\n this.resizeIframe(annotation);\n\n if (annotation.displayoptions == 'popup') {\n $('#annotation-modal').on('shown.bs.modal', function() {\n self.setModalDraggable('#annotation-modal .modal-dialog');\n });\n }\n }\n}"],"names":["ContentBank","Base","onEditFormLoaded","form","event","self","this","body","modal","find","init","M","cfg","courseContextId","off","on","e","preventDefault","addClass","currentid","val","html","refreshContentBank","data","removeClass","uploadForm","ModalForm","formClass","args","contextid","modalConfig","title","util","get_string","addEventListener","events","FORM_SUBMITTED","addNotification","contentid","detail","returnurl","match","setTimeout","trigger","ERROR","show","postContentRender","annotation","callback","id","hascompletion","completiontracking","completed","renderContainer","$message","$completiontoggle","remove","before","tooltip","resizeIframe","modalbody","document","querySelector","ResizeObserver","iframe","height","scrollHeight","style","observe","player","pause","annoid","renderViewer","async","render","attr","fadeIn","isEditMode","toggleCompletion","listenToEvents","detectH5P","H5P","contentWindow","prepend","statements","externalDispatcher","statement","verb","push","object","indexOf","Audio","wwwroot","play","complete","textclass","result","score","scaled","details","completeTime","Date","xp","char1","toFixed","duration","getTime","timecompleted","completiontime","toLocaleString","formatTime","reportView","raw","max","Number","requestAnimationFrame","xAPICheck","applyContent","enableManualCompletion","displayoptions","setModalDraggable"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"main.min.js","sources":["../src/main.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Main class for content bank\n *\n * @module ivplugin_contentbank/main\n * @copyright 2024 Sokunthearith Makara \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport $ from 'jquery';\nimport contentbankutil from 'ivplugin_contentbank/util';\nimport ModalForm from 'core_form/modalform';\nimport Base from 'mod_interactivevideo/type/base';\n\nexport default class ContentBank extends Base {\n /**\n * Called when the edit form is loaded.\n * @param {Object} form The form object\n * @param {Event} event The event object\n * @return {void}\n */\n onEditFormLoaded(form, event) {\n let self = this;\n let body = form.modal.modal.find('.modal-body');\n contentbankutil.init(M.cfg.courseContextId);\n // Refresh the content from the content bank.\n body.off('click', '#refreshcontentbank').on('click', '#refreshcontentbank', function(e) {\n e.preventDefault();\n $(this).find('i').addClass('fa-spin');\n const currentid = $('[name=contentid]').val();\n $('.contentbank-container').html(`
\n Loading...
`);\n contentbankutil.refreshContentBank(currentid, M.cfg.courseContextId, $(this).data('editable'), function() {\n $('#refreshcontentbank i').removeClass('fa-spin');\n });\n });\n\n // Upload a new content.\n body.off('click', '#uploadcontentbank').on('click', '#uploadcontentbank', function(e) {\n e.preventDefault();\n const uploadForm = new ModalForm({\n formClass: \"core_contentbank\\\\form\\\\upload_files\",\n args: {\n contextid: M.cfg.courseContextId,\n },\n modalConfig: {\n title: M.util.get_string('uploadcontent', 'ivplugin_contentbank')\n }\n });\n\n uploadForm.addEventListener(uploadForm.events.FORM_SUBMITTED, (e) => {\n self.addNotification(M.util.get_string('contentuploaded', 'ivplugin_contentbank'), 'success');\n const returnurl = e.detail.returnurl;\n const contentid = returnurl.match(/id=(\\d+)/)[1];\n $('[name=contentid]').val(contentid);\n setTimeout(function() {\n $('#refreshcontentbank').trigger('click');\n }, 1000);\n });\n\n uploadForm.addEventListener(uploadForm.events.ERROR, () => {\n self.addNotification(M.util.get_string('contentuploaderror', 'ivplugin_contentbank'));\n });\n\n uploadForm.show();\n });\n return {form, event};\n }\n\n /**\n * Handles the rendering of content annotations and applies specific classes and conditions.\n *\n * @param {Object} annotation - The annotation object containing details about the content.\n * @param {Function} callback - The callback function to be executed if certain conditions are met.\n * @returns {boolean|Function} - Returns true if the annotation does not meet the conditions for completion tracking,\n * otherwise returns the callback function.\n */\n postContentRender(annotation, callback) {\n $(`#message[data-id='${annotation.id}']`).addClass('hascontentbank');\n if (annotation.hascompletion == 1\n && annotation.completiontracking != 'manual' && !annotation.completed) {\n return callback;\n }\n return true;\n }\n\n /**\n * Initialize the container to display the annotation\n * @param {Object} annotation The annotation object\n * @returns {void}\n */\n renderContainer(annotation) {\n super.renderContainer(annotation);\n let $message = $(`#message[data-id='${annotation.id}']`);\n $message.find('.modal-body').addClass('p-0');\n let $completiontoggle = $message.find('#completiontoggle');\n $message.find('#title .info').remove();\n switch (annotation.completiontracking) {\n case 'complete':\n $completiontoggle.before(``);\n break;\n case 'completepass':\n $completiontoggle.before(``);\n break;\n case 'completefull':\n $completiontoggle.before(``);\n break;\n }\n $message.find('[data-toggle=\"tooltip\"]').tooltip();\n return $message;\n }\n\n /**\n * Resizes the iframe within a modal body based on the height of the iframe content.\n *\n * @param {Object} annotation - The annotation object containing the id.\n */\n resizeIframe(annotation) {\n const modalbody = document.querySelector(`#message[data-id='${annotation.id}'] .modal-body`);\n const resizeObserver = new ResizeObserver(() => {\n const iframe = modalbody.querySelector('iframe.h5p-player');\n if (iframe) {\n const height = iframe.scrollHeight;\n modalbody.style.height = `${height + 2000}px`;\n }\n });\n\n resizeObserver.observe(modalbody);\n }\n\n /**\n * Run the interaction\n * @param {Object} annotation The annotation object\n * @returns {void}\n */\n async runInteraction(annotation) {\n await this.player.pause();\n const annoid = annotation.id;\n let self = this;\n let $message;\n\n const xAPICheck = (annotation, listenToEvents = true) => {\n const detectH5P = () => {\n let H5P;\n try { // Try to get the H5P object.\n H5P = document.querySelector(`#message[data-id='${annoid}'] iframe`).contentWindow.H5P;\n } catch (e) {\n H5P = null;\n }\n if (typeof H5P !== 'undefined' && H5P !== null) {\n if (H5P.externalDispatcher === undefined) {\n requestAnimationFrame(detectH5P);\n return;\n }\n if (!listenToEvents) {\n return;\n }\n if (self.isEditMode()) {\n $message.find(`#title .btns .xapi`).remove();\n $message.find(`#title .btns`)\n .prepend(`
",contentbank.append(html)})),callback&&callback()}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=util.min.js.map
\ No newline at end of file
diff --git a/plugins/contentbank/amd/build/util.min.js.map b/plugins/contentbank/amd/build/util.min.js.map
index f6bc673..1260cb4 100644
--- a/plugins/contentbank/amd/build/util.min.js.map
+++ b/plugins/contentbank/amd/build/util.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"util.min.js","sources":["../src/util.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Content bank utility functions\n *\n * @module ivplugin_contentbank/util\n * @copyright 2024 Sokunthearith Makara \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport $ from 'jquery';\nimport Ajax from 'core/ajax';\n/**\n * Fetches content from the content bank and updates the target element if provided.\n *\n * @param {number} id - The ID of the content item to fetch.\n * @param {number} contextid - The context ID where the content item resides.\n * @param {string} [target] - The optional target element selector to update with the fetched content.\n * @returns {Promise} A promise that resolves with the response or updates the target element with the fetched content.\n */\nconst getcontent = (id, contextid, target) => {\n Ajax.call([{\n args: {\n id: id,\n contextid: contextid,\n },\n contextid: contextid,\n methodname: 'ivplugin_contentbank_getitem',\n }])[0].then((response) => {\n if (target) {\n return $(target).html(response.item);\n } else {\n return response;\n }\n }).catch(() => {\n // Do nothing.\n });\n};\n\n/**\n * Initializes event listeners for content bank interactions.\n *\n * @param {number} contextid - The context ID for the content bank.\n *\n * This function sets up click event handlers for elements within the content bank container.\n * It handles the selection of content items, updates the preview area, and manages xAPI event detection.\n *\n * Event Listeners:\n * - Click on content item details: Selects the item and updates the hidden input with the content ID.\n * - Click on content item view: Selects the item, updates the preview area, and sets up xAPI event detection.\n *\n * xAPI Event Detection:\n * - Monitors for xAPI events (completed, answered) emitted by the content.\n * - Displays a notification if such events are detected.\n */\nconst init = (contextid) => {\n $(document).off('click', '.contentbank-container .contentbank-item .contentbank-item-details')\n .on('click', '.contentbank-container .contentbank-item .contentbank-item-details', function(e) {\n e.preventDefault();\n $('.contentbank-container .contentbank-item').removeClass('selected');\n $(this).closest('.contentbank-item').addClass('selected');\n $('#contentbank-preview').empty();\n const id = $(this).closest('.contentbank-item').data('contentid');\n $('[name=contentid]').val(id);\n });\n\n $(document).off('click', '.contentbank-container .contentbank-item .contentbankview')\n .on('click', '.contentbank-container .contentbank-item .contentbankview', function(e) {\n e.preventDefault();\n $('.contentbank-container .contentbank-item').removeClass('selected');\n let targetContentbank = $(this).closest('.contentbank-item');\n targetContentbank.addClass('selected');\n const id = targetContentbank.data('contentid');\n $('#contentbank-preview').empty();\n $('#contentbank-preview').attr('data-contentid', id);\n $('[name=contentid]').val(id);\n // Preview selected content.\n getcontent(id, contextid, '#contentbank-preview');\n\n // Handle xAPI event. We want user to be able to check if the content emits xAPI events (completed, answered)\n // because some content types may not emit these events. Then user can decide\n // if they want students to mark it complete manually or automatically.\n const xapicheck = M.util.get_string('xapicheck', 'ivplugin_contentbank');\n let H5P;\n\n const checkH5P = () => {\n try { // Try to get the H5P object.\n H5P = document.querySelector('#contentbank-preview iframe.h5p-player').contentWindow.H5P;\n } catch (e) {\n H5P = null;\n }\n\n if (typeof H5P !== 'undefined' && H5P !== null) {\n $(\"#contentbank-preview .xapi\").remove();\n $(`#contentbank-preview[data-contentid=${id}]`)\n .prepend(`
`);\n const audio = new Audio(M.cfg.wwwroot + '/mod/interactivevideo/sounds/pop.mp3');\n audio.play();\n }\n });\n } else {\n requestAnimationFrame(checkH5P);\n }\n };\n\n requestAnimationFrame(checkH5P);\n });\n};\n\n/**\n * Refreshes the content bank by fetching and displaying content items.\n *\n * @param {number} id - The ID of the content to be highlighted.\n * @param {number} coursecontextid - The context ID of the course.\n * @param {boolean} [edit=true] - Whether to show edit options for the content items.\n * @param {Function} [callback] - Optional callback function to be executed after refreshing the content bank.\n * @returns {Promise} - A promise that resolves when the content bank is refreshed.\n */\nconst refreshContentBank = async (id, coursecontextid, edit = true, callback) => {\n $('#contentbank-preview').empty();\n let contentbankitems = await Ajax.call([{\n args: {\n contextid: coursecontextid\n },\n contextid: coursecontextid,\n methodname: 'ivplugin_contentbank_getitems',\n }])[0];\n\n let contents = JSON.parse(contentbankitems.contents);\n let contentbank = $('.modal-body form .contentbank-container');\n contentbank.empty();\n contents.forEach(function(content) {\n const editurl = M.cfg.wwwroot + '/contentbank/edit.php?contextid='\n + coursecontextid + '&id=' + content.id + '&plugin=' + content.type;\n let html = '
';\n if (content.icon) {\n html += '';\n } else {\n html += '';\n }\n\n html += '
' + content.name + '
';\n html += `
\n
`;\n if (edit) {\n html += `\n `;\n }\n\n html += `
`;\n\n\n contentbank.append(html);\n });\n\n if (callback) {\n callback();\n }\n};\n\nexport default {init, getcontent, refreshContentBank};"],"names":["getcontent","id","contextid","target","call","args","methodname","then","response","html","item","catch","init","document","off","on","e","preventDefault","removeClass","this","closest","addClass","empty","data","val","targetContentbank","attr","xapicheck","M","util","get_string","H5P","checkH5P","querySelector","contentWindow","remove","prepend","externalDispatcher","event","statement","verb","object","indexOf","Audio","cfg","wwwroot","play","requestAnimationFrame","refreshContentBank","async","coursecontextid","edit","callback","contentbankitems","Ajax","contents","JSON","parse","contentbank","forEach","content","editurl","type","icon","name","append"],"mappings":";;;;;;;uKAgCMA,WAAa,CAACC,GAAIC,UAAWC,wBAC1BC,KAAK,CAAC,CACPC,KAAM,CACFJ,GAAIA,GACJC,UAAWA,WAEfA,UAAWA,UACXI,WAAY,kCACZ,GAAGC,MAAMC,UACLL,QACO,mBAAEA,QAAQM,KAAKD,SAASE,MAExBF,WAEZG,OAAM,uBA4IE,CAACC,KAvHFV,gCACRW,UAAUC,IAAI,QAAS,sEACpBC,GAAG,QAAS,sEAAsE,SAASC,GACxFA,EAAEC,qCACA,4CAA4CC,YAAY,gCACxDC,MAAMC,QAAQ,qBAAqBC,SAAS,gCAC5C,wBAAwBC,cACpBrB,IAAK,mBAAEkB,MAAMC,QAAQ,qBAAqBG,KAAK,iCACnD,oBAAoBC,IAAIvB,2BAGhCY,UAAUC,IAAI,QAAS,6DACpBC,GAAG,QAAS,6DAA6D,SAASC,GAC/EA,EAAEC,qCACA,4CAA4CC,YAAY,gBACtDO,mBAAoB,mBAAEN,MAAMC,QAAQ,qBACxCK,kBAAkBJ,SAAS,kBACrBpB,GAAKwB,kBAAkBF,KAAK,iCAChC,wBAAwBD,4BACxB,wBAAwBI,KAAK,iBAAkBzB,wBAC/C,oBAAoBuB,IAAIvB,IAE1BD,WAAWC,GAAIC,UAAW,8BAKpByB,UAAYC,EAAEC,KAAKC,WAAW,YAAa,4BAC7CC,UAEEC,SAAW,SAETD,IAAMlB,SAASoB,cAAc,0CAA0CC,cAAcH,IACvF,MAAOf,GACLe,IAAM,KAGN,MAAOA,yBACL,8BAA8BI,2EACSlC,SACpCmC,8HACPT,qBACEI,IAAIM,mBAAmBtB,GAAG,QAAQ,SAASuB,WACF,4CAAhCA,MAAMf,KAAKgB,UAAUC,KAAKvC,IACQ,2CAAhCqC,MAAMf,KAAKgB,UAAUC,KAAKvC,KAC1BqC,MAAMf,KAAKgB,UAAUE,OAAOxC,GAAGyC,QAAQ,gBAAkB,EAAG,qBAC7D,8BAA8BP,6BAC9B,wBACGC,oKACyBR,EAAEC,KAAKC,WAAW,oBAAqB,mCACvD,IAAIa,MAAMf,EAAEgB,IAAIC,QAAU,wCAClCC,YAIdC,sBAAsBf,WAI9Be,sBAAsBf,cA4DZhC,WAAAA,WAAYgD,mBA/CPC,eAAOhD,GAAIiD,qBAAiBC,gEAAaC,oEAC9D,wBAAwB9B,YACtB+B,uBAAyBC,cAAKlD,KAAK,CAAC,CACpCC,KAAM,CACFH,UAAWgD,iBAEfhD,UAAWgD,gBACX5C,WAAY,mCACZ,GAEAiD,SAAWC,KAAKC,MAAMJ,iBAAiBE,UACvCG,aAAc,mBAAE,2CACpBA,YAAYpC,QACZiC,SAASI,SAAQ,SAASC,eAChBC,QAAUjC,EAAEgB,IAAIC,QAAU,mCAC1BK,gBAAkB,OAASU,QAAQ3D,GAAK,WAAa2D,QAAQE,SAC/DrD,KAAO,+DACJmD,QAAQ3D,IAAMA,GAAK,WAAa,IAAM,sBAAwB2D,QAAQ3D,GACvE,qEACF2D,QAAQG,KACRtD,MAAQ,gDAAkDmD,QAAQG,KAAO,MAEzEtD,MAAQ,iDAGZA,MAAQ,4CAA8CmD,QAAQI,KAAO,eACrEvD,mKAC4CmB,EAAEC,KAAKC,WAAW,UAAW,qFAErEqB,OACA1C,iEAA4DoD,mJAEpCjC,EAAEC,KAAKC,WAAW,OAAQ,6FAItDrB,eAGAiD,YAAYO,OAAOxD,SAGnB2C,UACAA"}
\ No newline at end of file
+{"version":3,"file":"util.min.js","sources":["../src/util.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Content bank utility functions\n *\n * @module ivplugin_contentbank/util\n * @copyright 2024 Sokunthearith Makara \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport $ from 'jquery';\nimport Ajax from 'core/ajax';\n/**\n * Fetches content from the content bank and updates the target element if provided.\n *\n * @param {number} id - The ID of the content item to fetch.\n * @param {number} contextid - The context ID where the content item resides.\n * @param {string} [target] - The optional target element selector to update with the fetched content.\n * @returns {Promise} A promise that resolves with the response or updates the target element with the fetched content.\n */\nconst getcontent = (id, contextid, target) => {\n Ajax.call([{\n args: {\n id: id,\n contextid: contextid,\n },\n contextid: contextid,\n methodname: 'ivplugin_contentbank_getitem',\n }])[0].then((response) => {\n if (target) {\n return $(target).html(response.item);\n } else {\n return response;\n }\n }).catch(() => {\n // Do nothing.\n });\n};\n\n/**\n * Initializes event listeners for content bank interactions.\n *\n * @param {number} contextid - The context ID for the content bank.\n *\n * This function sets up click event handlers for elements within the content bank container.\n * It handles the selection of content items, updates the preview area, and manages xAPI event detection.\n *\n * Event Listeners:\n * - Click on content item details: Selects the item and updates the hidden input with the content ID.\n * - Click on content item view: Selects the item, updates the preview area, and sets up xAPI event detection.\n *\n * xAPI Event Detection:\n * - Monitors for xAPI events (completed, answered) emitted by the content.\n * - Displays a notification if such events are detected.\n */\nconst init = (contextid) => {\n $(document).off('click', '.contentbank-container .contentbank-item .contentbank-item-details')\n .on('click', '.contentbank-container .contentbank-item .contentbank-item-details', function(e) {\n e.preventDefault();\n $('.contentbank-container .contentbank-item').removeClass('selected');\n $(this).closest('.contentbank-item').addClass('selected');\n $('#contentbank-preview').empty();\n const id = $(this).closest('.contentbank-item').data('contentid');\n $('[name=contentid]').val(id);\n });\n\n $(document).off('click', '.contentbank-container .contentbank-item .contentbankview')\n .on('click', '.contentbank-container .contentbank-item .contentbankview', function(e) {\n e.preventDefault();\n $('.contentbank-container .contentbank-item').removeClass('selected');\n let targetContentbank = $(this).closest('.contentbank-item');\n targetContentbank.addClass('selected');\n const id = targetContentbank.data('contentid');\n $('#contentbank-preview').empty();\n $('#contentbank-preview').attr('data-contentid', id);\n $('[name=contentid]').val(id);\n // Preview selected content.\n getcontent(id, contextid, '#contentbank-preview');\n\n // Handle xAPI event. We want user to be able to check if the content emits xAPI events (completed, answered)\n // because some content types may not emit these events. Then user can decide\n // if they want students to mark it complete manually or automatically.\n const xapicheck = M.util.get_string('xapicheck', 'ivplugin_contentbank');\n let H5P;\n\n const checkH5P = () => {\n try { // Try to get the H5P object.\n H5P = document.querySelector('#contentbank-preview iframe.h5p-player').contentWindow.H5P;\n } catch (e) {\n H5P = null;\n }\n\n if (typeof H5P !== 'undefined' && H5P !== null) {\n if (H5P.externalDispatcher === undefined) {\n requestAnimationFrame(checkH5P);\n return;\n }\n $(\"#contentbank-preview .xapi\").remove();\n $(`#contentbank-preview[data-contentid=${id}]`)\n .prepend(`
`);\n const audio = new Audio(M.cfg.wwwroot + '/mod/interactivevideo/sounds/pop.mp3');\n audio.play();\n }\n });\n } else {\n requestAnimationFrame(checkH5P);\n }\n };\n\n requestAnimationFrame(checkH5P);\n });\n};\n\n/**\n * Refreshes the content bank by fetching and displaying content items.\n *\n * @param {number} id - The ID of the content to be highlighted.\n * @param {number} coursecontextid - The context ID of the course.\n * @param {boolean} [edit=true] - Whether to show edit options for the content items.\n * @param {Function} [callback] - Optional callback function to be executed after refreshing the content bank.\n * @returns {Promise} - A promise that resolves when the content bank is refreshed.\n */\nconst refreshContentBank = async(id, coursecontextid, edit = true, callback) => {\n $('#contentbank-preview').empty();\n let contentbankitems = await Ajax.call([{\n args: {\n contextid: coursecontextid\n },\n contextid: coursecontextid,\n methodname: 'ivplugin_contentbank_getitems',\n }])[0];\n\n let contents = JSON.parse(contentbankitems.contents);\n let contentbank = $('.modal-body form .contentbank-container');\n contentbank.empty();\n contents.forEach(function(content) {\n const editurl = M.cfg.wwwroot + '/contentbank/edit.php?contextid='\n + coursecontextid + '&id=' + content.id + '&plugin=' + content.type;\n let html = '
';\n if (content.icon) {\n html += '';\n } else {\n html += '';\n }\n\n html += '
' + content.name + '
';\n html += `
\n
`;\n if (edit) {\n html += `\n `;\n }\n\n html += `
`;\n\n\n contentbank.append(html);\n });\n\n if (callback) {\n callback();\n }\n};\n\nexport default {init, getcontent, refreshContentBank};"],"names":["getcontent","id","contextid","target","call","args","methodname","then","response","html","item","catch","init","document","off","on","e","preventDefault","removeClass","this","closest","addClass","empty","data","val","targetContentbank","attr","xapicheck","M","util","get_string","H5P","checkH5P","querySelector","contentWindow","undefined","externalDispatcher","requestAnimationFrame","remove","prepend","event","statement","verb","object","indexOf","Audio","cfg","wwwroot","play","refreshContentBank","async","coursecontextid","edit","callback","contentbankitems","Ajax","contents","JSON","parse","contentbank","forEach","content","editurl","type","icon","name","append"],"mappings":";;;;;;;uKAgCMA,WAAa,CAACC,GAAIC,UAAWC,wBAC1BC,KAAK,CAAC,CACPC,KAAM,CACFJ,GAAIA,GACJC,UAAWA,WAEfA,UAAWA,UACXI,WAAY,kCACZ,GAAGC,MAAMC,UACLL,QACO,mBAAEA,QAAQM,KAAKD,SAASE,MAExBF,WAEZG,OAAM,uBAgJE,CAACC,KA3HFV,gCACRW,UAAUC,IAAI,QAAS,sEACpBC,GAAG,QAAS,sEAAsE,SAASC,GACxFA,EAAEC,qCACA,4CAA4CC,YAAY,gCACxDC,MAAMC,QAAQ,qBAAqBC,SAAS,gCAC5C,wBAAwBC,cACpBrB,IAAK,mBAAEkB,MAAMC,QAAQ,qBAAqBG,KAAK,iCACnD,oBAAoBC,IAAIvB,2BAGhCY,UAAUC,IAAI,QAAS,6DACpBC,GAAG,QAAS,6DAA6D,SAASC,GAC/EA,EAAEC,qCACA,4CAA4CC,YAAY,gBACtDO,mBAAoB,mBAAEN,MAAMC,QAAQ,qBACxCK,kBAAkBJ,SAAS,kBACrBpB,GAAKwB,kBAAkBF,KAAK,iCAChC,wBAAwBD,4BACxB,wBAAwBI,KAAK,iBAAkBzB,wBAC/C,oBAAoBuB,IAAIvB,IAE1BD,WAAWC,GAAIC,UAAW,8BAKpByB,UAAYC,EAAEC,KAAKC,WAAW,YAAa,4BAC7CC,UAEEC,SAAW,SAETD,IAAMlB,SAASoB,cAAc,0CAA0CC,cAAcH,IACvF,MAAOf,GACLe,IAAM,QAGN,MAAOA,IAAqC,SACbI,IAA3BJ,IAAIK,+BACJC,sBAAsBL,8BAGxB,8BAA8BM,2EACSrC,SACpCsC,8HACPZ,qBACEI,IAAIK,mBAAmBrB,GAAG,QAAQ,SAASyB,WACF,4CAAhCA,MAAMjB,KAAKkB,UAAUC,KAAKzC,IACQ,2CAAhCuC,MAAMjB,KAAKkB,UAAUC,KAAKzC,KAC1BuC,MAAMjB,KAAKkB,UAAUE,OAAO1C,GAAG2C,QAAQ,gBAAkB,EAAG,qBAC7D,8BAA8BN,6BAC9B,wBACGC,oKACyBX,EAAEC,KAAKC,WAAW,oBAAqB,mCACvD,IAAIe,MAAMjB,EAAEkB,IAAIC,QAAU,wCAClCC,gBAIdX,sBAAsBL,WAI9BK,sBAAsBL,cA4DZhC,WAAAA,WAAYiD,mBA/CPC,eAAMjD,GAAIkD,qBAAiBC,gEAAaC,oEAC7D,wBAAwB/B,YACtBgC,uBAAyBC,cAAKnD,KAAK,CAAC,CACpCC,KAAM,CACFH,UAAWiD,iBAEfjD,UAAWiD,gBACX7C,WAAY,mCACZ,GAEAkD,SAAWC,KAAKC,MAAMJ,iBAAiBE,UACvCG,aAAc,mBAAE,2CACpBA,YAAYrC,QACZkC,SAASI,SAAQ,SAASC,eAChBC,QAAUlC,EAAEkB,IAAIC,QAAU,mCAC1BI,gBAAkB,OAASU,QAAQ5D,GAAK,WAAa4D,QAAQE,SAC/DtD,KAAO,+DACJoD,QAAQ5D,IAAMA,GAAK,WAAa,IAAM,sBAAwB4D,QAAQ5D,GACvE,qEACF4D,QAAQG,KACRvD,MAAQ,gDAAkDoD,QAAQG,KAAO,MAEzEvD,MAAQ,iDAGZA,MAAQ,4CAA8CoD,QAAQI,KAAO,eACrExD,mKAC4CmB,EAAEC,KAAKC,WAAW,UAAW,qFAErEsB,OACA3C,iEAA4DqD,mJAEpClC,EAAEC,KAAKC,WAAW,OAAQ,6FAItDrB,eAGAkD,YAAYO,OAAOzD,SAGnB4C,UACAA"}
\ No newline at end of file
diff --git a/plugins/contentbank/amd/src/util.js b/plugins/contentbank/amd/src/util.js
index 606d1b4..4d789f4 100644
--- a/plugins/contentbank/amd/src/util.js
+++ b/plugins/contentbank/amd/src/util.js
@@ -141,7 +141,7 @@ const init = (contextid) => {
* @param {Function} [callback] - Optional callback function to be executed after refreshing the content bank.
* @returns {Promise} - A promise that resolves when the content bank is refreshed.
*/
-const refreshContentBank = async (id, coursecontextid, edit = true, callback) => {
+const refreshContentBank = async(id, coursecontextid, edit = true, callback) => {
$('#contentbank-preview').empty();
let contentbankitems = await Ajax.call([{
args: {
diff --git a/plugins/iframe/amd/build/main.min.js.map b/plugins/iframe/amd/build/main.min.js.map
index cbbdd8e..97ca5d1 100644
--- a/plugins/iframe/amd/build/main.min.js.map
+++ b/plugins/iframe/amd/build/main.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"main.min.js","sources":["../src/main.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Main class for the iframe plugin\n *\n * @module ivplugin_iframe/main\n * @copyright 2024 Sokunthearith Makara \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport $ from 'jquery';\nimport Base from 'mod_interactivevideo/type/base';\nexport default class Iframe extends Base {\n /**\n * Called when the edit form is loaded.\n * @param {Object} form The form object\n * @param {Event} event The event object\n * @return {void}\n */\n onEditFormLoaded(form, event) {\n const preview = (embed, ratio) => {\n $('.preview-iframe').html(embed);\n $('.preview-iframe').css('padding-bottom', ratio);\n };\n $(document).off('input', '[name=\"iframeurl\"]').on('input', '[name=\"iframeurl\"]', function(e) {\n e.preventDefault();\n e.stopPropagation();\n $('.preview-iframe').html('').css('padding-bottom', '0');\n $('[name=\"char1\"], [name=\"content\"]').val('');\n if ($('[name=\"iframeurl\"]').val() === '') {\n return;\n }\n const fallback = (url) => {\n $('[name=\"char1\"]').val('56.25%');\n $('[name=\"content\"]').val(``);\n preview(``, '56.25%');\n };\n $.ajax({\n url: M.cfg.wwwroot + '/mod/interactivevideo/plugins/iframe/ajax.php',\n type: 'GET',\n data: {\n action: 'getproviders',\n sesskey: M.cfg.sesskey,\n contextid: M.cfg.contextid,\n },\n success: function(data) {\n const providers = data;\n let url = $('[name=\"iframeurl\"]').val();\n // Format the url to match the provider_url\n let providerUrl = url.split('/')[2];\n const domain = providerUrl.split('.');\n if (domain.length > 2) {\n providerUrl = domain[1] + '.' + domain[2];\n } else {\n providerUrl = domain[0] + '.' + domain[1];\n }\n const provider = providers.find(function(provider) {\n return provider.provider_url.includes(providerUrl);\n });\n if (!provider) {\n fallback(url);\n return;\n }\n if (provider) {\n // Reformat the url to match the endpoints scheme.\n let urlendpoint = provider.endpoints[0].url.replace('{format}', 'json');\n if (urlendpoint.includes('?')) {\n urlendpoint = urlendpoint + '&url=' + url;\n } else {\n urlendpoint = urlendpoint + '?url=' + url;\n }\n if (!urlendpoint.includes('format=json')) {\n urlendpoint = urlendpoint + '&format=json';\n }\n $.ajax({\n url: M.cfg.wwwroot + '/mod/interactivevideo/plugins/iframe/ajax.php',\n data: {\n url: urlendpoint,\n sesskey: M.cfg.sesskey,\n action: 'getoembedinfo',\n contextid: M.cfg.contextid,\n },\n method: \"POST\",\n dataType: \"text\",\n success: function(res) {\n let data;\n try {\n data = JSON.parse(res);\n } catch (e) {\n fallback(url);\n return;\n }\n\n if (!data.html) {\n fallback(url);\n return;\n }\n\n let ratio = '56.25%';\n\n if (!data.width || data.width == 0 || data.width == '100%') {\n if (data.height && data.height !== 0) {\n ratio = data.height + 'px';\n }\n } else {\n ratio = (data.height / data.width * 100) + '%';\n }\n\n $('[name=\"char1\"]').val(ratio);\n // Remove any script tags from the html to avoid errors with requirejs from data.html using regex\n data.html = data.html.replace(/