diff --git a/mod/bigbluebuttonbn/amd/build/recordings.min.js b/mod/bigbluebuttonbn/amd/build/recordings.min.js
index 740c975d427..ca072d3cb3a 100644
--- a/mod/bigbluebuttonbn/amd/build/recordings.min.js
+++ b/mod/bigbluebuttonbn/amd/build/recordings.min.js
@@ -5,6 +5,6 @@ define("mod_bigbluebuttonbn/recordings",["exports","./repository","core/notifica
* @module mod_bigbluebuttonbn/recordings
* @copyright 2021 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */(repository),_modal_factory=_interopRequireDefault(_modal_factory),_modal_events=_interopRequireDefault(_modal_events),_pending=_interopRequireDefault(_pending);const stringsWithKeys={first:"view_recording_yui_first",prev:"view_recording_yui_prev",next:"view_recording_yui_next",last:"view_recording_yui_last",goToLabel:"view_recording_yui_page",goToAction:"view_recording_yui_go",perPage:"view_recording_yui_rows",showAll:"view_recording_yui_show_all"};(0,_prefetch.prefetchStrings)("bigbluebuttonbn",Object.entries(stringsWithKeys).map((entry=>entry[1])));const getStringsForYui=()=>{const stringMap=Object.keys(stringsWithKeys).map((key=>({key:stringsWithKeys[key],component:"mod_bigbluebuttonbn"})));return(0,_str.get_strings)(stringMap).then((stringArray=>Object.assign({},...Object.keys(stringsWithKeys).map(((key,index)=>({[key]:stringArray[index]})))))).catch()},getFormattedData=response=>{const recordingData=response.tabledata;return JSON.parse(recordingData.data)},getTableNode=tableSelector=>document.querySelector(tableSelector),fetchRecordingData=tableSelector=>{const tableNode=getTableNode(tableSelector);return tableNode.dataset.importMode?repository.fetchRecordingsToImport(tableNode.dataset.bbbid,tableNode.dataset.bbbSourceInstanceId,tableNode.dataset.bbbSourceCourseId,tableNode.dataset.tools,tableNode.dataset.groupId):repository.fetchRecordings(tableNode.dataset.bbbid,tableNode.dataset.tools,tableNode.dataset.groupId)},getDataTableFunctions=(tableId,searchFormId,dataTable)=>{const bbbid=getTableNode(tableId).dataset.bbbid,updateTableFromResponse=response=>{if(!response||!response.status)return;dataTable.get("data").reset(getFormattedData(response)),dataTable.set("currentData",dataTable.get("data"));const currentFilter=dataTable.get("currentFilter");currentFilter&&filterByText(currentFilter)},refreshTableData=()=>fetchRecordingData(tableId).then(updateTableFromResponse),filterByText=value=>{const dataModel=dataTable.get("currentData");dataTable.set("currentFilter",value);const escapedRegex=value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),rsearch=new RegExp(".*?".concat(escapedRegex,".*?"),"i");dataTable.set("data",dataModel.filter({asList:!0},(item=>{const name=item.get("recording");if(name&&rsearch.test(name))return!0;const description=item.get("description");return description&&rsearch.test(description)})))},recordingConfirmationMessage=async data=>{var _document$querySelect,_document$querySelect2;const playbackElement=document.querySelector("#playbacks-".concat(data.recordingid)),recordingType=await(0,_str.get_string)("true"===playbackElement.dataset.imported?"view_recording_link":"view_recording","bigbluebuttonbn"),confirmation=await(0,_str.get_string)("view_recording_".concat(data.action,"_confirmation"),"bigbluebuttonbn",recordingType);if("import"===data.action)return confirmation;const associatedLinkCount=null===(_document$querySelect=document.querySelector("a#recording-".concat(data.action,"-").concat(data.recordingid)))||void 0===_document$querySelect||null===(_document$querySelect2=_document$querySelect.dataset)||void 0===_document$querySelect2?void 0:_document$querySelect2.links;if(!associatedLinkCount||0===associatedLinkCount)return confirmation;return await(0,_str.get_string)("view_recording_".concat(data.action,1===associatedLinkCount?"_confirmation_warning_p":"_confirmation_warning_s"),"bigbluebuttonbn",associatedLinkCount)+"\n\n"+confirmation},processAction=e=>{const popoutLink=e.target.closest('[data-action="play"]');if(popoutLink){e.preventDefault();const videoPlayer=window.open("","_blank");return videoPlayer.opener=null,void(videoPlayer.location.href=popoutLink.href)}const clickedLink=e.target.closest("a[data-action]");if(clickedLink&&!clickedLink.classList.contains("disabled")){e.preventDefault();const iconPromise=(0,_loadingicon.addIconToContainerWithPromise)(dataTable.get("boundingBox").getDOMNode());(element=>{const getDataFromAction=(element,dataType)=>{const dataElement=element.closest("[data-".concat(dataType,"]"));return dataElement?dataElement.dataset[dataType]:null},elementData=element.dataset,payload={bigbluebuttonbnid:bbbid,recordingid:getDataFromAction(element,"recordingid"),additionaloptions:getDataFromAction(element,"additionaloptions"),action:elementData.action};if(payload.additionaloptions||(payload.additionaloptions={}),"import"===elementData.action){const bbbsourceid=getDataFromAction(element,"source-instance-id"),bbbcourseid=getDataFromAction(element,"source-course-id");payload.additionaloptions||(payload.additionaloptions={}),payload.additionaloptions.sourceid=bbbsourceid||0,payload.additionaloptions.bbbcourseid=bbbcourseid||0}return payload.additionaloptions=JSON.stringify(payload.additionaloptions),"1"===element.dataset.requireConfirmation?new Promise((resolve=>_modal_factory.default.create({title:(0,_str.get_string)("confirm"),body:recordingConfirmationMessage(payload),type:_modal_factory.default.types.SAVE_CANCEL}).then((async modal=>(modal.setSaveButtonText(await(0,_str.get_string)("ok","moodle")),modal.getRoot().on(_modal_events.default.save,(()=>{resolve(!0)})),modal.getRoot().on(_modal_events.default.hidden,(()=>{modal.destroy(),resolve(!1)})),modal.show(),modal))).catch(Notification.exception))).then((proceed=>proceed?repository.updateRecording(payload):()=>null)):repository.updateRecording(payload)})(clickedLink).then(refreshTableData).catch(_notification.exception).then(iconPromise.resolve).catch()}},processSearchSubmission=e=>{e.preventDefault();const searchInput=e.target.closest("div[role=search]").querySelector("input[name=search]");filterByText(searchInput.value)};return{filterByText:filterByText,refreshTableData:refreshTableData,registerEventListeners:()=>{dataTable.get("boundingBox").getDOMNode().addEventListener("click",processAction);if(document.querySelector(searchFormId)){document.querySelector(searchFormId+" button").addEventListener("click",processSearchSubmission)}}}},setupDatatable=(tableId,searchFormId,response)=>{if(!response)return Promise.resolve();if(!response.status)return Promise.resolve();const recordingData=response.tabledata,pendingPromise=new _pending.default("mod_bigbluebuttonbn/recordings/setupDatatable");return Promise.all([(lang=recordingData.locale,new Promise((resolve=>{YUI({lang:lang}).use("intl","datatable","datatable-sort","datatable-paginator","datatype-number",(Y=>{resolve(Y)}))}))),getStringsForYui()]).then((_ref=>{let[yuiInstance,strings]=_ref;return yuiInstance.Intl.add("datatable-paginator",yuiInstance.config.lang,{...strings}),yuiInstance.DataTable.BodyView.Formatters.customDate=()=>cell=>{return locale=recordingData.locale,date=cell.value,new Date(date).toLocaleDateString(locale,{weekday:"long",year:"numeric",month:"long",day:"numeric"});var locale,date},yuiInstance})).then((yuiInstance=>{const tableData=getFormattedData(response);return yuiInstance.RecordsPaginatorView=Y.Base.create("my-paginator-view",yuiInstance.DataTable.Paginator.View,[],{_modelChange:function(e){var changed=e.changed;changed&&changed.totalItems&&this._updateControlsUI(e.target.get("page"))}}),new yuiInstance.DataTable({paginatorView:"RecordsPaginatorView",width:"1195px",columns:recordingData.columns,data:tableData,rowsPerPage:10,paginatorLocation:["header","footer"],autoSync:!0})})).then((dataTable=>{dataTable.render(tableId);const{registerEventListeners:registerEventListeners}=getDataTableFunctions(tableId,searchFormId,dataTable);return registerEventListeners(),dataTable})).then((dataTable=>(pendingPromise.resolve(),dataTable)));var lang};_exports.init=(tableId,searchFormId)=>{fetchRecordingData(tableId).then((response=>setupDatatable(tableId,searchFormId,response))).catch(_notification.exception)}}));
+ */(repository),_modal_factory=_interopRequireDefault(_modal_factory),_modal_events=_interopRequireDefault(_modal_events),_pending=_interopRequireDefault(_pending);const stringsWithKeys={first:"view_recording_yui_first",prev:"view_recording_yui_prev",next:"view_recording_yui_next",last:"view_recording_yui_last",goToLabel:"view_recording_yui_page",goToAction:"view_recording_yui_go",perPage:"view_recording_yui_rows",showAll:"view_recording_yui_show_all"};(0,_prefetch.prefetchStrings)("bigbluebuttonbn",Object.entries(stringsWithKeys).map((entry=>entry[1])));const getStringsForYui=()=>{const stringMap=Object.keys(stringsWithKeys).map((key=>({key:stringsWithKeys[key],component:"mod_bigbluebuttonbn"})));return(0,_str.get_strings)(stringMap).then((stringArray=>Object.assign({},...Object.keys(stringsWithKeys).map(((key,index)=>({[key]:stringArray[index]})))))).catch()},getFormattedData=response=>{const recordingData=response.tabledata;return JSON.parse(recordingData.data)},getTableNode=tableSelector=>document.querySelector(tableSelector),fetchRecordingData=tableSelector=>{const tableNode=getTableNode(tableSelector);return null===tableNode?Promise.resolve(!1):tableNode.dataset.importMode?repository.fetchRecordingsToImport(tableNode.dataset.bbbid,tableNode.dataset.bbbSourceInstanceId,tableNode.dataset.bbbSourceCourseId,tableNode.dataset.tools,tableNode.dataset.groupId):repository.fetchRecordings(tableNode.dataset.bbbid,tableNode.dataset.tools,tableNode.dataset.groupId)},getDataTableFunctions=(tableId,searchFormId,dataTable)=>{const bbbid=getTableNode(tableId).dataset.bbbid,updateTableFromResponse=response=>{if(!response||!response.status)return;dataTable.get("data").reset(getFormattedData(response)),dataTable.set("currentData",dataTable.get("data"));const currentFilter=dataTable.get("currentFilter");currentFilter&&filterByText(currentFilter)},refreshTableData=()=>fetchRecordingData(tableId).then(updateTableFromResponse),filterByText=value=>{const dataModel=dataTable.get("currentData");dataTable.set("currentFilter",value);const escapedRegex=value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),rsearch=new RegExp(".*?".concat(escapedRegex,".*?"),"i");dataTable.set("data",dataModel.filter({asList:!0},(item=>{const name=item.get("recording");if(name&&rsearch.test(name))return!0;const description=item.get("description");return description&&rsearch.test(description)})))},recordingConfirmationMessage=async data=>{var _document$querySelect,_document$querySelect2;const playbackElement=document.querySelector("#playbacks-".concat(data.recordingid)),recordingType=await(0,_str.get_string)("true"===playbackElement.dataset.imported?"view_recording_link":"view_recording","bigbluebuttonbn"),confirmation=await(0,_str.get_string)("view_recording_".concat(data.action,"_confirmation"),"bigbluebuttonbn",recordingType);if("import"===data.action)return confirmation;const associatedLinkCount=null===(_document$querySelect=document.querySelector("a#recording-".concat(data.action,"-").concat(data.recordingid)))||void 0===_document$querySelect||null===(_document$querySelect2=_document$querySelect.dataset)||void 0===_document$querySelect2?void 0:_document$querySelect2.links;if(!associatedLinkCount||0===associatedLinkCount)return confirmation;return await(0,_str.get_string)("view_recording_".concat(data.action,1===associatedLinkCount?"_confirmation_warning_p":"_confirmation_warning_s"),"bigbluebuttonbn",associatedLinkCount)+"\n\n"+confirmation},processAction=e=>{const popoutLink=e.target.closest('[data-action="play"]');if(popoutLink){e.preventDefault();const videoPlayer=window.open("","_blank");return videoPlayer.opener=null,void(videoPlayer.location.href=popoutLink.href)}const clickedLink=e.target.closest("a[data-action]");if(clickedLink&&!clickedLink.classList.contains("disabled")){e.preventDefault();const iconPromise=(0,_loadingicon.addIconToContainerWithPromise)(dataTable.get("boundingBox").getDOMNode());(element=>{const getDataFromAction=(element,dataType)=>{const dataElement=element.closest("[data-".concat(dataType,"]"));return dataElement?dataElement.dataset[dataType]:null},elementData=element.dataset,payload={bigbluebuttonbnid:bbbid,recordingid:getDataFromAction(element,"recordingid"),additionaloptions:getDataFromAction(element,"additionaloptions"),action:elementData.action};if(payload.additionaloptions||(payload.additionaloptions={}),"import"===elementData.action){const bbbsourceid=getDataFromAction(element,"source-instance-id"),bbbcourseid=getDataFromAction(element,"source-course-id");payload.additionaloptions||(payload.additionaloptions={}),payload.additionaloptions.sourceid=bbbsourceid||0,payload.additionaloptions.bbbcourseid=bbbcourseid||0}return payload.additionaloptions=JSON.stringify(payload.additionaloptions),"1"===element.dataset.requireConfirmation?new Promise((resolve=>_modal_factory.default.create({title:(0,_str.get_string)("confirm"),body:recordingConfirmationMessage(payload),type:_modal_factory.default.types.SAVE_CANCEL}).then((async modal=>(modal.setSaveButtonText(await(0,_str.get_string)("ok","moodle")),modal.getRoot().on(_modal_events.default.save,(()=>{resolve(!0)})),modal.getRoot().on(_modal_events.default.hidden,(()=>{modal.destroy(),resolve(!1)})),modal.show(),modal))).catch(_notification.exception))).then((proceed=>proceed?repository.updateRecording(payload):()=>null)):repository.updateRecording(payload)})(clickedLink).then(refreshTableData).then(iconPromise.resolve).catch(_notification.exception)}},processSearchSubmission=e=>{e.preventDefault();const searchInput=e.target.closest("div[role=search]").querySelector("input[name=search]");filterByText(searchInput.value)};return{filterByText:filterByText,refreshTableData:refreshTableData,registerEventListeners:()=>{dataTable.get("boundingBox").getDOMNode().addEventListener("click",processAction);if(document.querySelector(searchFormId)){document.querySelector(searchFormId+" button").addEventListener("click",processSearchSubmission)}}}},setupDatatable=(tableId,searchFormId,response)=>{if(!response)return Promise.resolve();if(!response.status)return Promise.resolve();const recordingData=response.tabledata,pendingPromise=new _pending.default("mod_bigbluebuttonbn/recordings/setupDatatable");return Promise.all([(lang=recordingData.locale,new Promise((resolve=>{YUI({lang:lang}).use("intl","datatable","datatable-sort","datatable-paginator","datatype-number",(Y=>{resolve(Y)}))}))),getStringsForYui()]).then((_ref=>{let[yuiInstance,strings]=_ref;return yuiInstance.Intl.add("datatable-paginator",yuiInstance.config.lang,{...strings}),yuiInstance.DataTable.BodyView.Formatters.customDate=()=>cell=>{return locale=recordingData.locale,date=cell.value,new Date(date).toLocaleDateString(locale,{weekday:"long",year:"numeric",month:"long",day:"numeric"});var locale,date},yuiInstance})).then((yuiInstance=>{const tableData=getFormattedData(response);return yuiInstance.RecordsPaginatorView=Y.Base.create("my-paginator-view",yuiInstance.DataTable.Paginator.View,[],{_modelChange:function(e){var changed=e.changed;changed&&changed.totalItems&&this._updateControlsUI(e.target.get("page"))}}),new yuiInstance.DataTable({paginatorView:"RecordsPaginatorView",width:"1195px",columns:recordingData.columns,data:tableData,rowsPerPage:10,paginatorLocation:["header","footer"],autoSync:!0})})).then((dataTable=>{dataTable.render(tableId);const{registerEventListeners:registerEventListeners}=getDataTableFunctions(tableId,searchFormId,dataTable);return registerEventListeners(),dataTable})).then((dataTable=>(pendingPromise.resolve(),dataTable)));var lang};_exports.init=(tableId,searchFormId)=>{const pendingPromise=new _pending.default("mod_bigbluebuttonbn/recordings:init");fetchRecordingData(tableId).then((response=>setupDatatable(tableId,searchFormId,response))).then((()=>pendingPromise.resolve())).catch(_notification.exception)}}));
//# sourceMappingURL=recordings.min.js.map
\ No newline at end of file
diff --git a/mod/bigbluebuttonbn/amd/build/recordings.min.js.map b/mod/bigbluebuttonbn/amd/build/recordings.min.js.map
index 0be22238d13..977112e6d7c 100644
--- a/mod/bigbluebuttonbn/amd/build/recordings.min.js.map
+++ b/mod/bigbluebuttonbn/amd/build/recordings.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"recordings.min.js","sources":["../src/recordings.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 * JS for the recordings page on mod_bigbluebuttonbn plugin.\n *\n * @module mod_bigbluebuttonbn/recordings\n * @copyright 2021 Blindside Networks Inc\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as repository from './repository';\nimport {exception as displayException} from 'core/notification';\nimport {prefetchStrings} from 'core/prefetch';\nimport {get_string as getString, get_strings as getStrings} from 'core/str';\nimport {addIconToContainerWithPromise} from 'core/loadingicon';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport Pending from 'core/pending';\n\nconst stringsWithKeys = {\n first: 'view_recording_yui_first',\n prev: 'view_recording_yui_prev',\n next: 'view_recording_yui_next',\n last: 'view_recording_yui_last',\n goToLabel: 'view_recording_yui_page',\n goToAction: 'view_recording_yui_go',\n perPage: 'view_recording_yui_rows',\n showAll: 'view_recording_yui_show_all',\n};\n// Load global strings.\nprefetchStrings('bigbluebuttonbn', Object.entries(stringsWithKeys).map((entry) => entry[1]));\n\nconst getStringsForYui = () => {\n const stringMap = Object.keys(stringsWithKeys).map(key => {\n return {\n key: stringsWithKeys[key],\n component: 'mod_bigbluebuttonbn',\n };\n });\n\n // Return an object with the matching string keys (we want an object with {: ...}).\n return getStrings(stringMap)\n .then((stringArray) => Object.assign({}, ...Object.keys(stringsWithKeys).map(\n (key, index) => ({[key]: stringArray[index]})))\n ).catch();\n};\n\nconst getYuiInstance = lang => new Promise(resolve => {\n // eslint-disable-next-line\n YUI({\n lang,\n }).use('intl', 'datatable', 'datatable-sort', 'datatable-paginator', 'datatype-number', Y => {\n resolve(Y);\n });\n});\n\n/**\n * Format the supplied date per the specified locale.\n *\n * @param {string} locale\n * @param {number} date\n * @returns {array}\n */\nconst formatDate = (locale, date) => {\n const realDate = new Date(date);\n return realDate.toLocaleDateString(locale, {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n });\n};\n\n/**\n * Format response data for the table.\n *\n * @param {string} response JSON-encoded table data\n * @returns {array}\n */\nconst getFormattedData = response => {\n const recordingData = response.tabledata;\n return JSON.parse(recordingData.data);\n};\n\nconst getTableNode = tableSelector => document.querySelector(tableSelector);\n\nconst fetchRecordingData = tableSelector => {\n const tableNode = getTableNode(tableSelector);\n\n if (tableNode.dataset.importMode) {\n return repository.fetchRecordingsToImport(\n tableNode.dataset.bbbid,\n tableNode.dataset.bbbSourceInstanceId,\n tableNode.dataset.bbbSourceCourseId,\n tableNode.dataset.tools,\n tableNode.dataset.groupId\n );\n } else {\n return repository.fetchRecordings(\n tableNode.dataset.bbbid,\n tableNode.dataset.tools,\n tableNode.dataset.groupId\n );\n }\n};\n\n/**\n * Fetch the data table functinos for the specified table.\n *\n * @param {String} tableId in which we will display the table\n * @param {String} searchFormId The Id of the relate.\n * @param {Object} dataTable\n * @returns {Object}\n * @private\n */\nconst getDataTableFunctions = (tableId, searchFormId, dataTable) => {\n const tableNode = getTableNode(tableId);\n const bbbid = tableNode.dataset.bbbid;\n\n const updateTableFromResponse = response => {\n if (!response || !response.status) {\n // There was no output at all.\n return;\n }\n\n dataTable.get('data').reset(getFormattedData(response));\n dataTable.set(\n 'currentData',\n dataTable.get('data')\n );\n\n const currentFilter = dataTable.get('currentFilter');\n if (currentFilter) {\n filterByText(currentFilter);\n }\n };\n\n const refreshTableData = () => fetchRecordingData(tableId).then(updateTableFromResponse);\n\n const filterByText = value => {\n const dataModel = dataTable.get('currentData');\n dataTable.set('currentFilter', value);\n\n const escapedRegex = value.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g, \"\\\\$&\");\n const rsearch = new RegExp(`.*?${escapedRegex}.*?`, 'i');\n\n dataTable.set('data', dataModel.filter({asList: true}, item => {\n const name = item.get('recording');\n if (name && rsearch.test(name)) {\n return true;\n }\n\n const description = item.get('description');\n return description && rsearch.test(description);\n }));\n };\n\n const requestAction = (element) => {\n const getDataFromAction = (element, dataType) => {\n const dataElement = element.closest(`[data-${dataType}]`);\n if (dataElement) {\n return dataElement.dataset[dataType];\n }\n\n return null;\n };\n\n const elementData = element.dataset;\n const payload = {\n bigbluebuttonbnid: bbbid,\n recordingid: getDataFromAction(element, 'recordingid'),\n additionaloptions: getDataFromAction(element, 'additionaloptions'),\n action: elementData.action,\n };\n // Slight change for import, for additional options.\n if (!payload.additionaloptions) {\n payload.additionaloptions = {};\n }\n if (elementData.action === 'import') {\n const bbbsourceid = getDataFromAction(element, 'source-instance-id');\n const bbbcourseid = getDataFromAction(element, 'source-course-id');\n if (!payload.additionaloptions) {\n payload.additionaloptions = {};\n }\n payload.additionaloptions.sourceid = bbbsourceid ? bbbsourceid : 0;\n payload.additionaloptions.bbbcourseid = bbbcourseid ? bbbcourseid : 0;\n }\n // Now additional options should be a json string.\n payload.additionaloptions = JSON.stringify(payload.additionaloptions);\n if (element.dataset.requireConfirmation === \"1\") {\n // Create the confirmation dialogue.\n return new Promise((resolve) =>\n ModalFactory.create({\n title: getString('confirm'),\n body: recordingConfirmationMessage(payload),\n type: ModalFactory.types.SAVE_CANCEL\n }).then(async(modal) => {\n modal.setSaveButtonText(await getString('ok', 'moodle'));\n\n // Handle save event.\n modal.getRoot().on(ModalEvents.save, () => {\n resolve(true);\n });\n\n // Handle hidden event.\n modal.getRoot().on(ModalEvents.hidden, () => {\n // Destroy when hidden.\n modal.destroy();\n resolve(false);\n });\n\n modal.show();\n\n return modal;\n }).catch(Notification.exception)\n ).then((proceed) =>\n proceed ? repository.updateRecording(payload) : () => null\n );\n } else {\n return repository.updateRecording(payload);\n }\n };\n\n const recordingConfirmationMessage = async(data) => {\n\n const playbackElement = document.querySelector(`#playbacks-${data.recordingid}`);\n const recordingType = await getString(\n playbackElement.dataset.imported === 'true' ? 'view_recording_link' : 'view_recording',\n 'bigbluebuttonbn'\n );\n\n const confirmation = await getString(`view_recording_${data.action}_confirmation`, 'bigbluebuttonbn', recordingType);\n\n if (data.action === 'import') {\n return confirmation;\n }\n\n // If it has associated links imported in a different course/activity, show that in confirmation dialog.\n const associatedLinkCount = document.querySelector(`a#recording-${data.action}-${data.recordingid}`)?.dataset?.links;\n if (!associatedLinkCount || associatedLinkCount === 0) {\n return confirmation;\n }\n\n const confirmationWarning = await getString(\n associatedLinkCount === 1\n ? `view_recording_${data.action}_confirmation_warning_p`\n : `view_recording_${data.action}_confirmation_warning_s`,\n 'bigbluebuttonbn',\n associatedLinkCount\n );\n\n return confirmationWarning + '\\n\\n' + confirmation;\n };\n\n /**\n * Process an action event.\n *\n * @param {Event} e\n */\n const processAction = e => {\n const popoutLink = e.target.closest('[data-action=\"play\"]');\n if (popoutLink) {\n e.preventDefault();\n\n const videoPlayer = window.open('', '_blank');\n videoPlayer.opener = null;\n videoPlayer.location.href = popoutLink.href;\n // TODO send a recording viewed event when this event will be implemented.\n return;\n }\n\n // Fetch any clicked anchor.\n const clickedLink = e.target.closest('a[data-action]');\n if (clickedLink && !clickedLink.classList.contains('disabled')) {\n e.preventDefault();\n\n // Create a spinning icon on the table.\n const iconPromise = addIconToContainerWithPromise(dataTable.get('boundingBox').getDOMNode());\n\n requestAction(clickedLink)\n .then(refreshTableData)\n .catch(displayException)\n .then(iconPromise.resolve)\n .catch();\n }\n };\n\n const processSearchSubmission = e => {\n // Prevent the default action.\n e.preventDefault();\n const parentNode = e.target.closest('div[role=search]');\n const searchInput = parentNode.querySelector('input[name=search]');\n filterByText(searchInput.value);\n };\n\n const registerEventListeners = () => {\n // Add event listeners to the table boundingBox.\n const boundingBox = dataTable.get('boundingBox').getDOMNode();\n boundingBox.addEventListener('click', processAction);\n\n // Setup the search from handlers.\n const searchForm = document.querySelector(searchFormId);\n if (searchForm) {\n const searchButton = document.querySelector(searchFormId + ' button');\n searchButton.addEventListener('click', processSearchSubmission);\n }\n };\n\n return {\n filterByText,\n refreshTableData,\n registerEventListeners,\n };\n};\n\n/**\n * Setup the data table for the specified BBB instance.\n *\n * @param {String} tableId in which we will display the table\n * @param {String} searchFormId The Id of the relate.\n * @param {object} response The response from the data request\n * @returns {Promise}\n */\nconst setupDatatable = (tableId, searchFormId, response) => {\n if (!response) {\n return Promise.resolve();\n }\n\n if (!response.status) {\n // Something failed. Continue to show the plain output.\n return Promise.resolve();\n }\n\n const recordingData = response.tabledata;\n\n const pendingPromise = new Pending('mod_bigbluebuttonbn/recordings/setupDatatable');\n return Promise.all([getYuiInstance(recordingData.locale), getStringsForYui()])\n .then(([yuiInstance, strings]) => {\n // Here we use a custom formatter for date.\n // See https://clarle.github.io/yui3/yui/docs/api/classes/DataTable.BodyView.Formatters.html\n // Inspired from examples here: https://clarle.github.io/yui3/yui/docs/datatable/\n // Normally formatter have the prototype: (col) => (cell) => , see:\n // https://clarle.github.io/yui3/yui/docs/api/files/datatable_js_formatters.js.html#l100 .\n const dateCustomFormatter = () => (cell) => formatDate(recordingData.locale, cell.value);\n // Add the fetched strings to the YUI Instance.\n yuiInstance.Intl.add('datatable-paginator', yuiInstance.config.lang, {...strings});\n yuiInstance.DataTable.BodyView.Formatters.customDate = dateCustomFormatter;\n return yuiInstance;\n })\n .then(yuiInstance => {\n\n const tableData = getFormattedData(response);\n yuiInstance.RecordsPaginatorView = Y.Base.create('my-paginator-view', yuiInstance.DataTable.Paginator.View, [], {\n _modelChange: function(e) {\n var changed = e.changed,\n totalItems = (changed && changed.totalItems);\n if (totalItems) {\n this._updateControlsUI(e.target.get('page'));\n }\n }\n });\n return new yuiInstance.DataTable({\n paginatorView: \"RecordsPaginatorView\",\n width: \"1195px\",\n columns: recordingData.columns,\n data: tableData,\n rowsPerPage: 10,\n paginatorLocation: ['header', 'footer'],\n autoSync: true\n });\n })\n .then(dataTable => {\n dataTable.render(tableId);\n const {registerEventListeners} = getDataTableFunctions(\n tableId,\n searchFormId,\n dataTable);\n registerEventListeners();\n return dataTable;\n })\n .then(dataTable => {\n pendingPromise.resolve();\n return dataTable;\n });\n};\n\n/**\n * Initialise recordings code.\n *\n * @method init\n * @param {String} tableId in which we will display the table\n * @param {String} searchFormId The Id of the relate.\n */\nexport const init = (tableId, searchFormId) => {\n fetchRecordingData(tableId)\n .then(response => setupDatatable(tableId, searchFormId, response))\n .catch(displayException);\n};\n"],"names":["stringsWithKeys","first","prev","next","last","goToLabel","goToAction","perPage","showAll","Object","entries","map","entry","getStringsForYui","stringMap","keys","key","component","then","stringArray","assign","index","catch","getFormattedData","response","recordingData","tabledata","JSON","parse","data","getTableNode","tableSelector","document","querySelector","fetchRecordingData","tableNode","dataset","importMode","repository","fetchRecordingsToImport","bbbid","bbbSourceInstanceId","bbbSourceCourseId","tools","groupId","fetchRecordings","getDataTableFunctions","tableId","searchFormId","dataTable","updateTableFromResponse","status","get","reset","set","currentFilter","filterByText","refreshTableData","value","dataModel","escapedRegex","replace","rsearch","RegExp","filter","asList","item","name","test","description","recordingConfirmationMessage","async","playbackElement","recordingid","recordingType","imported","confirmation","action","associatedLinkCount","_document$querySelect","_document$querySelect2","links","processAction","e","popoutLink","target","closest","preventDefault","videoPlayer","window","open","opener","location","href","clickedLink","classList","contains","iconPromise","getDOMNode","element","getDataFromAction","dataType","dataElement","elementData","payload","bigbluebuttonbnid","additionaloptions","bbbsourceid","bbbcourseid","sourceid","stringify","requireConfirmation","Promise","resolve","ModalFactory","create","title","body","type","types","SAVE_CANCEL","modal","setSaveButtonText","getRoot","on","ModalEvents","save","hidden","destroy","show","Notification","exception","proceed","updateRecording","requestAction","displayException","processSearchSubmission","searchInput","registerEventListeners","addEventListener","setupDatatable","pendingPromise","Pending","all","lang","locale","YUI","use","Y","_ref","yuiInstance","strings","Intl","add","config","DataTable","BodyView","Formatters","customDate","cell","formatDate","date","Date","toLocaleDateString","weekday","year","month","day","tableData","RecordsPaginatorView","Base","Paginator","View","_modelChange","changed","totalItems","_updateControlsUI","paginatorView","width","columns","rowsPerPage","paginatorLocation","autoSync","render"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"recordings.min.js","sources":["../src/recordings.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 * JS for the recordings page on mod_bigbluebuttonbn plugin.\n *\n * @module mod_bigbluebuttonbn/recordings\n * @copyright 2021 Blindside Networks Inc\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as repository from './repository';\nimport {exception as displayException} from 'core/notification';\nimport {prefetchStrings} from 'core/prefetch';\nimport {get_string as getString, get_strings as getStrings} from 'core/str';\nimport {addIconToContainerWithPromise} from 'core/loadingicon';\nimport ModalFactory from 'core/modal_factory';\nimport ModalEvents from 'core/modal_events';\nimport Pending from 'core/pending';\n\nconst stringsWithKeys = {\n first: 'view_recording_yui_first',\n prev: 'view_recording_yui_prev',\n next: 'view_recording_yui_next',\n last: 'view_recording_yui_last',\n goToLabel: 'view_recording_yui_page',\n goToAction: 'view_recording_yui_go',\n perPage: 'view_recording_yui_rows',\n showAll: 'view_recording_yui_show_all',\n};\n// Load global strings.\nprefetchStrings('bigbluebuttonbn', Object.entries(stringsWithKeys).map((entry) => entry[1]));\n\nconst getStringsForYui = () => {\n const stringMap = Object.keys(stringsWithKeys).map(key => {\n return {\n key: stringsWithKeys[key],\n component: 'mod_bigbluebuttonbn',\n };\n });\n\n // Return an object with the matching string keys (we want an object with {: ...}).\n return getStrings(stringMap)\n .then((stringArray) => Object.assign({}, ...Object.keys(stringsWithKeys).map(\n (key, index) => ({[key]: stringArray[index]})))\n ).catch();\n};\n\nconst getYuiInstance = lang => new Promise(resolve => {\n // eslint-disable-next-line\n YUI({\n lang,\n }).use('intl', 'datatable', 'datatable-sort', 'datatable-paginator', 'datatype-number', Y => {\n resolve(Y);\n });\n});\n\n/**\n * Format the supplied date per the specified locale.\n *\n * @param {string} locale\n * @param {number} date\n * @returns {array}\n */\nconst formatDate = (locale, date) => {\n const realDate = new Date(date);\n return realDate.toLocaleDateString(locale, {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n });\n};\n\n/**\n * Format response data for the table.\n *\n * @param {string} response JSON-encoded table data\n * @returns {array}\n */\nconst getFormattedData = response => {\n const recordingData = response.tabledata;\n return JSON.parse(recordingData.data);\n};\n\nconst getTableNode = tableSelector => document.querySelector(tableSelector);\n\nconst fetchRecordingData = tableSelector => {\n const tableNode = getTableNode(tableSelector);\n if (tableNode === null) {\n return Promise.resolve(false);\n }\n\n if (tableNode.dataset.importMode) {\n return repository.fetchRecordingsToImport(\n tableNode.dataset.bbbid,\n tableNode.dataset.bbbSourceInstanceId,\n tableNode.dataset.bbbSourceCourseId,\n tableNode.dataset.tools,\n tableNode.dataset.groupId\n );\n } else {\n return repository.fetchRecordings(\n tableNode.dataset.bbbid,\n tableNode.dataset.tools,\n tableNode.dataset.groupId\n );\n }\n};\n\n/**\n * Fetch the data table functinos for the specified table.\n *\n * @param {String} tableId in which we will display the table\n * @param {String} searchFormId The Id of the relate.\n * @param {Object} dataTable\n * @returns {Object}\n * @private\n */\nconst getDataTableFunctions = (tableId, searchFormId, dataTable) => {\n const tableNode = getTableNode(tableId);\n const bbbid = tableNode.dataset.bbbid;\n\n const updateTableFromResponse = response => {\n if (!response || !response.status) {\n // There was no output at all.\n return;\n }\n\n dataTable.get('data').reset(getFormattedData(response));\n dataTable.set(\n 'currentData',\n dataTable.get('data')\n );\n\n const currentFilter = dataTable.get('currentFilter');\n if (currentFilter) {\n filterByText(currentFilter);\n }\n };\n\n const refreshTableData = () => fetchRecordingData(tableId).then(updateTableFromResponse);\n\n const filterByText = value => {\n const dataModel = dataTable.get('currentData');\n dataTable.set('currentFilter', value);\n\n const escapedRegex = value.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g, \"\\\\$&\");\n const rsearch = new RegExp(`.*?${escapedRegex}.*?`, 'i');\n\n dataTable.set('data', dataModel.filter({asList: true}, item => {\n const name = item.get('recording');\n if (name && rsearch.test(name)) {\n return true;\n }\n\n const description = item.get('description');\n return description && rsearch.test(description);\n }));\n };\n\n const requestAction = (element) => {\n const getDataFromAction = (element, dataType) => {\n const dataElement = element.closest(`[data-${dataType}]`);\n if (dataElement) {\n return dataElement.dataset[dataType];\n }\n\n return null;\n };\n\n const elementData = element.dataset;\n const payload = {\n bigbluebuttonbnid: bbbid,\n recordingid: getDataFromAction(element, 'recordingid'),\n additionaloptions: getDataFromAction(element, 'additionaloptions'),\n action: elementData.action,\n };\n // Slight change for import, for additional options.\n if (!payload.additionaloptions) {\n payload.additionaloptions = {};\n }\n if (elementData.action === 'import') {\n const bbbsourceid = getDataFromAction(element, 'source-instance-id');\n const bbbcourseid = getDataFromAction(element, 'source-course-id');\n if (!payload.additionaloptions) {\n payload.additionaloptions = {};\n }\n payload.additionaloptions.sourceid = bbbsourceid ? bbbsourceid : 0;\n payload.additionaloptions.bbbcourseid = bbbcourseid ? bbbcourseid : 0;\n }\n // Now additional options should be a json string.\n payload.additionaloptions = JSON.stringify(payload.additionaloptions);\n if (element.dataset.requireConfirmation === \"1\") {\n // Create the confirmation dialogue.\n return new Promise((resolve) =>\n ModalFactory.create({\n title: getString('confirm'),\n body: recordingConfirmationMessage(payload),\n type: ModalFactory.types.SAVE_CANCEL\n }).then(async(modal) => {\n modal.setSaveButtonText(await getString('ok', 'moodle'));\n\n // Handle save event.\n modal.getRoot().on(ModalEvents.save, () => {\n resolve(true);\n });\n\n // Handle hidden event.\n modal.getRoot().on(ModalEvents.hidden, () => {\n // Destroy when hidden.\n modal.destroy();\n resolve(false);\n });\n\n modal.show();\n\n return modal;\n }).catch(displayException)\n ).then((proceed) =>\n proceed ? repository.updateRecording(payload) : () => null\n );\n } else {\n return repository.updateRecording(payload);\n }\n };\n\n const recordingConfirmationMessage = async(data) => {\n\n const playbackElement = document.querySelector(`#playbacks-${data.recordingid}`);\n const recordingType = await getString(\n playbackElement.dataset.imported === 'true' ? 'view_recording_link' : 'view_recording',\n 'bigbluebuttonbn'\n );\n\n const confirmation = await getString(`view_recording_${data.action}_confirmation`, 'bigbluebuttonbn', recordingType);\n\n if (data.action === 'import') {\n return confirmation;\n }\n\n // If it has associated links imported in a different course/activity, show that in confirmation dialog.\n const associatedLinkCount = document.querySelector(`a#recording-${data.action}-${data.recordingid}`)?.dataset?.links;\n if (!associatedLinkCount || associatedLinkCount === 0) {\n return confirmation;\n }\n\n const confirmationWarning = await getString(\n associatedLinkCount === 1\n ? `view_recording_${data.action}_confirmation_warning_p`\n : `view_recording_${data.action}_confirmation_warning_s`,\n 'bigbluebuttonbn',\n associatedLinkCount\n );\n\n return confirmationWarning + '\\n\\n' + confirmation;\n };\n\n /**\n * Process an action event.\n *\n * @param {Event} e\n */\n const processAction = e => {\n const popoutLink = e.target.closest('[data-action=\"play\"]');\n if (popoutLink) {\n e.preventDefault();\n\n const videoPlayer = window.open('', '_blank');\n videoPlayer.opener = null;\n videoPlayer.location.href = popoutLink.href;\n // TODO send a recording viewed event when this event will be implemented.\n return;\n }\n\n // Fetch any clicked anchor.\n const clickedLink = e.target.closest('a[data-action]');\n if (clickedLink && !clickedLink.classList.contains('disabled')) {\n e.preventDefault();\n\n // Create a spinning icon on the table.\n const iconPromise = addIconToContainerWithPromise(dataTable.get('boundingBox').getDOMNode());\n\n requestAction(clickedLink)\n .then(refreshTableData)\n .then(iconPromise.resolve)\n .catch(displayException);\n }\n };\n\n const processSearchSubmission = e => {\n // Prevent the default action.\n e.preventDefault();\n const parentNode = e.target.closest('div[role=search]');\n const searchInput = parentNode.querySelector('input[name=search]');\n filterByText(searchInput.value);\n };\n\n const registerEventListeners = () => {\n // Add event listeners to the table boundingBox.\n const boundingBox = dataTable.get('boundingBox').getDOMNode();\n boundingBox.addEventListener('click', processAction);\n\n // Setup the search from handlers.\n const searchForm = document.querySelector(searchFormId);\n if (searchForm) {\n const searchButton = document.querySelector(searchFormId + ' button');\n searchButton.addEventListener('click', processSearchSubmission);\n }\n };\n\n return {\n filterByText,\n refreshTableData,\n registerEventListeners,\n };\n};\n\n/**\n * Setup the data table for the specified BBB instance.\n *\n * @param {String} tableId in which we will display the table\n * @param {String} searchFormId The Id of the relate.\n * @param {object} response The response from the data request\n * @returns {Promise}\n */\nconst setupDatatable = (tableId, searchFormId, response) => {\n if (!response) {\n return Promise.resolve();\n }\n\n if (!response.status) {\n // Something failed. Continue to show the plain output.\n return Promise.resolve();\n }\n\n const recordingData = response.tabledata;\n\n const pendingPromise = new Pending('mod_bigbluebuttonbn/recordings/setupDatatable');\n return Promise.all([getYuiInstance(recordingData.locale), getStringsForYui()])\n .then(([yuiInstance, strings]) => {\n // Here we use a custom formatter for date.\n // See https://clarle.github.io/yui3/yui/docs/api/classes/DataTable.BodyView.Formatters.html\n // Inspired from examples here: https://clarle.github.io/yui3/yui/docs/datatable/\n // Normally formatter have the prototype: (col) => (cell) => , see:\n // https://clarle.github.io/yui3/yui/docs/api/files/datatable_js_formatters.js.html#l100 .\n const dateCustomFormatter = () => (cell) => formatDate(recordingData.locale, cell.value);\n // Add the fetched strings to the YUI Instance.\n yuiInstance.Intl.add('datatable-paginator', yuiInstance.config.lang, {...strings});\n yuiInstance.DataTable.BodyView.Formatters.customDate = dateCustomFormatter;\n return yuiInstance;\n })\n .then(yuiInstance => {\n\n const tableData = getFormattedData(response);\n yuiInstance.RecordsPaginatorView = Y.Base.create('my-paginator-view', yuiInstance.DataTable.Paginator.View, [], {\n _modelChange: function(e) {\n var changed = e.changed,\n totalItems = (changed && changed.totalItems);\n if (totalItems) {\n this._updateControlsUI(e.target.get('page'));\n }\n }\n });\n return new yuiInstance.DataTable({\n paginatorView: \"RecordsPaginatorView\",\n width: \"1195px\",\n columns: recordingData.columns,\n data: tableData,\n rowsPerPage: 10,\n paginatorLocation: ['header', 'footer'],\n autoSync: true\n });\n })\n .then(dataTable => {\n dataTable.render(tableId);\n const {registerEventListeners} = getDataTableFunctions(\n tableId,\n searchFormId,\n dataTable);\n registerEventListeners();\n return dataTable;\n })\n .then(dataTable => {\n pendingPromise.resolve();\n return dataTable;\n });\n};\n\n/**\n * Initialise recordings code.\n *\n * @method init\n * @param {String} tableId in which we will display the table\n * @param {String} searchFormId The Id of the relate.\n */\nexport const init = (tableId, searchFormId) => {\n const pendingPromise = new Pending('mod_bigbluebuttonbn/recordings:init');\n\n fetchRecordingData(tableId)\n .then(response => setupDatatable(tableId, searchFormId, response))\n .then(() => pendingPromise.resolve())\n .catch(displayException);\n};\n"],"names":["stringsWithKeys","first","prev","next","last","goToLabel","goToAction","perPage","showAll","Object","entries","map","entry","getStringsForYui","stringMap","keys","key","component","then","stringArray","assign","index","catch","getFormattedData","response","recordingData","tabledata","JSON","parse","data","getTableNode","tableSelector","document","querySelector","fetchRecordingData","tableNode","Promise","resolve","dataset","importMode","repository","fetchRecordingsToImport","bbbid","bbbSourceInstanceId","bbbSourceCourseId","tools","groupId","fetchRecordings","getDataTableFunctions","tableId","searchFormId","dataTable","updateTableFromResponse","status","get","reset","set","currentFilter","filterByText","refreshTableData","value","dataModel","escapedRegex","replace","rsearch","RegExp","filter","asList","item","name","test","description","recordingConfirmationMessage","async","playbackElement","recordingid","recordingType","imported","confirmation","action","associatedLinkCount","_document$querySelect","_document$querySelect2","links","processAction","e","popoutLink","target","closest","preventDefault","videoPlayer","window","open","opener","location","href","clickedLink","classList","contains","iconPromise","getDOMNode","element","getDataFromAction","dataType","dataElement","elementData","payload","bigbluebuttonbnid","additionaloptions","bbbsourceid","bbbcourseid","sourceid","stringify","requireConfirmation","ModalFactory","create","title","body","type","types","SAVE_CANCEL","modal","setSaveButtonText","getRoot","on","ModalEvents","save","hidden","destroy","show","displayException","proceed","updateRecording","requestAction","processSearchSubmission","searchInput","registerEventListeners","addEventListener","setupDatatable","pendingPromise","Pending","all","lang","locale","YUI","use","Y","_ref","yuiInstance","strings","Intl","add","config","DataTable","BodyView","Formatters","customDate","cell","formatDate","date","Date","toLocaleDateString","weekday","year","month","day","tableData","RecordsPaginatorView","Base","Paginator","View","_modelChange","changed","totalItems","_updateControlsUI","paginatorView","width","columns","rowsPerPage","paginatorLocation","autoSync","render"],"mappings":"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"}
\ No newline at end of file
diff --git a/mod/bigbluebuttonbn/amd/build/roomupdater.min.js b/mod/bigbluebuttonbn/amd/build/roomupdater.min.js
index 51cf33805e2..8a6187a3388 100644
--- a/mod/bigbluebuttonbn/amd/build/roomupdater.min.js
+++ b/mod/bigbluebuttonbn/amd/build/roomupdater.min.js
@@ -1,10 +1,10 @@
-define("mod_bigbluebuttonbn/roomupdater",["exports","core/templates","core/notification","./repository"],(function(_exports,_templates,_notification,_repository){var obj;
+define("mod_bigbluebuttonbn/roomupdater",["exports","core/pending","core/templates","core/notification","./repository"],(function(_exports,_pending,_templates,_notification,_repository){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* JS room updater.
*
* @module mod_bigbluebuttonbn/roomupdater
* @copyright 2021 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.updateRoom=_exports.stop=_exports.start=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};let timerReference=null,timerRunning=!1,pollInterval=0,pollIntervalFactor=1;const resetValues=()=>{timerRunning=!1,timerReference=null,pollInterval=0,pollIntervalFactor=1};_exports.start=interval=>{resetValues(),timerRunning=!0,pollInterval=interval,poll()};_exports.stop=()=>{timerReference&&clearTimeout(timerReference),resetValues()};const poll=()=>{timerRunning&&pollInterval&&updateRoom().then((updateOk=>(updateOk||(pollIntervalFactor=pollIntervalFactor<10?pollIntervalFactor+1:10),timerReference=setTimeout((()=>poll()),pollInterval*pollIntervalFactor),!0))).catch()},updateRoom=function(){let updatecache=arguments.length>0&&void 0!==arguments[0]&&arguments[0];const bbbRoomViewElement=document.getElementById("bigbluebuttonbn-room-view");if(!bbbRoomViewElement)return Promise.resolve(!1);const bbbId=bbbRoomViewElement.dataset.bbbId,groupId=bbbRoomViewElement.dataset.groupId;return(0,_repository.getMeetingInfo)(bbbId,groupId,updatecache).then((data=>(data.haspresentations=!(!data.presentations||!data.presentations.length),_templates.default.renderForPromise("mod_bigbluebuttonbn/room_view",data)))).then((_ref=>{let{html:html,js:js}=_ref;return _templates.default.replaceNode(bbbRoomViewElement,html,js)})).then((()=>!0)).catch((ex=>((0,_notification.exception)(ex),!1)))};_exports.updateRoom=updateRoom}));
+ */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.updateRoom=_exports.stop=_exports.start=void 0,_pending=_interopRequireDefault(_pending),_templates=_interopRequireDefault(_templates);let timerReference=null,timerRunning=!1,pollInterval=0,pollIntervalFactor=1;const resetValues=()=>{timerRunning=!1,timerReference=null,pollInterval=0,pollIntervalFactor=1};_exports.start=interval=>{resetValues(),timerRunning=!0,pollInterval=interval,poll()};_exports.stop=()=>{timerReference&&clearTimeout(timerReference),resetValues()};const poll=()=>{timerRunning&&pollInterval&&updateRoom().then((updateOk=>(updateOk||(pollIntervalFactor=pollIntervalFactor<10?pollIntervalFactor+1:10),timerReference=setTimeout((()=>poll()),pollInterval*pollIntervalFactor),!0))).catch()},updateRoom=function(){let updatecache=arguments.length>0&&void 0!==arguments[0]&&arguments[0];const bbbRoomViewElement=document.getElementById("bigbluebuttonbn-room-view");if(null===bbbRoomViewElement)return Promise.resolve(!1);const bbbId=bbbRoomViewElement.dataset.bbbId,groupId=bbbRoomViewElement.dataset.groupId,pendingPromise=new _pending.default("mod_bigbluebuttonbn/roomupdater:updateRoom");return(0,_repository.getMeetingInfo)(bbbId,groupId,updatecache).then((data=>(data.haspresentations=!(!data.presentations||!data.presentations.length),_templates.default.renderForPromise("mod_bigbluebuttonbn/room_view",data)))).then((_ref=>{let{html:html,js:js}=_ref;return _templates.default.replaceNode(bbbRoomViewElement,html,js)})).then((()=>pendingPromise.resolve())).catch(_notification.exception)};_exports.updateRoom=updateRoom}));
//# sourceMappingURL=roomupdater.min.js.map
\ No newline at end of file
diff --git a/mod/bigbluebuttonbn/amd/build/roomupdater.min.js.map b/mod/bigbluebuttonbn/amd/build/roomupdater.min.js.map
index 8c46af6713f..3209314246f 100644
--- a/mod/bigbluebuttonbn/amd/build/roomupdater.min.js.map
+++ b/mod/bigbluebuttonbn/amd/build/roomupdater.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"roomupdater.min.js","sources":["../src/roomupdater.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 * JS room updater.\n *\n * @module mod_bigbluebuttonbn/roomupdater\n * @copyright 2021 Blindside Networks Inc\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from \"core/templates\";\nimport {exception as displayException} from 'core/notification';\nimport {getMeetingInfo} from './repository';\n\nlet timerReference = null;\nlet timerRunning = false;\nlet pollInterval = 0;\nlet pollIntervalFactor = 1;\nconst MAX_POLL_INTERVAL_FACTOR = 10;\n\nconst resetValues = () => {\n timerRunning = false;\n timerReference = null;\n pollInterval = 0;\n pollIntervalFactor = 1;\n};\n\n/**\n * Start the information poller.\n * @param {Number} interval interval in miliseconds between each poll action.\n */\nexport const start = (interval) => {\n resetValues();\n timerRunning = true;\n pollInterval = interval;\n poll();\n};\n\n/**\n * Stop the room updater.\n */\nexport const stop = () => {\n if (timerReference) {\n clearTimeout(timerReference);\n }\n resetValues();\n};\n\n/**\n * Start the information poller.\n */\nconst poll = () => {\n if (!timerRunning || !pollInterval) {\n // The poller has been stopped.\n return;\n }\n updateRoom()\n .then((updateOk) => {\n if (!updateOk) {\n pollIntervalFactor = (pollIntervalFactor < MAX_POLL_INTERVAL_FACTOR) ?\n pollIntervalFactor + 1 : MAX_POLL_INTERVAL_FACTOR;\n // We make sure if there is an error that we do not try too often.\n }\n timerReference = setTimeout(() => poll(), pollInterval * pollIntervalFactor);\n return true;\n })\n .catch();\n};\n\n/**\n * Update the room information.\n *\n * @param {boolean} [updatecache=false] should we update cache\n * @returns {Promise}\n */\nexport const updateRoom = (updatecache = false) => {\n const bbbRoomViewElement = document.getElementById('bigbluebuttonbn-room-view');\n if (!bbbRoomViewElement) {\n return Promise.resolve(false);\n }\n const bbbId = bbbRoomViewElement.dataset.bbbId;\n const groupId = bbbRoomViewElement.dataset.groupId;\n return getMeetingInfo(bbbId, groupId, updatecache)\n .then(data => {\n // Just make sure we have the right information for the template.\n data.haspresentations = !!(data.presentations && data.presentations.length);\n return Templates.renderForPromise('mod_bigbluebuttonbn/room_view', data);\n })\n .then(({html, js}) => Templates.replaceNode(bbbRoomViewElement, html, js))\n .then(() => true)\n .catch((ex) => {\n displayException(ex);\n return false;\n });\n};\n"],"names":["timerReference","timerRunning","pollInterval","pollIntervalFactor","resetValues","interval","poll","clearTimeout","updateRoom","then","updateOk","setTimeout","catch","updatecache","bbbRoomViewElement","document","getElementById","Promise","resolve","bbbId","dataset","groupId","data","haspresentations","presentations","length","Templates","renderForPromise","_ref","html","js","replaceNode","ex"],"mappings":";;;;;;;uLA2BIA,eAAiB,KACjBC,cAAe,EACfC,aAAe,EACfC,mBAAqB,QAGnBC,YAAc,KAChBH,cAAe,EACfD,eAAiB,KACjBE,aAAe,EACfC,mBAAqB,kBAOHE,WAClBD,cACAH,cAAe,EACfC,aAAeG,SACfC,sBAMgB,KACZN,gBACAO,aAAaP,gBAEjBI,qBAMEE,KAAO,KACJL,cAAiBC,cAItBM,aACKC,MAAMC,WACEA,WACDP,mBAAsBA,mBAzCL,GA0CbA,mBAAqB,EA1CR,IA6CrBH,eAAiBW,YAAW,IAAML,QAAQJ,aAAeC,qBAClD,KAEVS,SASIJ,WAAa,eAACK,0EACjBC,mBAAqBC,SAASC,eAAe,iCAC9CF,0BACMG,QAAQC,SAAQ,SAErBC,MAAQL,mBAAmBM,QAAQD,MACnCE,QAAUP,mBAAmBM,QAAQC,eACpC,8BAAeF,MAAOE,QAASR,aACjCJ,MAAKa,OAEFA,KAAKC,oBAAsBD,KAAKE,gBAAiBF,KAAKE,cAAcC,QAC7DC,mBAAUC,iBAAiB,gCAAiCL,SAEtEb,MAAKmB,WAACC,KAACA,KAADC,GAAOA,gBAAQJ,mBAAUK,YAAYjB,mBAAoBe,KAAMC,OACrErB,MAAK,KAAM,IACXG,OAAOoB,iCACaA,KACV"}
\ No newline at end of file
+{"version":3,"file":"roomupdater.min.js","sources":["../src/roomupdater.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 * JS room updater.\n *\n * @module mod_bigbluebuttonbn/roomupdater\n * @copyright 2021 Blindside Networks Inc\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\nimport Templates from \"core/templates\";\nimport {exception as displayException} from 'core/notification';\nimport {getMeetingInfo} from './repository';\n\nlet timerReference = null;\nlet timerRunning = false;\nlet pollInterval = 0;\nlet pollIntervalFactor = 1;\nconst MAX_POLL_INTERVAL_FACTOR = 10;\n\nconst resetValues = () => {\n timerRunning = false;\n timerReference = null;\n pollInterval = 0;\n pollIntervalFactor = 1;\n};\n\n/**\n * Start the information poller.\n * @param {Number} interval interval in miliseconds between each poll action.\n */\nexport const start = (interval) => {\n resetValues();\n timerRunning = true;\n pollInterval = interval;\n poll();\n};\n\n/**\n * Stop the room updater.\n */\nexport const stop = () => {\n if (timerReference) {\n clearTimeout(timerReference);\n }\n resetValues();\n};\n\n/**\n * Start the information poller.\n */\nconst poll = () => {\n if (!timerRunning || !pollInterval) {\n // The poller has been stopped.\n return;\n }\n updateRoom()\n .then((updateOk) => {\n if (!updateOk) {\n pollIntervalFactor = (pollIntervalFactor < MAX_POLL_INTERVAL_FACTOR) ?\n pollIntervalFactor + 1 : MAX_POLL_INTERVAL_FACTOR;\n // We make sure if there is an error that we do not try too often.\n }\n timerReference = setTimeout(() => poll(), pollInterval * pollIntervalFactor);\n return true;\n })\n .catch();\n};\n\n/**\n * Update the room information.\n *\n * @param {boolean} [updatecache=false] should we update cache\n * @returns {Promise}\n */\nexport const updateRoom = (updatecache = false) => {\n const bbbRoomViewElement = document.getElementById('bigbluebuttonbn-room-view');\n if (bbbRoomViewElement === null) {\n return Promise.resolve(false);\n }\n\n const bbbId = bbbRoomViewElement.dataset.bbbId;\n const groupId = bbbRoomViewElement.dataset.groupId;\n\n const pendingPromise = new Pending('mod_bigbluebuttonbn/roomupdater:updateRoom');\n\n return getMeetingInfo(bbbId, groupId, updatecache)\n .then(data => {\n // Just make sure we have the right information for the template.\n data.haspresentations = !!(data.presentations && data.presentations.length);\n return Templates.renderForPromise('mod_bigbluebuttonbn/room_view', data);\n })\n .then(({html, js}) => Templates.replaceNode(bbbRoomViewElement, html, js))\n .then(() => pendingPromise.resolve())\n .catch(displayException);\n};\n"],"names":["timerReference","timerRunning","pollInterval","pollIntervalFactor","resetValues","interval","poll","clearTimeout","updateRoom","then","updateOk","setTimeout","catch","updatecache","bbbRoomViewElement","document","getElementById","Promise","resolve","bbbId","dataset","groupId","pendingPromise","Pending","data","haspresentations","presentations","length","Templates","renderForPromise","_ref","html","js","replaceNode","displayException"],"mappings":";;;;;;;iNA4BIA,eAAiB,KACjBC,cAAe,EACfC,aAAe,EACfC,mBAAqB,QAGnBC,YAAc,KAChBH,cAAe,EACfD,eAAiB,KACjBE,aAAe,EACfC,mBAAqB,kBAOHE,WAClBD,cACAH,cAAe,EACfC,aAAeG,SACfC,sBAMgB,KACZN,gBACAO,aAAaP,gBAEjBI,qBAMEE,KAAO,KACJL,cAAiBC,cAItBM,aACKC,MAAMC,WACEA,WACDP,mBAAsBA,mBAzCL,GA0CbA,mBAAqB,EA1CR,IA6CrBH,eAAiBW,YAAW,IAAML,QAAQJ,aAAeC,qBAClD,KAEVS,SASIJ,WAAa,eAACK,0EACjBC,mBAAqBC,SAASC,eAAe,gCACxB,OAAvBF,0BACOG,QAAQC,SAAQ,SAGrBC,MAAQL,mBAAmBM,QAAQD,MACnCE,QAAUP,mBAAmBM,QAAQC,QAErCC,eAAiB,IAAIC,iBAAQ,qDAE5B,8BAAeJ,MAAOE,QAASR,aACjCJ,MAAKe,OAEFA,KAAKC,oBAAsBD,KAAKE,gBAAiBF,KAAKE,cAAcC,QAC7DC,mBAAUC,iBAAiB,gCAAiCL,SAEtEf,MAAKqB,WAACC,KAACA,KAADC,GAAOA,gBAAQJ,mBAAUK,YAAYnB,mBAAoBiB,KAAMC,OACrEvB,MAAK,IAAMa,eAAeJ,YAC1BN,MAAMsB"}
\ No newline at end of file
diff --git a/mod/bigbluebuttonbn/amd/src/recordings.js b/mod/bigbluebuttonbn/amd/src/recordings.js
index a8c05f13244..23ba066d8de 100644
--- a/mod/bigbluebuttonbn/amd/src/recordings.js
+++ b/mod/bigbluebuttonbn/amd/src/recordings.js
@@ -99,6 +99,9 @@ const getTableNode = tableSelector => document.querySelector(tableSelector);
const fetchRecordingData = tableSelector => {
const tableNode = getTableNode(tableSelector);
+ if (tableNode === null) {
+ return Promise.resolve(false);
+ }
if (tableNode.dataset.importMode) {
return repository.fetchRecordingsToImport(
@@ -225,7 +228,7 @@ const getDataTableFunctions = (tableId, searchFormId, dataTable) => {
modal.show();
return modal;
- }).catch(Notification.exception)
+ }).catch(displayException)
).then((proceed) =>
proceed ? repository.updateRecording(payload) : () => null
);
@@ -292,9 +295,8 @@ const getDataTableFunctions = (tableId, searchFormId, dataTable) => {
requestAction(clickedLink)
.then(refreshTableData)
- .catch(displayException)
.then(iconPromise.resolve)
- .catch();
+ .catch(displayException);
}
};
@@ -405,7 +407,10 @@ const setupDatatable = (tableId, searchFormId, response) => {
* @param {String} searchFormId The Id of the relate.
*/
export const init = (tableId, searchFormId) => {
+ const pendingPromise = new Pending('mod_bigbluebuttonbn/recordings:init');
+
fetchRecordingData(tableId)
.then(response => setupDatatable(tableId, searchFormId, response))
+ .then(() => pendingPromise.resolve())
.catch(displayException);
};
diff --git a/mod/bigbluebuttonbn/amd/src/roomupdater.js b/mod/bigbluebuttonbn/amd/src/roomupdater.js
index 13ccfca7391..b33408bc4e6 100644
--- a/mod/bigbluebuttonbn/amd/src/roomupdater.js
+++ b/mod/bigbluebuttonbn/amd/src/roomupdater.js
@@ -21,6 +21,7 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
+import Pending from 'core/pending';
import Templates from "core/templates";
import {exception as displayException} from 'core/notification';
import {getMeetingInfo} from './repository';
@@ -88,11 +89,15 @@ const poll = () => {
*/
export const updateRoom = (updatecache = false) => {
const bbbRoomViewElement = document.getElementById('bigbluebuttonbn-room-view');
- if (!bbbRoomViewElement) {
+ if (bbbRoomViewElement === null) {
return Promise.resolve(false);
}
+
const bbbId = bbbRoomViewElement.dataset.bbbId;
const groupId = bbbRoomViewElement.dataset.groupId;
+
+ const pendingPromise = new Pending('mod_bigbluebuttonbn/roomupdater:updateRoom');
+
return getMeetingInfo(bbbId, groupId, updatecache)
.then(data => {
// Just make sure we have the right information for the template.
@@ -100,9 +105,6 @@ export const updateRoom = (updatecache = false) => {
return Templates.renderForPromise('mod_bigbluebuttonbn/room_view', data);
})
.then(({html, js}) => Templates.replaceNode(bbbRoomViewElement, html, js))
- .then(() => true)
- .catch((ex) => {
- displayException(ex);
- return false;
- });
+ .then(() => pendingPromise.resolve())
+ .catch(displayException);
};
diff --git a/mod/bigbluebuttonbn/tests/behat/add_instance.feature b/mod/bigbluebuttonbn/tests/behat/add_instance.feature
index 4d5c7a41f17..6483591e2e6 100644
--- a/mod/bigbluebuttonbn/tests/behat/add_instance.feature
+++ b/mod/bigbluebuttonbn/tests/behat/add_instance.feature
@@ -36,16 +36,17 @@ Feature: bigbluebuttonbn instance
And I should see "Recordings"
Scenario: Add a Recording Only activity and check that no live session settings are available for this instance type
- Given I am on the "Test course" "course" page logged in as "admin"
- And I am on "Test course" course homepage with editing mode on
+ When I turn editing mode on
+ And I change window size to "large"
And I add a "BigBlueButton" to section "1"
- When I select "Recordings only" from the "Instance type" singleselect
+ And I select "Recordings only" from the "Instance type" singleselect
Then I should not see "Lock settings"
Scenario Outline: Add an activity and check that required settings are available for the three types of instance types
When I turn editing mode on
+ And I change window size to "large"
And I add a "BigBlueButton" to section "1"
- And I select "" from the "Instance type" singleselect
+ And I select "" from the "Instance type" singleselect
Then I should see "Restrict access"
Examples:
diff --git a/mod/bigbluebuttonbn/tests/behat/recordings.feature b/mod/bigbluebuttonbn/tests/behat/recordings.feature
index fc182bd2e92..edeb244d124 100644
--- a/mod/bigbluebuttonbn/tests/behat/recordings.feature
+++ b/mod/bigbluebuttonbn/tests/behat/recordings.feature
@@ -24,36 +24,25 @@ Feature: The recording can be managed through the room page
| RoomRecordings | Recording 3 | Description 3 | 0 |
| RoomRecordings | Recording 4 | Description 4 | 1 |
- @javascript
- Scenario: Recordings are not listed until the server informs that they are available
- Given I am on the "RoomRecordings" "bigbluebuttonbn activity" page logged in as admin
- And I should not see "Recording 3"
- When the BigBlueButtonBN server has sent recording ready notifications
- And I reload the page
- And I should see "Recording 3"
-
- @javascript
- Scenario: Recordings are not listed until we can fetch their metadata, then they are listed
- Given I am on the "RoomRecordings" "bigbluebuttonbn activity" page logged in as admin
- # Recording 3 will be fetched and metadata will be present so, we will see it.
- And I should not see "Recording 3"
- And I should not see "Recording 4"
- When the BigBlueButtonBN server has sent recording ready notifications
- And I reload the page
- Then I should see "Recording 3"
- And I should not see "Recording 4"
-
@javascript
Scenario: I can see the recordings related to an activity
- Given the BigBlueButtonBN server has sent recording ready notifications
- When I am on the "RoomRecordings" "bigbluebuttonbn activity" page logged in as admin
+ Given I am on the "RoomRecordings" "bigbluebuttonbn activity" page logged in as admin
+ And "Recording 1" "table_row" should exist
+ And "Recording 2" "table_row" should exist
+ And "Recording 3" "table_row" should not exist
+ And "Recording 4" "table_row" should not exist
+ # Recording 3 will be fetched and metadata will be present so, we will see it.
+ When the BigBlueButtonBN server has sent recording ready notifications
+ And I run the scheduled task "mod_bigbluebuttonbn\task\check_pending_recordings"
+ And I reload the page
Then "Recording 1" "table_row" should exist
And "Recording 2" "table_row" should exist
+ And "Recording 3" "table_row" should exist
+ And "Recording 4" "table_row" should not exist
@javascript
Scenario: I can rename the recording
- Given the BigBlueButtonBN server has sent recording ready notifications
- And I am on the "RoomRecordings" "bigbluebuttonbn activity" page logged in as admin
+ Given I am on the "RoomRecordings" "bigbluebuttonbn activity" page logged in as admin
When I set the field "Edit name" in the "Recording 1" "table_row" to "Recording with an updated name 1"
Then I should see "Recording with an updated name 1"
And I should see "Recording 2"
@@ -63,8 +52,7 @@ Feature: The recording can be managed through the room page
@javascript
Scenario: I can set a new description for this recording
- Given the BigBlueButtonBN server has sent recording ready notifications
- And I am on the "RoomRecordings" "bigbluebuttonbn activity" page logged in as admin
+ Given I am on the "RoomRecordings" "bigbluebuttonbn activity" page logged in as admin
When I set the field "Edit description" in the "Recording 1" "table_row" to "This is a new recording description 1"
Then I should see "This is a new recording description 1"
And I should see "Description 2" in the "Recording 2" "table_row"
@@ -74,10 +62,8 @@ Feature: The recording can be managed through the room page
@javascript
Scenario: I can delete a recording
- Given the BigBlueButtonBN server has sent recording ready notifications
- And I am on the "RoomRecordings" "bigbluebuttonbn activity" page logged in as admin
+ Given I am on the "RoomRecordings" "bigbluebuttonbn activity" page logged in as admin
When I click on "a[data-action='delete']" "css_element" in the "Recording 1" "table_row"
- And I wait until the page is ready
And I click on "OK" "button" in the "Confirm" "dialogue"
Then I should not see "Recording 1"
And I should see "Recording 2"