From 4ffb8ab1a5c49fcbc74e2358169c3f3d0639db39 Mon Sep 17 00:00:00 2001 From: Jan Britz Date: Tue, 27 Feb 2024 12:19:51 +0100 Subject: [PATCH] feat: favourite package via UI --- amd/build/edit_question.min.js | 2 +- amd/build/edit_question.min.js.map | 2 +- .../package_search/components/package.min.js | 3 + .../components/package.min.js.map | 1 + .../components/pagination.min.js | 2 +- .../components/pagination.min.js.map | 2 +- .../components/tab_content.min.js | 2 +- .../components/tab_content.min.js.map | 2 +- .../components/tab_header.min.js | 2 +- .../components/tab_header.min.js.map | 2 +- amd/build/package_search/mutations.min.js | 2 +- amd/build/package_search/mutations.min.js.map | 2 +- amd/build/package_search/reactive.min.js | 2 +- amd/build/package_search/reactive.min.js.map | 2 +- amd/build/utils.min.js | 3 + amd/build/utils.min.js.map | 1 + amd/src/edit_question.js | 31 ++++++++-- amd/src/package_search/components/package.js | 53 +++++++++++++++++ .../package_search/components/pagination.js | 6 +- .../package_search/components/tab_content.js | 23 +++++--- .../package_search/components/tab_header.js | 4 +- amd/src/package_search/mutations.js | 59 ++++++++++++++++++- amd/src/package_search/reactive.js | 32 ++++------ amd/src/utils.js | 42 +++++++++++++ edit_questionpy_form.php | 9 ++- lang/en/qtype_questionpy.php | 2 + styles.css | 6 ++ templates/package/package_selection.mustache | 52 ++++++++++------ version.php | 2 +- 29 files changed, 282 insertions(+), 71 deletions(-) create mode 100644 amd/build/package_search/components/package.min.js create mode 100644 amd/build/package_search/components/package.min.js.map create mode 100644 amd/build/utils.min.js create mode 100644 amd/build/utils.min.js.map create mode 100644 amd/src/package_search/components/package.js create mode 100644 amd/src/utils.js diff --git a/amd/build/edit_question.min.js b/amd/build/edit_question.min.js index 51111ab0..53deb809 100644 --- a/amd/build/edit_question.min.js +++ b/amd/build/edit_question.min.js @@ -1,3 +1,3 @@ -define("qtype_questionpy/edit_question",["exports","core_form/changechecker"],(function(_exports,_changechecker){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.initActionButton=function(cardId,selected){const packageChanged=document.querySelector('input[name="qpy_package_changed"]'),packageHash=document.querySelector('input[name="qpy_package_hash"]'),card=document.getElementById(cardId);if(selected){const changeButton=card.getElementsByClassName("qpy-version-selection-button")[0];changeButton.addEventListener("click",(e=>{e.preventDefault(),packageChanged.removeAttribute("disabled"),packageHash.value="",(0,_changechecker.resetFormDirtyState)(changeButton),e.target.form.submit()}))}else{const selectedHash=card.getElementsByClassName("qpy-version-selection")[0],selectButton=card.getElementsByClassName("qpy-version-selection-button")[0];selectButton.addEventListener("click",(e=>{e.preventDefault(),packageChanged.removeAttribute("disabled"),packageHash.value=selectedHash.value,(0,_changechecker.resetFormDirtyState)(selectButton),e.target.form.submit()}))}}})); +define("qtype_questionpy/edit_question",["exports","core_form/changechecker","core/notification","qtype_questionpy/utils"],(function(_exports,_changechecker,_notification,_utils){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.initActionButton=function(card,selected){const packageChanged=document.querySelector('input[name="qpy_package_changed"]'),packageHash=document.querySelector('input[name="qpy_package_hash"]');if(selected){const changeButton=card.getElementsByClassName("qpy-version-selection-button")[0];changeButton.addEventListener("click",(e=>{e.preventDefault(),packageChanged.removeAttribute("disabled"),packageHash.value="",(0,_changechecker.resetFormDirtyState)(changeButton),e.target.form.submit()}))}else{const selectedHash=card.getElementsByClassName("qpy-version-selection")[0],selectButton=card.getElementsByClassName("qpy-version-selection-button")[0];selectButton.addEventListener("click",(e=>{e.preventDefault(),packageChanged.removeAttribute("disabled"),packageHash.value=selectedHash.value,(0,_changechecker.resetFormDirtyState)(selectButton),e.target.form.submit()}))}},_exports.initFavouriteButton=function(card,packageId,contextId){const button=card.querySelector('[data-for="favourite-button"]');button.addEventListener("click",(async()=>{try{const isFavourite=button.hasAttribute("data-is-favourite");await(0,_utils.favouritePackage)(packageId,!isFavourite,contextId)&&button.toggleAttribute("data-is-favourite",!isFavourite)}catch(exception){await _notification.default.exception(exception)}}))},_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj}})); //# sourceMappingURL=edit_question.min.js.map \ No newline at end of file diff --git a/amd/build/edit_question.min.js.map b/amd/build/edit_question.min.js.map index cc6b893d..8fd1b50a 100644 --- a/amd/build/edit_question.min.js.map +++ b/amd/build/edit_question.min.js.map @@ -1 +1 @@ -{"version":3,"file":"edit_question.min.js","sources":["../src/edit_question.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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\nimport {resetFormDirtyState} from 'core_form/changechecker';\n\n/**\n * This function is called by the package_selection-template and initializes the action button.\n *\n * When the package is changed, this function enables the hidden form element qpy_package_changed and\n * submits the form. Since qpy_package_changed is registered as a no-submit button, it prevents the form\n * data from being saved to the question, while still re-rendering the form with access to the new selected package\n * hash.\n *\n * @param {string} cardId\n * @param {boolean} selected\n */\nexport function initActionButton(cardId, selected) {\n const packageChanged = document.querySelector('input[name=\"qpy_package_changed\"]');\n const packageHash = document.querySelector('input[name=\"qpy_package_hash\"]');\n\n const card = document.getElementById(cardId);\n\n if (selected) {\n // Initialize the button to change the package.\n const changeButton = card.getElementsByClassName(\"qpy-version-selection-button\")[0];\n changeButton.addEventListener(\"click\", (e) => {\n e.preventDefault();\n\n // Remove package hash.\n packageChanged.removeAttribute(\"disabled\");\n packageHash.value = '';\n\n // We do not want any form checking when changing a package.\n resetFormDirtyState(changeButton);\n e.target.form.submit();\n });\n } else {\n const selectedHash = card.getElementsByClassName(\"qpy-version-selection\")[0];\n const selectButton = card.getElementsByClassName(\"qpy-version-selection-button\")[0];\n\n selectButton.addEventListener(\"click\", (e) => {\n e.preventDefault();\n\n // Set package hash.\n packageChanged.removeAttribute(\"disabled\");\n packageHash.value = selectedHash.value;\n\n // We do not want any form checking when selecting a package.\n resetFormDirtyState(selectButton);\n e.target.form.submit();\n });\n }\n}\n\n"],"names":["cardId","selected","packageChanged","document","querySelector","packageHash","card","getElementById","changeButton","getElementsByClassName","addEventListener","e","preventDefault","removeAttribute","value","target","form","submit","selectedHash","selectButton"],"mappings":"4MA8BiCA,OAAQC,gBAC/BC,eAAiBC,SAASC,cAAc,qCACxCC,YAAcF,SAASC,cAAc,kCAErCE,KAAOH,SAASI,eAAeP,WAEjCC,SAAU,OAEJO,aAAeF,KAAKG,uBAAuB,gCAAgC,GACjFD,aAAaE,iBAAiB,SAAUC,IACpCA,EAAEC,iBAGFV,eAAeW,gBAAgB,YAC/BR,YAAYS,MAAQ,0CAGAN,cACpBG,EAAEI,OAAOC,KAAKC,gBAEf,OACGC,aAAeZ,KAAKG,uBAAuB,yBAAyB,GACpEU,aAAeb,KAAKG,uBAAuB,gCAAgC,GAEjFU,aAAaT,iBAAiB,SAAUC,IACpCA,EAAEC,iBAGFV,eAAeW,gBAAgB,YAC/BR,YAAYS,MAAQI,aAAaJ,6CAGbK,cACpBR,EAAEI,OAAOC,KAAKC"} \ No newline at end of file +{"version":3,"file":"edit_question.min.js","sources":["../src/edit_question.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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\nimport {resetFormDirtyState} from 'core_form/changechecker';\nimport Notification from 'core/notification';\nimport {favouritePackage} from 'qtype_questionpy/utils';\n\n/**\n * This function is called by the package_selection-template and initializes the action button.\n *\n * When the package is changed, this function enables the hidden form element qpy_package_changed and\n * submits the form. Since qpy_package_changed is registered as a no-submit button, it prevents the form\n * data from being saved to the question, while still re-rendering the form with access to the new selected package\n * hash.\n *\n * @param {HTMLDivElement} card\n * @param {boolean} selected\n */\nexport function initActionButton(card, selected) {\n const packageChanged = document.querySelector('input[name=\"qpy_package_changed\"]');\n const packageHash = document.querySelector('input[name=\"qpy_package_hash\"]');\n\n if (selected) {\n // Initialize the button to change the package.\n const changeButton = card.getElementsByClassName(\"qpy-version-selection-button\")[0];\n changeButton.addEventListener(\"click\", (e) => {\n e.preventDefault();\n\n // Remove package hash.\n packageChanged.removeAttribute(\"disabled\");\n packageHash.value = '';\n\n // We do not want any form checking when changing a package.\n resetFormDirtyState(changeButton);\n e.target.form.submit();\n });\n } else {\n const selectedHash = card.getElementsByClassName(\"qpy-version-selection\")[0];\n const selectButton = card.getElementsByClassName(\"qpy-version-selection-button\")[0];\n\n selectButton.addEventListener(\"click\", (e) => {\n e.preventDefault();\n\n // Set package hash.\n packageChanged.removeAttribute(\"disabled\");\n packageHash.value = selectedHash.value;\n\n // We do not want any form checking when selecting a package.\n resetFormDirtyState(selectButton);\n e.target.form.submit();\n });\n }\n}\n\n/**\n * This function is called by the package_selection-template and initializes the favourite button, when\n * a package is already selected.\n *\n * @param {HTMLDivElement} card\n * @param {number} packageId\n * @param {number} contextId\n */\nexport function initFavouriteButton(card, packageId, contextId) {\n const button = card.querySelector('[data-for=\"favourite-button\"]');\n const isFavouriteAttributeName = \"data-is-favourite\";\n button.addEventListener(\"click\", async() => {\n try {\n const isFavourite = button.hasAttribute(isFavouriteAttributeName);\n const successful = await favouritePackage(packageId, !isFavourite, contextId);\n if (successful) {\n button.toggleAttribute(isFavouriteAttributeName, !isFavourite);\n }\n } catch (exception) {\n await Notification.exception(exception);\n }\n });\n}"],"names":["card","selected","packageChanged","document","querySelector","packageHash","changeButton","getElementsByClassName","addEventListener","e","preventDefault","removeAttribute","value","target","form","submit","selectedHash","selectButton","packageId","contextId","button","async","isFavourite","hasAttribute","toggleAttribute","exception","Notification"],"mappings":"sRAgCiCA,KAAMC,gBAC7BC,eAAiBC,SAASC,cAAc,qCACxCC,YAAcF,SAASC,cAAc,qCAEvCH,SAAU,OAEJK,aAAeN,KAAKO,uBAAuB,gCAAgC,GACjFD,aAAaE,iBAAiB,SAAUC,IACpCA,EAAEC,iBAGFR,eAAeS,gBAAgB,YAC/BN,YAAYO,MAAQ,0CAGAN,cACpBG,EAAEI,OAAOC,KAAKC,gBAEf,OACGC,aAAehB,KAAKO,uBAAuB,yBAAyB,GACpEU,aAAejB,KAAKO,uBAAuB,gCAAgC,GAEjFU,aAAaT,iBAAiB,SAAUC,IACpCA,EAAEC,iBAGFR,eAAeS,gBAAgB,YAC/BN,YAAYO,MAAQI,aAAaJ,6CAGbK,cACpBR,EAAEI,OAAOC,KAAKC,oDAaUf,KAAMkB,UAAWC,iBAC3CC,OAASpB,KAAKI,cAAc,iCAElCgB,OAAOZ,iBAAiB,SAASa,oBAEnBC,YAAcF,OAAOG,aAHF,2BAIA,2BAAiBL,WAAYI,YAAaH,YAE/DC,OAAOI,gBANc,qBAM6BF,aAExD,MAAOG,iBACCC,sBAAaD,UAAUA"} \ No newline at end of file diff --git a/amd/build/package_search/components/package.min.js b/amd/build/package_search/components/package.min.js new file mode 100644 index 00000000..4e3bfc0d --- /dev/null +++ b/amd/build/package_search/components/package.min.js @@ -0,0 +1,3 @@ +define("qtype_questionpy/package_search/components/package",["exports","qtype_questionpy/package_search/component"],(function(_exports,_component){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_component=(obj=_component)&&obj.__esModule?obj:{default:obj};class _default extends _component.default{getWatchers(){return[{watch:"".concat(this.category,"Packages[").concat(this.packageid,"].isfavourite:updated"),handler:this.favouriteChanged}]}create(descriptor){this.packageid=descriptor.packageid,this.category=descriptor.category,this.selectors={FAVOURITE_BUTTON:'[data-for="favourite-button"]'}}isFavourite(){return this.getState()["".concat(this.category,"Packages")].get(this.packageid).isfavourite}stateReady(){this.addEventListener(this.getElement(this.selectors.FAVOURITE_BUTTON),"click",(()=>{this.reactive.dispatch("favourite",this.packageid,!this.isFavourite())}))}async favouriteChanged(){const isFavourite=this.isFavourite();this.getElement(this.selectors.FAVOURITE_BUTTON).toggleAttribute("data-is-favourite",isFavourite)}}return _exports.default=_default,_exports.default})); + +//# sourceMappingURL=package.min.js.map \ No newline at end of file diff --git a/amd/build/package_search/components/package.min.js.map b/amd/build/package_search/components/package.min.js.map new file mode 100644 index 00000000..f52e8d13 --- /dev/null +++ b/amd/build/package_search/components/package.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"package.min.js","sources":["../../../src/package_search/components/package.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/package_search/components/package\n */\n\nimport Component from 'qtype_questionpy/package_search/component';\n\nexport default class extends Component {\n getWatchers() {\n return [\n {watch: `${this.category}Packages[${this.packageid}].isfavourite:updated`, handler: this.favouriteChanged},\n ];\n }\n\n create(descriptor) {\n this.packageid = descriptor.packageid;\n this.category = descriptor.category;\n this.selectors = {\n FAVOURITE_BUTTON: '[data-for=\"favourite-button\"]',\n };\n }\n\n isFavourite() {\n return this.getState()[`${this.category}Packages`].get(this.packageid).isfavourite;\n }\n\n stateReady() {\n this.addEventListener(this.getElement(this.selectors.FAVOURITE_BUTTON), \"click\", () => {\n this.reactive.dispatch(\"favourite\", this.packageid, !this.isFavourite());\n });\n }\n\n async favouriteChanged() {\n const isFavourite = this.isFavourite();\n this.getElement(this.selectors.FAVOURITE_BUTTON).toggleAttribute(\"data-is-favourite\", isFavourite);\n }\n}\n"],"names":["Component","getWatchers","watch","this","category","packageid","handler","favouriteChanged","create","descriptor","selectors","FAVOURITE_BUTTON","isFavourite","getState","get","isfavourite","stateReady","addEventListener","getElement","reactive","dispatch","toggleAttribute"],"mappings":"gUAuB6BA,mBACzBC,oBACW,CACH,CAACC,gBAAUC,KAAKC,6BAAoBD,KAAKE,mCAAkCC,QAASH,KAAKI,mBAIjGC,OAAOC,iBACEJ,UAAYI,WAAWJ,eACvBD,SAAWK,WAAWL,cACtBM,UAAY,CACbC,iBAAkB,iCAI1BC,qBACWT,KAAKU,qBAAcV,KAAKC,sBAAoBU,IAAIX,KAAKE,WAAWU,YAG3EC,kBACSC,iBAAiBd,KAAKe,WAAWf,KAAKO,UAAUC,kBAAmB,SAAS,UACxEQ,SAASC,SAAS,YAAajB,KAAKE,WAAYF,KAAKS,iDAKxDA,YAAcT,KAAKS,mBACpBM,WAAWf,KAAKO,UAAUC,kBAAkBU,gBAAgB,oBAAqBT"} \ No newline at end of file diff --git a/amd/build/package_search/components/pagination.min.js b/amd/build/package_search/components/pagination.min.js index 34f9b0e0..cba9cb10 100644 --- a/amd/build/package_search/components/pagination.min.js +++ b/amd/build/package_search/components/pagination.min.js @@ -1,3 +1,3 @@ -define("qtype_questionpy/package_search/components/pagination",["exports","qtype_questionpy/package_search/component"],(function(_exports,_component){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_component=(obj=_component)&&obj.__esModule?obj:{default:obj};class _default extends _component.default{getWatchers(){return[{watch:"".concat(this.category,".data:updated"),handler:this.updateCurrentPage}]}create(descriptor){this.category=descriptor.category,this.selectors={PREVIOUS_BUTTON:'[data-for="pagination-previous"]',NEXT_BUTTON:'[data-for="pagination-next"]',CURRENT:'[data-for="pagination-current"]'}}stateReady(){this.addEventListener(this.getElement(this.selectors.PREVIOUS_BUTTON),"click",this.previousPage),this.addEventListener(this.getElement(this.selectors.NEXT_BUTTON),"click",this.nextPage)}_getCurrentPage(){return this.getState()[this.category].page}_getLastPage(){const state=this.getState()[this.category];return 0===state.data.total?0:Math.floor((state.data.total-1)/this.reactive.options.limit)}updateCurrentPage(){const currentPage=this._getCurrentPage(),lastPage=this._getLastPage();this.getElement(this.selectors.CURRENT).innerHTML="".concat(currentPage+1," / ").concat(lastPage+1),this.getElement(this.selectors.PREVIOUS_BUTTON).classList.toggle("disabled",0===currentPage),this.getElement(this.selectors.NEXT_BUTTON).classList.toggle("disabled",currentPage===lastPage)}previousPage(){const currentPage=this._getCurrentPage();currentPage>0&&this.reactive.dispatch("changePage",this.category,currentPage-1)}nextPage(){const currentPage=this._getCurrentPage();currentPage0&&this.reactive.dispatch("changePage",this.category,currentPage-1)}nextPage(){const currentPage=this._getCurrentPage();currentPage.\n */\n\n/**\n * @module qtype_questionpy/package_search/components/pagination\n */\n\nimport Component from 'qtype_questionpy/package_search/component';\n\nexport default class extends Component {\n getWatchers() {\n return [\n {watch: `${this.category}.data:updated`, handler: this.updateCurrentPage},\n ];\n }\n\n create(descriptor) {\n this.category = descriptor.category;\n this.selectors = {\n PREVIOUS_BUTTON: `[data-for=\"pagination-previous\"]`,\n NEXT_BUTTON: `[data-for=\"pagination-next\"]`,\n CURRENT: `[data-for=\"pagination-current\"]`,\n };\n }\n\n stateReady() {\n this.addEventListener(this.getElement(this.selectors.PREVIOUS_BUTTON), \"click\", this.previousPage);\n this.addEventListener(this.getElement(this.selectors.NEXT_BUTTON), \"click\", this.nextPage);\n }\n\n /**\n * Returns the current page number starting form zero.\n *\n * @returns {number}\n * @private\n */\n _getCurrentPage() {\n return this.getState()[this.category].page;\n }\n\n /**\n * Returns the last page number starting from zero.\n *\n * @returns {number}\n * @private\n */\n _getLastPage() {\n const state = this.getState()[this.category];\n if (state.data.total === 0) {\n return 0;\n }\n return Math.floor((state.data.total - 1) / this.reactive.options.limit);\n }\n\n /**\n * Updates the current page status and disables or enables the previous and next button.\n */\n updateCurrentPage() {\n const currentPage = this._getCurrentPage();\n const lastPage = this._getLastPage();\n\n // Update page status.\n this.getElement(this.selectors.CURRENT).innerHTML = `${currentPage + 1} / ${lastPage + 1}`;\n\n // Disable or enable buttons.\n this.getElement(this.selectors.PREVIOUS_BUTTON).classList.toggle(\"disabled\", currentPage === 0);\n this.getElement(this.selectors.NEXT_BUTTON).classList.toggle(\"disabled\", currentPage === lastPage);\n }\n\n /**\n * Dispatches mutation that retrieves packages from the previous page.\n */\n previousPage() {\n const currentPage = this._getCurrentPage();\n if (currentPage > 0) {\n this.reactive.dispatch(\"changePage\", this.category, currentPage - 1);\n }\n }\n\n /**\n * Dispatches mutation that retrieves packages from the next page.\n */\n nextPage() {\n const currentPage = this._getCurrentPage();\n const lastPage = this._getLastPage();\n if (currentPage < lastPage) {\n this.reactive.dispatch(\"changePage\", this.category, currentPage + 1);\n }\n }\n\n}\n"],"names":["Component","getWatchers","watch","this","category","handler","updateCurrentPage","create","descriptor","selectors","PREVIOUS_BUTTON","NEXT_BUTTON","CURRENT","stateReady","addEventListener","getElement","previousPage","nextPage","_getCurrentPage","getState","page","_getLastPage","state","data","total","Math","floor","reactive","options","limit","currentPage","lastPage","innerHTML","classList","toggle","dispatch"],"mappings":"mUAuB6BA,mBACzBC,oBACW,CACH,CAACC,gBAAUC,KAAKC,0BAAyBC,QAASF,KAAKG,oBAI/DC,OAAOC,iBACEJ,SAAWI,WAAWJ,cACtBK,UAAY,CACbC,mDACAC,2CACAC,2CAIRC,kBACSC,iBAAiBX,KAAKY,WAAWZ,KAAKM,UAAUC,iBAAkB,QAASP,KAAKa,mBAChFF,iBAAiBX,KAAKY,WAAWZ,KAAKM,UAAUE,aAAc,QAASR,KAAKc,UASrFC,yBACWf,KAAKgB,WAAWhB,KAAKC,UAAUgB,KAS1CC,qBACUC,MAAQnB,KAAKgB,WAAWhB,KAAKC,iBACV,IAArBkB,MAAMC,KAAKC,MACJ,EAEJC,KAAKC,OAAOJ,MAAMC,KAAKC,MAAQ,GAAKrB,KAAKwB,SAASC,QAAQC,OAMrEvB,0BACUwB,YAAc3B,KAAKe,kBACnBa,SAAW5B,KAAKkB,oBAGjBN,WAAWZ,KAAKM,UAAUG,SAASoB,oBAAeF,YAAc,gBAAOC,SAAW,QAGlFhB,WAAWZ,KAAKM,UAAUC,iBAAiBuB,UAAUC,OAAO,WAA4B,IAAhBJ,kBACxEf,WAAWZ,KAAKM,UAAUE,aAAasB,UAAUC,OAAO,WAAYJ,cAAgBC,UAM7Ff,qBACUc,YAAc3B,KAAKe,kBACrBY,YAAc,QACTH,SAASQ,SAAS,aAAchC,KAAKC,SAAU0B,YAAc,GAO1Eb,iBACUa,YAAc3B,KAAKe,kBAErBY,YADa3B,KAAKkB,qBAEbM,SAASQ,SAAS,aAAchC,KAAKC,SAAU0B,YAAc"} \ No newline at end of file +{"version":3,"file":"pagination.min.js","sources":["../../../src/package_search/components/pagination.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/package_search/components/pagination\n */\n\nimport Component from 'qtype_questionpy/package_search/component';\n\nexport default class extends Component {\n getWatchers() {\n return [\n {watch: `${this.category}:updated`, handler: this.updateCurrentPage},\n ];\n }\n\n create(descriptor) {\n this.category = descriptor.category;\n this.selectors = {\n PREVIOUS_BUTTON: `[data-for=\"pagination-previous\"]`,\n NEXT_BUTTON: `[data-for=\"pagination-next\"]`,\n CURRENT: `[data-for=\"pagination-current\"]`,\n };\n }\n\n stateReady() {\n this.addEventListener(this.getElement(this.selectors.PREVIOUS_BUTTON), \"click\", this.previousPage);\n this.addEventListener(this.getElement(this.selectors.NEXT_BUTTON), \"click\", this.nextPage);\n }\n\n /**\n * Returns the current page number starting form zero.\n *\n * @returns {number}\n * @private\n */\n _getCurrentPage() {\n return this.getState()[this.category].page;\n }\n\n /**\n * Returns the last page number starting from zero.\n *\n * @returns {number}\n * @private\n */\n _getLastPage() {\n const state = this.getState()[this.category];\n if (state.total === 0) {\n return 0;\n }\n return Math.floor((state.total - 1) / this.reactive.options.limit);\n }\n\n /**\n * Updates the current page status and disables or enables the previous and next button.\n */\n updateCurrentPage() {\n const currentPage = this._getCurrentPage();\n const lastPage = this._getLastPage();\n\n // Update page status.\n this.getElement(this.selectors.CURRENT).innerHTML = `${currentPage + 1} / ${lastPage + 1}`;\n\n // Disable or enable buttons.\n this.getElement(this.selectors.PREVIOUS_BUTTON).classList.toggle(\"disabled\", currentPage === 0);\n this.getElement(this.selectors.NEXT_BUTTON).classList.toggle(\"disabled\", currentPage === lastPage);\n }\n\n /**\n * Dispatches mutation that retrieves packages from the previous page.\n */\n previousPage() {\n const currentPage = this._getCurrentPage();\n if (currentPage > 0) {\n this.reactive.dispatch(\"changePage\", this.category, currentPage - 1);\n }\n }\n\n /**\n * Dispatches mutation that retrieves packages from the next page.\n */\n nextPage() {\n const currentPage = this._getCurrentPage();\n const lastPage = this._getLastPage();\n if (currentPage < lastPage) {\n this.reactive.dispatch(\"changePage\", this.category, currentPage + 1);\n }\n }\n\n}\n"],"names":["Component","getWatchers","watch","this","category","handler","updateCurrentPage","create","descriptor","selectors","PREVIOUS_BUTTON","NEXT_BUTTON","CURRENT","stateReady","addEventListener","getElement","previousPage","nextPage","_getCurrentPage","getState","page","_getLastPage","state","total","Math","floor","reactive","options","limit","currentPage","lastPage","innerHTML","classList","toggle","dispatch"],"mappings":"mUAuB6BA,mBACzBC,oBACW,CACH,CAACC,gBAAUC,KAAKC,qBAAoBC,QAASF,KAAKG,oBAI1DC,OAAOC,iBACEJ,SAAWI,WAAWJ,cACtBK,UAAY,CACbC,mDACAC,2CACAC,2CAIRC,kBACSC,iBAAiBX,KAAKY,WAAWZ,KAAKM,UAAUC,iBAAkB,QAASP,KAAKa,mBAChFF,iBAAiBX,KAAKY,WAAWZ,KAAKM,UAAUE,aAAc,QAASR,KAAKc,UASrFC,yBACWf,KAAKgB,WAAWhB,KAAKC,UAAUgB,KAS1CC,qBACUC,MAAQnB,KAAKgB,WAAWhB,KAAKC,iBACf,IAAhBkB,MAAMC,MACC,EAEJC,KAAKC,OAAOH,MAAMC,MAAQ,GAAKpB,KAAKuB,SAASC,QAAQC,OAMhEtB,0BACUuB,YAAc1B,KAAKe,kBACnBY,SAAW3B,KAAKkB,oBAGjBN,WAAWZ,KAAKM,UAAUG,SAASmB,oBAAeF,YAAc,gBAAOC,SAAW,QAGlFf,WAAWZ,KAAKM,UAAUC,iBAAiBsB,UAAUC,OAAO,WAA4B,IAAhBJ,kBACxEd,WAAWZ,KAAKM,UAAUE,aAAaqB,UAAUC,OAAO,WAAYJ,cAAgBC,UAM7Fd,qBACUa,YAAc1B,KAAKe,kBACrBW,YAAc,QACTH,SAASQ,SAAS,aAAc/B,KAAKC,SAAUyB,YAAc,GAO1EZ,iBACUY,YAAc1B,KAAKe,kBAErBW,YADa1B,KAAKkB,qBAEbK,SAASQ,SAAS,aAAc/B,KAAKC,SAAUyB,YAAc"} \ No newline at end of file diff --git a/amd/build/package_search/components/tab_content.min.js b/amd/build/package_search/components/tab_content.min.js index 0f0fae4e..e843399a 100644 --- a/amd/build/package_search/components/tab_content.min.js +++ b/amd/build/package_search/components/tab_content.min.js @@ -1,3 +1,3 @@ -define("qtype_questionpy/package_search/components/tab_content",["exports","core/templates","core/notification","qtype_questionpy/package_search/component","qtype_questionpy/package_search/components/pagination","qtype_questionpy/package_search/components/sort"],(function(_exports,templates,_notification,_component,_pagination,_sort){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,templates=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(templates),_notification=_interopRequireDefault(_notification),_component=_interopRequireDefault(_component),_pagination=_interopRequireDefault(_pagination),_sort=_interopRequireDefault(_sort);class _default extends _component.default{getWatchers(){return[{watch:"".concat(this.category,":updated"),handler:this.render}]}async create(descriptor){this.category=descriptor.category,this.selectors={CONTENT:".qpy-tab-content",SORT:'[data-for="sort"]',PAGINATION:'[data-for="pagination"]'};const sortElement=this.getElement(this.selectors.SORT);sortElement&&new _sort.default({element:sortElement,name:"sort_".concat(this.category),reactive:descriptor.reactive}),new _pagination.default({element:this.getElement(this.selectors.PAGINATION),name:"pagiation_".concat(this.category),reactive:descriptor.reactive,category:this.category})}_getPackageTemplatesPromise(contexts){let promises=[];for(const context of contexts){const promise=templates.renderForPromise("qtype_questionpy/package/package_selection",context);promises.push(promise)}return Promise.all(promises)}async render(){try{const state=this.getState()[this.category],packageTemplates=await this._getPackageTemplatesPromise(state.data.packages),element=this.getElement(this.selectors.CONTENT);element.innerHTML="";for(const{html:html,js:js}of packageTemplates)templates.appendNodeContents(element,html,js)}catch(exception){await _notification.default.exception(exception)}}}return _exports.default=_default,_exports.default})); +define("qtype_questionpy/package_search/components/tab_content",["exports","core/templates","core/notification","qtype_questionpy/package_search/component","qtype_questionpy/package_search/components/pagination","qtype_questionpy/package_search/components/sort","qtype_questionpy/package_search/components/package"],(function(_exports,templates,_notification,_component,_pagination,_sort,_package){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,templates=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(templates),_notification=_interopRequireDefault(_notification),_component=_interopRequireDefault(_component),_pagination=_interopRequireDefault(_pagination),_sort=_interopRequireDefault(_sort),_package=_interopRequireDefault(_package);class _default extends _component.default{getWatchers(){return[{watch:"state.".concat(this.category,"Packages:updated"),handler:this.render}]}async create(descriptor){this.category=descriptor.category,this.selectors={CONTENT:".qpy-tab-content",SORT:'[data-for="sort"]',PAGINATION:'[data-for="pagination"]'};const sortElement=this.getElement(this.selectors.SORT);sortElement&&new _sort.default({element:sortElement,name:"sort_".concat(this.category),reactive:descriptor.reactive}),new _pagination.default({element:this.getElement(this.selectors.PAGINATION),name:"pagiation_".concat(this.category),reactive:descriptor.reactive,category:this.category})}_getPackageTemplatesPromise(contexts){let promises=[];for(const context of contexts){const contextObj=Object.assign({},context),promise=templates.renderForPromise("qtype_questionpy/package/package_selection",contextObj);promises.push(promise)}return Promise.all(promises)}async render(){try{const packages=Array.from(this.getState()["".concat(this.category,"Packages")].values()),packageTemplates=await this._getPackageTemplatesPromise(packages),contentElement=this.getElement(this.selectors.CONTENT);contentElement.innerHTML="";let index=0;for(const{html:html,js:js}of packageTemplates){const packageElement=templates.appendNodeContents(contentElement,html,js)[0];new _package.default({element:packageElement,category:this.category,packageid:packages[index++].id})}}catch(exception){await _notification.default.exception(exception)}}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=tab_content.min.js.map \ No newline at end of file diff --git a/amd/build/package_search/components/tab_content.min.js.map b/amd/build/package_search/components/tab_content.min.js.map index 094e8694..e0157e10 100644 --- a/amd/build/package_search/components/tab_content.min.js.map +++ b/amd/build/package_search/components/tab_content.min.js.map @@ -1 +1 @@ -{"version":3,"file":"tab_content.min.js","sources":["../../../src/package_search/components/tab_content.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/package_search/components/tab_content\n */\n\nimport * as templates from 'core/templates';\nimport Notification from 'core/notification';\nimport Component from 'qtype_questionpy/package_search/component';\nimport Pagination from 'qtype_questionpy/package_search/components/pagination';\nimport Sort from 'qtype_questionpy/package_search/components/sort';\n\nexport default class extends Component {\n getWatchers() {\n return [\n {watch: `${this.category}:updated`, handler: this.render},\n ];\n }\n\n async create(descriptor) {\n this.category = descriptor.category;\n this.selectors = {\n CONTENT: \".qpy-tab-content\",\n SORT: '[data-for=\"sort\"]',\n PAGINATION: '[data-for=\"pagination\"]',\n };\n\n // Register sort if available.\n const sortElement = this.getElement(this.selectors.SORT);\n if (sortElement) {\n new Sort({\n element: sortElement,\n name: `sort_${this.category}`,\n reactive: descriptor.reactive,\n });\n }\n\n // Register pagination.\n new Pagination({\n element: this.getElement(this.selectors.PAGINATION),\n name: `pagiation_${this.category}`,\n reactive: descriptor.reactive,\n category: this.category,\n });\n }\n\n /**\n * Groups render promises for package templates.\n *\n * @param {Object[]} contexts\n * @returns {Promise}\n * @private\n */\n _getPackageTemplatesPromise(contexts) {\n let promises = [];\n for (const context of contexts) {\n const promise = templates.renderForPromise(\"qtype_questionpy/package/package_selection\", context);\n promises.push(promise);\n }\n return Promise.all(promises);\n }\n\n /**\n * Renders every package inside the current state.\n */\n async render() {\n try {\n const state = this.getState()[this.category];\n const packageTemplates = await this._getPackageTemplatesPromise(state.data.packages);\n const element = this.getElement(this.selectors.CONTENT);\n element.innerHTML = \"\";\n for (const {html, js} of packageTemplates) {\n templates.appendNodeContents(element, html, js);\n }\n } catch (exception) {\n await Notification.exception(exception);\n }\n }\n}\n"],"names":["Component","getWatchers","watch","this","category","handler","render","descriptor","selectors","CONTENT","SORT","PAGINATION","sortElement","getElement","Sort","element","name","reactive","Pagination","_getPackageTemplatesPromise","contexts","promises","context","promise","templates","renderForPromise","push","Promise","all","state","getState","packageTemplates","data","packages","innerHTML","html","js","appendNodeContents","exception","Notification"],"mappings":"onDA2B6BA,mBACzBC,oBACW,CACH,CAACC,gBAAUC,KAAKC,qBAAoBC,QAASF,KAAKG,sBAI7CC,iBACJH,SAAWG,WAAWH,cACtBI,UAAY,CACbC,QAAS,mBACTC,KAAM,oBACNC,WAAY,iCAIVC,YAAcT,KAAKU,WAAWV,KAAKK,UAAUE,MAC/CE,iBACIE,cAAK,CACLC,QAASH,YACTI,oBAAcb,KAAKC,UACnBa,SAAUV,WAAWU,eAKzBC,oBAAW,CACXH,QAASZ,KAAKU,WAAWV,KAAKK,UAAUG,YACxCK,yBAAmBb,KAAKC,UACxBa,SAAUV,WAAWU,SACrBb,SAAUD,KAAKC,WAWvBe,4BAA4BC,cACpBC,SAAW,OACV,MAAMC,WAAWF,SAAU,OACtBG,QAAUC,UAAUC,iBAAiB,6CAA8CH,SACzFD,SAASK,KAAKH,gBAEXI,QAAQC,IAAIP,mCAQTQ,MAAQ1B,KAAK2B,WAAW3B,KAAKC,UAC7B2B,uBAAyB5B,KAAKgB,4BAA4BU,MAAMG,KAAKC,UACrElB,QAAUZ,KAAKU,WAAWV,KAAKK,UAAUC,SAC/CM,QAAQmB,UAAY,OACf,MAAMC,KAACA,KAADC,GAAOA,MAAOL,iBACrBP,UAAUa,mBAAmBtB,QAASoB,KAAMC,IAElD,MAAOE,iBACCC,sBAAaD,UAAUA"} \ No newline at end of file +{"version":3,"file":"tab_content.min.js","sources":["../../../src/package_search/components/tab_content.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/package_search/components/tab_content\n */\n\nimport * as templates from 'core/templates';\nimport Notification from 'core/notification';\nimport Component from 'qtype_questionpy/package_search/component';\nimport Pagination from 'qtype_questionpy/package_search/components/pagination';\nimport Sort from 'qtype_questionpy/package_search/components/sort';\nimport Package from 'qtype_questionpy/package_search/components/package';\n\nexport default class extends Component {\n getWatchers() {\n return [\n {watch: `state.${this.category}Packages:updated`, handler: this.render},\n ];\n }\n\n async create(descriptor) {\n this.category = descriptor.category;\n this.selectors = {\n CONTENT: \".qpy-tab-content\",\n SORT: '[data-for=\"sort\"]',\n PAGINATION: '[data-for=\"pagination\"]',\n };\n\n // Register sort if available.\n const sortElement = this.getElement(this.selectors.SORT);\n if (sortElement) {\n new Sort({\n element: sortElement,\n name: `sort_${this.category}`,\n reactive: descriptor.reactive,\n });\n }\n\n // Register pagination.\n new Pagination({\n element: this.getElement(this.selectors.PAGINATION),\n name: `pagiation_${this.category}`,\n reactive: descriptor.reactive,\n category: this.category,\n });\n }\n\n /**\n * Groups render promises for package templates.\n *\n * @param {Object[]} contexts\n * @returns {Promise}\n * @private\n */\n _getPackageTemplatesPromise(contexts) {\n let promises = [];\n for (const context of contexts) {\n // Context is a proxy, we need to get the target.\n const contextObj = Object.assign({}, context);\n const promise = templates.renderForPromise(\"qtype_questionpy/package/package_selection\", contextObj);\n promises.push(promise);\n }\n return Promise.all(promises);\n }\n\n /**\n * Renders every package inside the current state.\n */\n async render() {\n try {\n const packages = Array.from(this.getState()[`${this.category}Packages`].values());\n const packageTemplates = await this._getPackageTemplatesPromise(packages);\n const contentElement = this.getElement(this.selectors.CONTENT);\n contentElement.innerHTML = \"\";\n let index = 0;\n for (const {html, js} of packageTemplates) {\n const packageElement = templates.appendNodeContents(contentElement, html, js)[0];\n new Package({\n element: packageElement,\n category: this.category,\n packageid: packages[index++].id,\n });\n }\n } catch (exception) {\n await Notification.exception(exception);\n }\n }\n}\n"],"names":["Component","getWatchers","watch","this","category","handler","render","descriptor","selectors","CONTENT","SORT","PAGINATION","sortElement","getElement","Sort","element","name","reactive","Pagination","_getPackageTemplatesPromise","contexts","promises","context","contextObj","Object","assign","promise","templates","renderForPromise","push","Promise","all","packages","Array","from","getState","values","packageTemplates","contentElement","innerHTML","index","html","js","packageElement","appendNodeContents","Package","packageid","id","exception","Notification"],"mappings":"4tDA4B6BA,mBACzBC,oBACW,CACH,CAACC,sBAAgBC,KAAKC,6BAA4BC,QAASF,KAAKG,sBAI3DC,iBACJH,SAAWG,WAAWH,cACtBI,UAAY,CACbC,QAAS,mBACTC,KAAM,oBACNC,WAAY,iCAIVC,YAAcT,KAAKU,WAAWV,KAAKK,UAAUE,MAC/CE,iBACIE,cAAK,CACLC,QAASH,YACTI,oBAAcb,KAAKC,UACnBa,SAAUV,WAAWU,eAKzBC,oBAAW,CACXH,QAASZ,KAAKU,WAAWV,KAAKK,UAAUG,YACxCK,yBAAmBb,KAAKC,UACxBa,SAAUV,WAAWU,SACrBb,SAAUD,KAAKC,WAWvBe,4BAA4BC,cACpBC,SAAW,OACV,MAAMC,WAAWF,SAAU,OAEtBG,WAAaC,OAAOC,OAAO,GAAIH,SAC/BI,QAAUC,UAAUC,iBAAiB,6CAA8CL,YACzFF,SAASQ,KAAKH,gBAEXI,QAAQC,IAAIV,mCAQTW,SAAWC,MAAMC,KAAK/B,KAAKgC,qBAAchC,KAAKC,sBAAoBgC,UAClEC,uBAAyBlC,KAAKgB,4BAA4Ba,UAC1DM,eAAiBnC,KAAKU,WAAWV,KAAKK,UAAUC,SACtD6B,eAAeC,UAAY,OACvBC,MAAQ,MACP,MAAMC,KAACA,KAADC,GAAOA,MAAOL,iBAAkB,OACjCM,eAAiBhB,UAAUiB,mBAAmBN,eAAgBG,KAAMC,IAAI,OAC1EG,iBAAQ,CACR9B,QAAS4B,eACTvC,SAAUD,KAAKC,SACf0C,UAAWd,SAASQ,SAASO,MAGvC,MAAOC,iBACCC,sBAAaD,UAAUA"} \ No newline at end of file diff --git a/amd/build/package_search/components/tab_header.min.js b/amd/build/package_search/components/tab_header.min.js index 2142162a..6a218958 100644 --- a/amd/build/package_search/components/tab_header.min.js +++ b/amd/build/package_search/components/tab_header.min.js @@ -1,3 +1,3 @@ -define("qtype_questionpy/package_search/components/tab_header",["exports","core/str","qtype_questionpy/package_search/component"],(function(_exports,strings,_component){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,strings=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(strings),_component=(obj=_component)&&obj.__esModule?obj:{default:obj};class _default extends _component.default{getWatchers(){return[{watch:"".concat(this.category,":updated"),handler:this.render}]}async create(descriptor){this.category=descriptor.category}async render(){const data=this.getState()[this.category].data;this.element.innerHTML=await strings.get_string("search_".concat(this.category,"_header"),"qtype_questionpy",data.total)}}return _exports.default=_default,_exports.default})); +define("qtype_questionpy/package_search/components/tab_header",["exports","core/str","qtype_questionpy/package_search/component"],(function(_exports,strings,_component){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,strings=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(strings),_component=(obj=_component)&&obj.__esModule?obj:{default:obj};class _default extends _component.default{getWatchers(){return[{watch:"".concat(this.category,".total:updated"),handler:this.render}]}async create(descriptor){this.category=descriptor.category}async render(){const data=this.getState()[this.category];this.element.innerHTML=await strings.get_string("search_".concat(this.category,"_header"),"qtype_questionpy",data.total)}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=tab_header.min.js.map \ No newline at end of file diff --git a/amd/build/package_search/components/tab_header.min.js.map b/amd/build/package_search/components/tab_header.min.js.map index 00b38fed..abbe0312 100644 --- a/amd/build/package_search/components/tab_header.min.js.map +++ b/amd/build/package_search/components/tab_header.min.js.map @@ -1 +1 @@ -{"version":3,"file":"tab_header.min.js","sources":["../../../src/package_search/components/tab_header.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/package_search/components/tab_header\n */\n\nimport * as strings from 'core/str';\nimport Component from 'qtype_questionpy/package_search/component';\n\nexport default class extends Component {\n getWatchers() {\n return [\n {watch: `${this.category}:updated`, handler: this.render},\n ];\n }\n\n async create(descriptor) {\n this.category = descriptor.category;\n }\n\n /**\n * Renders every package inside a specific tab.\n */\n async render() {\n const data = this.getState()[this.category].data;\n this.element.innerHTML = await strings.get_string(`search_${this.category}_header`, \"qtype_questionpy\", data.total);\n }\n\n}\n"],"names":["Component","getWatchers","watch","this","category","handler","render","descriptor","data","getState","element","innerHTML","strings","get_string","total"],"mappings":"uwCAwB6BA,mBACzBC,oBACW,CACH,CAACC,gBAAUC,KAAKC,qBAAoBC,QAASF,KAAKG,sBAI7CC,iBACJH,SAAWG,WAAWH,8BAOrBI,KAAOL,KAAKM,WAAWN,KAAKC,UAAUI,UACvCE,QAAQC,gBAAkBC,QAAQC,4BAAqBV,KAAKC,oBAAmB,mBAAoBI,KAAKM"} \ No newline at end of file +{"version":3,"file":"tab_header.min.js","sources":["../../../src/package_search/components/tab_header.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/package_search/components/tab_header\n */\n\nimport * as strings from 'core/str';\nimport Component from 'qtype_questionpy/package_search/component';\n\nexport default class extends Component {\n getWatchers() {\n return [\n {watch: `${this.category}.total:updated`, handler: this.render},\n ];\n }\n\n async create(descriptor) {\n this.category = descriptor.category;\n }\n\n /**\n * Renders every package inside a specific tab.\n */\n async render() {\n const data = this.getState()[this.category];\n this.element.innerHTML = await strings.get_string(`search_${this.category}_header`, \"qtype_questionpy\", data.total);\n }\n\n}\n"],"names":["Component","getWatchers","watch","this","category","handler","render","descriptor","data","getState","element","innerHTML","strings","get_string","total"],"mappings":"uwCAwB6BA,mBACzBC,oBACW,CACH,CAACC,gBAAUC,KAAKC,2BAA0BC,QAASF,KAAKG,sBAInDC,iBACJH,SAAWG,WAAWH,8BAOrBI,KAAOL,KAAKM,WAAWN,KAAKC,eAC7BM,QAAQC,gBAAkBC,QAAQC,4BAAqBV,KAAKC,oBAAmB,mBAAoBI,KAAKM"} \ No newline at end of file diff --git a/amd/build/package_search/mutations.min.js b/amd/build/package_search/mutations.min.js index f8325580..66c4408e 100644 --- a/amd/build/package_search/mutations.min.js +++ b/amd/build/package_search/mutations.min.js @@ -1,3 +1,3 @@ -define("qtype_questionpy/package_search/mutations",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);return _exports.default=class{constructor(options){this.options=options}_getSearchPackagesInCategoriesPromise(state,page,categories){const methods=[];for(const category of categories){const method={methodname:"qtype_questionpy_search_packages",args:{query:state.general.query,tags:state.general.tags,category:category,sort:state.general.sorting.sort,order:state.general.sorting.order,limit:this.options.limit,page:"number"==typeof page?page:state[category].page,contextid:this.options.contextid}};methods.push(method)}return _ajax.default.call(methods)}async searchPackages(stateManager){let args=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,categories=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const state=stateManager.state;args=args||{},categories=categories||["all","recentlyused","favourites","mine"],stateManager.setReadOnly(!1),state.general.loading=!0,state.general.query="string"==typeof args.query?args.query:state.general.query,state.general.tags=[],state.general.sorting={sort:args.sort||state.general.sorting.sort,order:args.order||state.general.sorting.order},stateManager.setReadOnly(!0);try{let results=await this._getSearchPackagesInCategoriesPromise(state,args.page,categories);stateManager.setReadOnly(!1);for(const[index,category]of categories.entries())state[category].data=await results[index],"number"==typeof args.page&&(state[category].page=args.page);state.general.loading=!1,stateManager.setReadOnly(!0)}catch(exception){await _notification.default.exception(exception)}}async searchPackagesByQuery(stateManager,query){await this.searchPackages(stateManager,{page:0,query:query})}async changePage(stateManager,category,page){await this.searchPackages(stateManager,{page:page},[category])}async changeSort(stateManager,sort,order){await this.searchPackages(stateManager,{sort:sort,order:order},["all","favourites","mine"])}},_exports.default})); +define("qtype_questionpy/package_search/mutations",["exports","core/ajax","core/notification","../utils"],(function(_exports,_ajax,_notification,_utils){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);return _exports.default=class{constructor(options){this.options=options}_getSearchPackagesInCategoriesPromise(state,page,categories){const methods=[];for(const category of categories){const method={methodname:"qtype_questionpy_search_packages",args:{query:state.general.query,tags:state.general.tags,category:category,sort:state.general.sorting.sort,order:state.general.sorting.order,limit:this.options.limit,page:"number"==typeof page?page:state[category].page,contextid:this.options.contextid}};methods.push(method)}return _ajax.default.call(methods)}async searchPackages(stateManager){let args=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,categories=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;const state=stateManager.state;args=args||{},categories=categories||["all","recentlyused","favourites","mine"],stateManager.setReadOnly(!1),state.general.loading=!0,state.general.query="string"==typeof args.query?args.query:state.general.query,state.general.tags=[],state.general.sorting={sort:args.sort||state.general.sorting.sort,order:args.order||state.general.sorting.order},stateManager.setReadOnly(!0);try{let results=await this._getSearchPackagesInCategoriesPromise(state,args.page,categories);stateManager.setReadOnly(!1);for(const[index,category]of categories.entries()){const result=await results[index];state["".concat(category,"Packages")]=result.packages,state[category].count=result.count,state[category].total=result.total,"number"==typeof args.page&&(state[category].page=args.page)}state.general.loading=!1,stateManager.setReadOnly(!0)}catch(exception){await _notification.default.exception(exception)}}async searchPackagesByQuery(stateManager,query){await this.searchPackages(stateManager,{page:0,query:query})}async changePage(stateManager,category,page){await this.searchPackages(stateManager,{page:page},[category])}async changeSort(stateManager,sort,order){await this.searchPackages(stateManager,{sort:sort,order:order},["all","favourites","mine"])}async load(stateManager,categories){await this.searchPackages(stateManager,{},categories)}async favourite(stateManager,packageid,favourite){stateManager.setReadOnly(!1);const state=stateManager.state;try{state.general.loading=!0;if(!await(0,_utils.favouritePackage)(packageid,favourite,this.options.contextid))return;for(const category of["all","mine","recentlyused"]){const pkg=state["".concat(category,"Packages")].get(packageid);pkg&&(pkg.isfavourite=favourite)}stateManager.setReadOnly(!0);let page=state.favourites.page;if(!favourite){const isFirstPage=0===page,isLastPage=page===Math.floor((state.favourites.total-1)/this.options.limit),existsOnePackage=1===state.favourites.count;!isFirstPage&&isLastPage&&existsOnePackage&&(page-=1)}await this.changePage(stateManager,"favourites",page)}catch(exception){await _notification.default.exception(exception)}}},_exports.default})); //# sourceMappingURL=mutations.min.js.map \ No newline at end of file diff --git a/amd/build/package_search/mutations.min.js.map b/amd/build/package_search/mutations.min.js.map index 6f11a6f3..5fd92eb5 100644 --- a/amd/build/package_search/mutations.min.js.map +++ b/amd/build/package_search/mutations.min.js.map @@ -1 +1 @@ -{"version":3,"file":"mutations.min.js","sources":["../../src/package_search/mutations.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/package_search/mutations\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\nexport default class {\n /**\n * @param {{contextid: number, limit: number}} options\n */\n constructor(options) {\n this.options = options;\n }\n\n /**\n * Search through given categories.\n *\n * If no page is provided the current page will be used.\n *\n * @param {any} state\n * @param {number|null} page\n * @param {string[]} categories\n * @returns {any}\n * @private\n */\n _getSearchPackagesInCategoriesPromise(state, page, categories) {\n const methods = [];\n for (const category of categories) {\n const method = {\n methodname: \"qtype_questionpy_search_packages\",\n args: {\n query: state.general.query,\n tags: state.general.tags,\n category: category,\n sort: state.general.sorting.sort,\n order: state.general.sorting.order,\n limit: this.options.limit,\n page: (typeof page === \"number\") ? page : state[category].page,\n contextid: this.options.contextid,\n },\n };\n methods.push(method);\n }\n return Ajax.call(methods);\n }\n\n /**\n * Used to search packages.\n *\n * Missing arguments are taken from the current state.\n *\n * @param {StateManager} stateManager\n * @param {Object|null} args\n * @param {string[]|null} categories\n */\n async searchPackages(stateManager, args = null, categories = null) {\n const state = stateManager.state;\n\n // Missing arguments are taken from the current state.\n args = args || {};\n\n // Search through every category if no categories are provided.\n categories = categories || [\"all\", \"recentlyused\", \"favourites\", \"mine\"];\n\n // Update general data.\n stateManager.setReadOnly(false);\n state.general.loading = true;\n state.general.query = (typeof args.query === \"string\") ? args.query : state.general.query;\n state.general.tags = [];\n state.general.sorting = {\n sort: args.sort || state.general.sorting.sort,\n order: args.order || state.general.sorting.order,\n };\n stateManager.setReadOnly(true);\n\n try {\n // Get search results for each category.\n let results = await this._getSearchPackagesInCategoriesPromise(state, args.page, categories);\n\n stateManager.setReadOnly(false);\n // Update category specific data.\n for (const [index, category] of categories.entries()) {\n state[category].data = await results[index];\n if (typeof args.page === \"number\") {\n state[category].page = args.page;\n }\n }\n // Update loading status.\n state.general.loading = false;\n stateManager.setReadOnly(true);\n } catch (exception) {\n await Notification.exception(exception);\n }\n }\n\n /**\n * Used to search for packages only by providing a query.\n *\n * @param {StateManager} stateManager\n * @param {string} query\n */\n async searchPackagesByQuery(stateManager, query) {\n await this.searchPackages(stateManager, {page: 0, query: query});\n }\n\n /**\n * Used to change the current page of a tab.\n *\n * @param {StateManager} stateManager\n * @param {string} category\n * @param {number} page\n */\n async changePage(stateManager, category, page) {\n await this.searchPackages(stateManager, {page: page}, [category]);\n }\n\n /**\n * Used to change the current sorting.\n *\n * @param {StateManager} stateManager\n * @param {string} sort\n * @param {string} order\n */\n async changeSort(stateManager, sort, order) {\n await this.searchPackages(stateManager, {sort: sort, order: order}, [\"all\", \"favourites\", \"mine\"]);\n }\n}\n"],"names":["constructor","options","_getSearchPackagesInCategoriesPromise","state","page","categories","methods","category","method","methodname","args","query","general","tags","sort","sorting","order","limit","this","contextid","push","Ajax","call","stateManager","setReadOnly","loading","results","index","entries","data","exception","Notification","searchPackages"],"mappings":"+ZA4BIA,YAAYC,cACHA,QAAUA,QAcnBC,sCAAsCC,MAAOC,KAAMC,kBACzCC,QAAU,OACX,MAAMC,YAAYF,WAAY,OACzBG,OAAS,CACXC,WAAY,mCACZC,KAAM,CACFC,MAAOR,MAAMS,QAAQD,MACrBE,KAAMV,MAAMS,QAAQC,KACpBN,SAAUA,SACVO,KAAMX,MAAMS,QAAQG,QAAQD,KAC5BE,MAAOb,MAAMS,QAAQG,QAAQC,MAC7BC,MAAOC,KAAKjB,QAAQgB,MACpBb,KAAuB,iBAATA,KAAqBA,KAAOD,MAAMI,UAAUH,KAC1De,UAAWD,KAAKjB,QAAQkB,YAGhCb,QAAQc,KAAKZ,eAEVa,cAAKC,KAAKhB,8BAYAiB,kBAAcb,4DAAO,KAAML,kEAAa,WACnDF,MAAQoB,aAAapB,MAG3BO,KAAOA,MAAQ,GAGfL,WAAaA,YAAc,CAAC,MAAO,eAAgB,aAAc,QAGjEkB,aAAaC,aAAY,GACzBrB,MAAMS,QAAQa,SAAU,EACxBtB,MAAMS,QAAQD,MAA+B,iBAAfD,KAAKC,MAAsBD,KAAKC,MAAQR,MAAMS,QAAQD,MACpFR,MAAMS,QAAQC,KAAO,GACrBV,MAAMS,QAAQG,QAAU,CACpBD,KAAMJ,KAAKI,MAAQX,MAAMS,QAAQG,QAAQD,KACzCE,MAAON,KAAKM,OAASb,MAAMS,QAAQG,QAAQC,OAE/CO,aAAaC,aAAY,WAIjBE,cAAgBR,KAAKhB,sCAAsCC,MAAOO,KAAKN,KAAMC,YAEjFkB,aAAaC,aAAY,OAEpB,MAAOG,MAAOpB,YAAaF,WAAWuB,UACvCzB,MAAMI,UAAUsB,WAAaH,QAAQC,OACZ,iBAAdjB,KAAKN,OACZD,MAAMI,UAAUH,KAAOM,KAAKN,MAIpCD,MAAMS,QAAQa,SAAU,EACxBF,aAAaC,aAAY,GAC3B,MAAOM,iBACCC,sBAAaD,UAAUA,wCAUTP,aAAcZ,aAChCO,KAAKc,eAAeT,aAAc,CAACnB,KAAM,EAAGO,MAAOA,yBAU5CY,aAAchB,SAAUH,YAC/Bc,KAAKc,eAAeT,aAAc,CAACnB,KAAMA,MAAO,CAACG,4BAU1CgB,aAAcT,KAAME,aAC3BE,KAAKc,eAAeT,aAAc,CAACT,KAAMA,KAAME,MAAOA,OAAQ,CAAC,MAAO,aAAc"} \ No newline at end of file +{"version":3,"file":"mutations.min.js","sources":["../../src/package_search/mutations.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/package_search/mutations\n */\n\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\nimport {favouritePackage} from \"../utils\";\n\nexport default class {\n /**\n * @param {{contextid: number, limit: number}} options\n */\n constructor(options) {\n this.options = options;\n }\n\n /**\n * Search through given categories.\n *\n * If no page is provided the current page will be used.\n *\n * @param {any} state\n * @param {number|null} page\n * @param {string[]} categories\n * @returns {any}\n * @private\n */\n _getSearchPackagesInCategoriesPromise(state, page, categories) {\n const methods = [];\n for (const category of categories) {\n const method = {\n methodname: \"qtype_questionpy_search_packages\",\n args: {\n query: state.general.query,\n tags: state.general.tags,\n category: category,\n sort: state.general.sorting.sort,\n order: state.general.sorting.order,\n limit: this.options.limit,\n page: (typeof page === \"number\") ? page : state[category].page,\n contextid: this.options.contextid,\n },\n };\n methods.push(method);\n }\n return Ajax.call(methods);\n }\n\n /**\n * Used to search packages.\n *\n * Missing arguments are taken from the current state.\n *\n * @param {StateManager} stateManager\n * @param {Object|null} args\n * @param {string[]|null} categories\n */\n async searchPackages(stateManager, args = null, categories = null) {\n const state = stateManager.state;\n\n // Missing arguments are taken from the current state.\n args = args || {};\n\n // Search through every category if no categories are provided.\n categories = categories || [\"all\", \"recentlyused\", \"favourites\", \"mine\"];\n\n // Update general data.\n stateManager.setReadOnly(false);\n state.general.loading = true;\n state.general.query = (typeof args.query === \"string\") ? args.query : state.general.query;\n state.general.tags = [];\n state.general.sorting = {\n sort: args.sort || state.general.sorting.sort,\n order: args.order || state.general.sorting.order,\n };\n stateManager.setReadOnly(true);\n\n try {\n // Get search results for each category.\n let results = await this._getSearchPackagesInCategoriesPromise(state, args.page, categories);\n\n stateManager.setReadOnly(false);\n // Update category specific data.\n for (const [index, category] of categories.entries()) {\n const result = await results[index];\n state[`${category}Packages`] = result.packages;\n state[category].count = result.count;\n state[category].total = result.total;\n if (typeof args.page === \"number\") {\n state[category].page = args.page;\n }\n }\n // Update loading status.\n state.general.loading = false;\n stateManager.setReadOnly(true);\n } catch (exception) {\n await Notification.exception(exception);\n }\n }\n\n /**\n * Used to search for packages only by providing a query.\n *\n * @param {StateManager} stateManager\n * @param {string} query\n */\n async searchPackagesByQuery(stateManager, query) {\n await this.searchPackages(stateManager, {page: 0, query: query});\n }\n\n /**\n * Used to change the current page of a tab.\n *\n * @param {StateManager} stateManager\n * @param {string} category\n * @param {number} page\n */\n async changePage(stateManager, category, page) {\n await this.searchPackages(stateManager, {page: page}, [category]);\n }\n\n /**\n * Used to change the current sorting.\n *\n * @param {StateManager} stateManager\n * @param {string} sort\n * @param {string} order\n */\n async changeSort(stateManager, sort, order) {\n await this.searchPackages(stateManager, {sort: sort, order: order}, [\"all\", \"favourites\", \"mine\"]);\n }\n\n /**\n * Used to re-/load data of given categories.\n *\n * @param {StateManager} stateManager\n * @param {string[]} categories\n */\n async load(stateManager, categories) {\n await this.searchPackages(stateManager, {}, categories);\n }\n\n /**\n * Used to un-/favourite a package.\n *\n * @param {StateManager} stateManager\n * @param {int} packageid\n * @param {boolean} favourite\n */\n async favourite(stateManager, packageid, favourite) {\n stateManager.setReadOnly(false);\n const state = stateManager.state;\n try {\n state.general.loading = true;\n const successful = await favouritePackage(packageid, favourite, this.options.contextid);\n if (!successful) {\n return;\n }\n for (const category of [\"all\", \"mine\", \"recentlyused\"]) {\n const pkg = state[`${category}Packages`].get(packageid);\n if (pkg) {\n pkg.isfavourite = favourite;\n }\n }\n stateManager.setReadOnly(true);\n let page = state.favourites.page;\n if (!favourite) {\n // Turn back a page in 'favourites' if the unmarked package was the last one on the page.\n const isFirstPage = page === 0;\n const isLastPage = page === Math.floor((state.favourites.total - 1) / this.options.limit);\n const existsOnePackage = state.favourites.count === 1;\n if (!isFirstPage && isLastPage && existsOnePackage) {\n page -= 1;\n }\n }\n await this.changePage(stateManager, 'favourites', page);\n } catch (exception) {\n await Notification.exception(exception);\n }\n }\n}\n"],"names":["constructor","options","_getSearchPackagesInCategoriesPromise","state","page","categories","methods","category","method","methodname","args","query","general","tags","sort","sorting","order","limit","this","contextid","push","Ajax","call","stateManager","setReadOnly","loading","results","index","entries","result","packages","count","total","exception","Notification","searchPackages","packageid","favourite","pkg","get","isfavourite","favourites","isFirstPage","isLastPage","Math","floor","existsOnePackage","changePage"],"mappings":"ibA6BIA,YAAYC,cACHA,QAAUA,QAcnBC,sCAAsCC,MAAOC,KAAMC,kBACzCC,QAAU,OACX,MAAMC,YAAYF,WAAY,OACzBG,OAAS,CACXC,WAAY,mCACZC,KAAM,CACFC,MAAOR,MAAMS,QAAQD,MACrBE,KAAMV,MAAMS,QAAQC,KACpBN,SAAUA,SACVO,KAAMX,MAAMS,QAAQG,QAAQD,KAC5BE,MAAOb,MAAMS,QAAQG,QAAQC,MAC7BC,MAAOC,KAAKjB,QAAQgB,MACpBb,KAAuB,iBAATA,KAAqBA,KAAOD,MAAMI,UAAUH,KAC1De,UAAWD,KAAKjB,QAAQkB,YAGhCb,QAAQc,KAAKZ,eAEVa,cAAKC,KAAKhB,8BAYAiB,kBAAcb,4DAAO,KAAML,kEAAa,WACnDF,MAAQoB,aAAapB,MAG3BO,KAAOA,MAAQ,GAGfL,WAAaA,YAAc,CAAC,MAAO,eAAgB,aAAc,QAGjEkB,aAAaC,aAAY,GACzBrB,MAAMS,QAAQa,SAAU,EACxBtB,MAAMS,QAAQD,MAA+B,iBAAfD,KAAKC,MAAsBD,KAAKC,MAAQR,MAAMS,QAAQD,MACpFR,MAAMS,QAAQC,KAAO,GACrBV,MAAMS,QAAQG,QAAU,CACpBD,KAAMJ,KAAKI,MAAQX,MAAMS,QAAQG,QAAQD,KACzCE,MAAON,KAAKM,OAASb,MAAMS,QAAQG,QAAQC,OAE/CO,aAAaC,aAAY,WAIjBE,cAAgBR,KAAKhB,sCAAsCC,MAAOO,KAAKN,KAAMC,YAEjFkB,aAAaC,aAAY,OAEpB,MAAOG,MAAOpB,YAAaF,WAAWuB,UAAW,OAC5CC,aAAeH,QAAQC,OAC7BxB,gBAASI,sBAAsBsB,OAAOC,SACtC3B,MAAMI,UAAUwB,MAAQF,OAAOE,MAC/B5B,MAAMI,UAAUyB,MAAQH,OAAOG,MACN,iBAAdtB,KAAKN,OACZD,MAAMI,UAAUH,KAAOM,KAAKN,MAIpCD,MAAMS,QAAQa,SAAU,EACxBF,aAAaC,aAAY,GAC3B,MAAOS,iBACCC,sBAAaD,UAAUA,wCAUTV,aAAcZ,aAChCO,KAAKiB,eAAeZ,aAAc,CAACnB,KAAM,EAAGO,MAAOA,yBAU5CY,aAAchB,SAAUH,YAC/Bc,KAAKiB,eAAeZ,aAAc,CAACnB,KAAMA,MAAO,CAACG,4BAU1CgB,aAAcT,KAAME,aAC3BE,KAAKiB,eAAeZ,aAAc,CAACT,KAAMA,KAAME,MAAOA,OAAQ,CAAC,MAAO,aAAc,oBASnFO,aAAclB,kBACfa,KAAKiB,eAAeZ,aAAc,GAAIlB,4BAUhCkB,aAAca,UAAWC,WACrCd,aAAaC,aAAY,SACnBrB,MAAQoB,aAAapB,UAEvBA,MAAMS,QAAQa,SAAU,YACC,2BAAiBW,UAAWC,UAAWnB,KAAKjB,QAAQkB,sBAIxE,MAAMZ,WAAY,CAAC,MAAO,OAAQ,gBAAiB,OAC9C+B,IAAMnC,gBAASI,sBAAoBgC,IAAIH,WACzCE,MACAA,IAAIE,YAAcH,WAG1Bd,aAAaC,aAAY,OACrBpB,KAAOD,MAAMsC,WAAWrC,SACvBiC,UAAW,OAENK,YAAuB,IAATtC,KACduC,WAAavC,OAASwC,KAAKC,OAAO1C,MAAMsC,WAAWT,MAAQ,GAAKd,KAAKjB,QAAQgB,OAC7E6B,iBAA8C,IAA3B3C,MAAMsC,WAAWV,OACrCW,aAAeC,YAAcG,mBAC9B1C,MAAQ,SAGVc,KAAK6B,WAAWxB,aAAc,aAAcnB,MACpD,MAAO6B,iBACCC,sBAAaD,UAAUA"} \ No newline at end of file diff --git a/amd/build/package_search/reactive.min.js b/amd/build/package_search/reactive.min.js index 681ed87a..408d0e66 100644 --- a/amd/build/package_search/reactive.min.js +++ b/amd/build/package_search/reactive.min.js @@ -1,3 +1,3 @@ -define("qtype_questionpy/package_search/reactive",["exports","core/reactive","qtype_questionpy/package_search/mutations","qtype_questionpy/package_search/events","qtype_questionpy/package_search/components/area"],(function(_exports,_reactive,_mutations,_events,_area){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_mutations=_interopRequireDefault(_mutations),_area=_interopRequireDefault(_area);let counter=0;class _default extends _reactive.Reactive{constructor(target,options){super({name:"PackageSearch".concat(counter++),eventName:_events.eventNames.stateChanged,eventDispatch:_events.notifyStateChanged,target:target,mutations:new _mutations.default(options),state:{general:{loading:!0,sorting:{sort:"alpha",order:"asc"},query:""},all:{data:{packages:[],count:0,total:0},page:0},recentlyused:{data:{packages:[],count:0,total:0},page:0},favourites:{data:{packages:[],count:0,total:0},page:0},mine:{data:{packages:[],count:0,total:0},page:0}}}),this.options=options}load(){new _area.default({element:this.target,name:"search_area",reactive:this})}}return _exports.default=_default,_exports.default})); +define("qtype_questionpy/package_search/reactive",["exports","core/reactive","qtype_questionpy/package_search/mutations","qtype_questionpy/package_search/events","qtype_questionpy/package_search/components/area"],(function(_exports,_reactive,_mutations,_events,_area){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_mutations=_interopRequireDefault(_mutations),_area=_interopRequireDefault(_area);let counter=0;class _default extends _reactive.Reactive{constructor(target,options){super({name:"PackageSearch".concat(counter++),eventName:_events.eventNames.stateChanged,eventDispatch:_events.notifyStateChanged,target:target,mutations:new _mutations.default(options),state:{general:{loading:!0,sorting:{sort:"alpha",order:"asc"},query:""},all:{count:0,total:0,page:0},allPackages:[],recentlyused:{count:0,total:0,page:0},recentlyusedPackages:[],favourites:{count:0,total:0,page:0},favouritesPackages:[],mine:{count:0,total:0,page:0},minePackages:[]}}),this.options=options}load(){new _area.default({element:this.target,name:"search_area",reactive:this})}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=reactive.min.js.map \ No newline at end of file diff --git a/amd/build/package_search/reactive.min.js.map b/amd/build/package_search/reactive.min.js.map index 86575c2f..69f22801 100644 --- a/amd/build/package_search/reactive.min.js.map +++ b/amd/build/package_search/reactive.min.js.map @@ -1 +1 @@ -{"version":3,"file":"reactive.min.js","sources":["../../src/package_search/reactive.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/package_search/reactive\n */\n\nimport {Reactive} from 'core/reactive';\n\nimport SearchMutations from 'qtype_questionpy/package_search/mutations';\nimport {eventNames, notifyStateChanged} from 'qtype_questionpy/package_search/events';\nimport Area from 'qtype_questionpy/package_search/components/area';\n\nlet counter = 0;\n\nexport default class extends Reactive {\n /**\n * Reactive element used for package search.\n *\n * @param {HTMLDivElement} target\n * @param {{contextid: number, limit: number}} options\n */\n constructor(target, options) {\n super({\n name: `PackageSearch${counter++}`,\n eventName: eventNames.stateChanged,\n eventDispatch: notifyStateChanged,\n target: target,\n mutations: new SearchMutations(options),\n state: {\n general: {\n loading: true,\n sorting: {\n sort: \"alpha\",\n order: \"asc\",\n },\n query: \"\",\n },\n all: {\n data: {\n packages: [],\n count: 0,\n total: 0,\n },\n page: 0,\n },\n recentlyused: {\n data: {\n packages: [],\n count: 0,\n total: 0,\n },\n page: 0,\n },\n favourites: {\n data: {\n packages: [],\n count: 0,\n total: 0,\n },\n page: 0,\n },\n mine: {\n data: {\n packages: [],\n count: 0,\n total: 0,\n },\n page: 0,\n },\n },\n });\n this.options = options;\n }\n\n /**\n * Loads every component of the package search area.\n */\n load() {\n new Area({\n element: this.target,\n name: \"search_area\",\n reactive: this\n });\n }\n}\n"],"names":["counter","Reactive","constructor","target","options","name","eventName","eventNames","stateChanged","eventDispatch","notifyStateChanged","mutations","SearchMutations","state","general","loading","sorting","sort","order","query","all","data","packages","count","total","page","recentlyused","favourites","mine","load","Area","element","this","reactive"],"mappings":"ogBA2BIA,QAAU,yBAEeC,mBAOzBC,YAAYC,OAAQC,eACV,CACFC,4BAAsBL,WACtBM,UAAWC,mBAAWC,aACtBC,cAAeC,2BACfP,OAAQA,OACRQ,UAAW,IAAIC,mBAAgBR,SAC/BS,MAAO,CACHC,QAAS,CACLC,SAAS,EACTC,QAAS,CACPC,KAAM,QACNC,MAAO,OAETC,MAAO,IAEXC,IAAK,CACDC,KAAM,CACFC,SAAU,GACVC,MAAO,EACPC,MAAO,GAEXC,KAAM,GAEVC,aAAc,CACVL,KAAM,CACFC,SAAU,GACVC,MAAO,EACPC,MAAO,GAEXC,KAAM,GAEVE,WAAY,CACRN,KAAM,CACFC,SAAU,GACVC,MAAO,EACPC,MAAO,GAEXC,KAAM,GAEVG,KAAM,CACFP,KAAM,CACFC,SAAU,GACVC,MAAO,EACPC,MAAO,GAEXC,KAAM,WAIbrB,QAAUA,QAMnByB,WACQC,cAAK,CACLC,QAASC,KAAK7B,OACdE,KAAM,cACN4B,SAAUD"} \ No newline at end of file +{"version":3,"file":"reactive.min.js","sources":["../../src/package_search/reactive.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/package_search/reactive\n */\n\nimport {Reactive} from 'core/reactive';\n\nimport SearchMutations from 'qtype_questionpy/package_search/mutations';\nimport {eventNames, notifyStateChanged} from 'qtype_questionpy/package_search/events';\nimport Area from 'qtype_questionpy/package_search/components/area';\n\nlet counter = 0;\n\nexport default class extends Reactive {\n /**\n * Reactive element used for package search.\n *\n * @param {HTMLDivElement} target\n * @param {{contextid: number, limit: number}} options\n */\n constructor(target, options) {\n super({\n name: `PackageSearch${counter++}`,\n eventName: eventNames.stateChanged,\n eventDispatch: notifyStateChanged,\n target: target,\n mutations: new SearchMutations(options),\n state: {\n general: {\n loading: true,\n sorting: {\n sort: \"alpha\",\n order: \"asc\",\n },\n query: \"\",\n },\n all: {\n count: 0,\n total: 0,\n page: 0,\n },\n allPackages: [],\n recentlyused: {\n count: 0,\n total: 0,\n page: 0,\n },\n recentlyusedPackages: [],\n favourites: {\n count: 0,\n total: 0,\n page: 0,\n },\n favouritesPackages: [],\n mine: {\n count: 0,\n total: 0,\n page: 0,\n },\n minePackages: [],\n },\n });\n this.options = options;\n }\n\n /**\n * Loads every component of the package search area.\n */\n load() {\n new Area({\n element: this.target,\n name: \"search_area\",\n reactive: this\n });\n }\n}\n"],"names":["counter","Reactive","constructor","target","options","name","eventName","eventNames","stateChanged","eventDispatch","notifyStateChanged","mutations","SearchMutations","state","general","loading","sorting","sort","order","query","all","count","total","page","allPackages","recentlyused","recentlyusedPackages","favourites","favouritesPackages","mine","minePackages","load","Area","element","this","reactive"],"mappings":"ogBA2BIA,QAAU,yBAEeC,mBAOzBC,YAAYC,OAAQC,eACV,CACFC,4BAAsBL,WACtBM,UAAWC,mBAAWC,aACtBC,cAAeC,2BACfP,OAAQA,OACRQ,UAAW,IAAIC,mBAAgBR,SAC/BS,MAAO,CACHC,QAAS,CACLC,SAAS,EACTC,QAAS,CACPC,KAAM,QACNC,MAAO,OAETC,MAAO,IAEXC,IAAK,CACDC,MAAO,EACPC,MAAO,EACPC,KAAM,GAEVC,YAAa,GACbC,aAAc,CACVJ,MAAO,EACPC,MAAO,EACPC,KAAM,GAEVG,qBAAsB,GACtBC,WAAY,CACRN,MAAO,EACPC,MAAO,EACPC,KAAM,GAEVK,mBAAoB,GACpBC,KAAM,CACFR,MAAO,EACPC,MAAO,EACPC,KAAM,GAEVO,aAAc,WAGjB1B,QAAUA,QAMnB2B,WACQC,cAAK,CACLC,QAASC,KAAK/B,OACdE,KAAM,cACN8B,SAAUD"} \ No newline at end of file diff --git a/amd/build/utils.min.js b/amd/build/utils.min.js new file mode 100644 index 00000000..113e5711 --- /dev/null +++ b/amd/build/utils.min.js @@ -0,0 +1,3 @@ +define("qtype_questionpy/utils",["exports","core/ajax"],(function(_exports,_ajax){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.favouritePackage=void 0,_ajax=(obj=_ajax)&&obj.__esModule?obj:{default:obj};_exports.favouritePackage=async(packageid,favourite,contextid)=>await _ajax.default.call([{methodname:"qtype_questionpy_favourite_package",args:{packageid:packageid,favourite:favourite,contextid:contextid}}])[0]})); + +//# sourceMappingURL=utils.min.js.map \ No newline at end of file diff --git a/amd/build/utils.min.js.map b/amd/build/utils.min.js.map new file mode 100644 index 00000000..d8f8227c --- /dev/null +++ b/amd/build/utils.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.min.js","sources":["../src/utils.js"],"sourcesContent":["/*\n * This file is part of the QuestionPy Moodle plugin - https://questionpy.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/**\n * @module qtype_questionpy/utils\n */\n\nimport Ajax from 'core/ajax';\n\n\n/**\n * Favourites a package.\n *\n * @param {number} packageid\n * @param {boolean} favourite\n * @param {number} contextid\n * @returns {Promise}\n */\nexport const favouritePackage = async(packageid, favourite, contextid) => {\n return await Ajax.call([{\n methodname: \"qtype_questionpy_favourite_package\",\n args: {\n packageid: packageid,\n favourite: favourite,\n contextid: contextid,\n },\n }])[0];\n};"],"names":["async","packageid","favourite","contextid","Ajax","call","methodname","args"],"mappings":"iQAgCgCA,MAAMC,UAAWC,UAAWC,kBAC3CC,cAAKC,KAAK,CAAC,CACpBC,WAAY,qCACZC,KAAM,CACFN,UAAWA,UACXC,UAAWA,UACXC,UAAWA,cAEf"} \ No newline at end of file diff --git a/amd/src/edit_question.js b/amd/src/edit_question.js index 82e17e1c..6b5517d1 100644 --- a/amd/src/edit_question.js +++ b/amd/src/edit_question.js @@ -16,6 +16,8 @@ */ import {resetFormDirtyState} from 'core_form/changechecker'; +import Notification from 'core/notification'; +import {favouritePackage} from 'qtype_questionpy/utils'; /** * This function is called by the package_selection-template and initializes the action button. @@ -25,15 +27,13 @@ import {resetFormDirtyState} from 'core_form/changechecker'; * data from being saved to the question, while still re-rendering the form with access to the new selected package * hash. * - * @param {string} cardId + * @param {HTMLDivElement} card * @param {boolean} selected */ -export function initActionButton(cardId, selected) { +export function initActionButton(card, selected) { const packageChanged = document.querySelector('input[name="qpy_package_changed"]'); const packageHash = document.querySelector('input[name="qpy_package_hash"]'); - const card = document.getElementById(cardId); - if (selected) { // Initialize the button to change the package. const changeButton = card.getElementsByClassName("qpy-version-selection-button")[0]; @@ -66,3 +66,26 @@ export function initActionButton(cardId, selected) { } } +/** + * This function is called by the package_selection-template and initializes the favourite button, when + * a package is already selected. + * + * @param {HTMLDivElement} card + * @param {number} packageId + * @param {number} contextId + */ +export function initFavouriteButton(card, packageId, contextId) { + const button = card.querySelector('[data-for="favourite-button"]'); + const isFavouriteAttributeName = "data-is-favourite"; + button.addEventListener("click", async() => { + try { + const isFavourite = button.hasAttribute(isFavouriteAttributeName); + const successful = await favouritePackage(packageId, !isFavourite, contextId); + if (successful) { + button.toggleAttribute(isFavouriteAttributeName, !isFavourite); + } + } catch (exception) { + await Notification.exception(exception); + } + }); +} \ No newline at end of file diff --git a/amd/src/package_search/components/package.js b/amd/src/package_search/components/package.js new file mode 100644 index 00000000..d48329eb --- /dev/null +++ b/amd/src/package_search/components/package.js @@ -0,0 +1,53 @@ +/* + * This file is part of the QuestionPy Moodle plugin - https://questionpy.org + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ + +/** + * @module qtype_questionpy/package_search/components/package + */ + +import Component from 'qtype_questionpy/package_search/component'; + +export default class extends Component { + getWatchers() { + return [ + {watch: `${this.category}Packages[${this.packageid}].isfavourite:updated`, handler: this.favouriteChanged}, + ]; + } + + create(descriptor) { + this.packageid = descriptor.packageid; + this.category = descriptor.category; + this.selectors = { + FAVOURITE_BUTTON: '[data-for="favourite-button"]', + }; + } + + isFavourite() { + return this.getState()[`${this.category}Packages`].get(this.packageid).isfavourite; + } + + stateReady() { + this.addEventListener(this.getElement(this.selectors.FAVOURITE_BUTTON), "click", () => { + this.reactive.dispatch("favourite", this.packageid, !this.isFavourite()); + }); + } + + async favouriteChanged() { + const isFavourite = this.isFavourite(); + this.getElement(this.selectors.FAVOURITE_BUTTON).toggleAttribute("data-is-favourite", isFavourite); + } +} diff --git a/amd/src/package_search/components/pagination.js b/amd/src/package_search/components/pagination.js index 078e806b..ae62f2ac 100644 --- a/amd/src/package_search/components/pagination.js +++ b/amd/src/package_search/components/pagination.js @@ -24,7 +24,7 @@ import Component from 'qtype_questionpy/package_search/component'; export default class extends Component { getWatchers() { return [ - {watch: `${this.category}.data:updated`, handler: this.updateCurrentPage}, + {watch: `${this.category}:updated`, handler: this.updateCurrentPage}, ]; } @@ -60,10 +60,10 @@ export default class extends Component { */ _getLastPage() { const state = this.getState()[this.category]; - if (state.data.total === 0) { + if (state.total === 0) { return 0; } - return Math.floor((state.data.total - 1) / this.reactive.options.limit); + return Math.floor((state.total - 1) / this.reactive.options.limit); } /** diff --git a/amd/src/package_search/components/tab_content.js b/amd/src/package_search/components/tab_content.js index 92f0fe8a..7b8dd972 100644 --- a/amd/src/package_search/components/tab_content.js +++ b/amd/src/package_search/components/tab_content.js @@ -24,11 +24,12 @@ import Notification from 'core/notification'; import Component from 'qtype_questionpy/package_search/component'; import Pagination from 'qtype_questionpy/package_search/components/pagination'; import Sort from 'qtype_questionpy/package_search/components/sort'; +import Package from 'qtype_questionpy/package_search/components/package'; export default class extends Component { getWatchers() { return [ - {watch: `${this.category}:updated`, handler: this.render}, + {watch: `state.${this.category}Packages:updated`, handler: this.render}, ]; } @@ -69,7 +70,9 @@ export default class extends Component { _getPackageTemplatesPromise(contexts) { let promises = []; for (const context of contexts) { - const promise = templates.renderForPromise("qtype_questionpy/package/package_selection", context); + // Context is a proxy, we need to get the target. + const contextObj = Object.assign({}, context); + const promise = templates.renderForPromise("qtype_questionpy/package/package_selection", contextObj); promises.push(promise); } return Promise.all(promises); @@ -80,12 +83,18 @@ export default class extends Component { */ async render() { try { - const state = this.getState()[this.category]; - const packageTemplates = await this._getPackageTemplatesPromise(state.data.packages); - const element = this.getElement(this.selectors.CONTENT); - element.innerHTML = ""; + const packages = Array.from(this.getState()[`${this.category}Packages`].values()); + const packageTemplates = await this._getPackageTemplatesPromise(packages); + const contentElement = this.getElement(this.selectors.CONTENT); + contentElement.innerHTML = ""; + let index = 0; for (const {html, js} of packageTemplates) { - templates.appendNodeContents(element, html, js); + const packageElement = templates.appendNodeContents(contentElement, html, js)[0]; + new Package({ + element: packageElement, + category: this.category, + packageid: packages[index++].id, + }); } } catch (exception) { await Notification.exception(exception); diff --git a/amd/src/package_search/components/tab_header.js b/amd/src/package_search/components/tab_header.js index 66a96561..2a10451d 100644 --- a/amd/src/package_search/components/tab_header.js +++ b/amd/src/package_search/components/tab_header.js @@ -25,7 +25,7 @@ import Component from 'qtype_questionpy/package_search/component'; export default class extends Component { getWatchers() { return [ - {watch: `${this.category}:updated`, handler: this.render}, + {watch: `${this.category}.total:updated`, handler: this.render}, ]; } @@ -37,7 +37,7 @@ export default class extends Component { * Renders every package inside a specific tab. */ async render() { - const data = this.getState()[this.category].data; + const data = this.getState()[this.category]; this.element.innerHTML = await strings.get_string(`search_${this.category}_header`, "qtype_questionpy", data.total); } diff --git a/amd/src/package_search/mutations.js b/amd/src/package_search/mutations.js index 907b47f1..025e382a 100644 --- a/amd/src/package_search/mutations.js +++ b/amd/src/package_search/mutations.js @@ -21,6 +21,7 @@ import Ajax from 'core/ajax'; import Notification from 'core/notification'; +import {favouritePackage} from "../utils"; export default class { /** @@ -98,7 +99,10 @@ export default class { stateManager.setReadOnly(false); // Update category specific data. for (const [index, category] of categories.entries()) { - state[category].data = await results[index]; + const result = await results[index]; + state[`${category}Packages`] = result.packages; + state[category].count = result.count; + state[category].total = result.total; if (typeof args.page === "number") { state[category].page = args.page; } @@ -142,4 +146,57 @@ export default class { async changeSort(stateManager, sort, order) { await this.searchPackages(stateManager, {sort: sort, order: order}, ["all", "favourites", "mine"]); } + + /** + * Used to re-/load data of given categories. + * + * @param {StateManager} stateManager + * @param {string[]} categories + */ + async load(stateManager, categories) { + await this.searchPackages(stateManager, {}, categories); + } + + /** + * Used to un-/favourite a package. + * + * @param {StateManager} stateManager + * @param {int} packageid + * @param {boolean} favourite + */ + async favourite(stateManager, packageid, favourite) { + const state = stateManager.state; + try { + stateManager.setReadOnly(false); + state.general.loading = true; + const successful = await favouritePackage(packageid, favourite, this.options.contextid); + if (!successful) { + return; + } + for (const category of ["all", "mine", "recentlyused"]) { + const pkg = state[`${category}Packages`].get(packageid); + if (pkg) { + pkg.isfavourite = favourite; + } + } + stateManager.setReadOnly(true); + let page = state.favourites.page; + if (!favourite) { + // Turn back a page in 'favourites' if the unmarked package was the last one on the page. + const isFirstPage = page === 0; + const isLastPage = page === Math.floor((state.favourites.total - 1) / this.options.limit); + const existsOnePackage = state.favourites.count === 1; + if (!isFirstPage && isLastPage && existsOnePackage) { + page -= 1; + } + } + await this.changePage(stateManager, 'favourites', page); + } catch (exception) { + if (!stateManager.readonly) { + state.general.loading = false; + stateManager.setReadOnly(true); + } + await Notification.exception(exception); + } + } } diff --git a/amd/src/package_search/reactive.js b/amd/src/package_search/reactive.js index a5242f21..ae2ec5bd 100644 --- a/amd/src/package_search/reactive.js +++ b/amd/src/package_search/reactive.js @@ -51,37 +51,29 @@ export default class extends Reactive { query: "", }, all: { - data: { - packages: [], - count: 0, - total: 0, - }, + count: 0, + total: 0, page: 0, }, + allPackages: [], recentlyused: { - data: { - packages: [], - count: 0, - total: 0, - }, + count: 0, + total: 0, page: 0, }, + recentlyusedPackages: [], favourites: { - data: { - packages: [], - count: 0, - total: 0, - }, + count: 0, + total: 0, page: 0, }, + favouritesPackages: [], mine: { - data: { - packages: [], - count: 0, - total: 0, - }, + count: 0, + total: 0, page: 0, }, + minePackages: [], }, }); this.options = options; diff --git a/amd/src/utils.js b/amd/src/utils.js new file mode 100644 index 00000000..9a30877d --- /dev/null +++ b/amd/src/utils.js @@ -0,0 +1,42 @@ +/* + * This file is part of the QuestionPy Moodle plugin - https://questionpy.org + * + * Moodle is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Moodle is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Moodle. If not, see . + */ + +/** + * @module qtype_questionpy/utils + */ + +import Ajax from 'core/ajax'; + + +/** + * Favourites a package. + * + * @param {number} packageid + * @param {boolean} favourite + * @param {number} contextid + * @returns {Promise} + */ +export const favouritePackage = async(packageid, favourite, contextid) => { + return await Ajax.call([{ + methodname: "qtype_questionpy_favourite_package", + args: { + packageid: packageid, + favourite: favourite, + contextid: contextid, + }, + }])[0]; +}; \ No newline at end of file diff --git a/edit_questionpy_form.php b/edit_questionpy_form.php index a74963c5..b89233f6 100644 --- a/edit_questionpy_form.php +++ b/edit_questionpy_form.php @@ -55,7 +55,7 @@ protected function definition_package_selection(MoodleQuickForm $mform): void { // Create a group which contains the package container - the group is used to simplify the styling. // TODO: get limit from settings. $group[] = $mform->createElement('html', $OUTPUT->render_from_template('qtype_questionpy/package_search/area', - ['contextid' => $PAGE->context->id, 'limit' => 10])); + ['contextid' => $PAGE->context->id, 'limit' => 2])); $mform->addGroup($group, 'questionpy_package_container', get_string('selection_title', 'qtype_questionpy'), null, false); $mform->addRule( 'questionpy_package_container', @@ -73,7 +73,7 @@ protected function definition_package_selection(MoodleQuickForm $mform): void { * @throws moodle_exception */ protected function definition_package_settings(MoodleQuickForm $mform, string $packagehash): void { - global $OUTPUT; + global $OUTPUT, $USER, $PAGE; $pkgversion = package_version::get_by_hash($packagehash); $package = package::get_by_version($pkgversion->id); @@ -82,6 +82,11 @@ protected function definition_package_settings(MoodleQuickForm $mform, string $p $packagearray = $package->as_localized_array($languages); $packagearray['selected'] = true; $packagearray['versions'] = ['hash' => $pkgversion->hash, 'version' => $pkgversion->version]; + $packagearray['contextid'] = $PAGE->context->id; + + $usercontext = context_user::instance($USER->id); + $ufservice = \core_favourites\service_factory::get_service_for_user_context($usercontext); + $packagearray['isfavourite'] = $ufservice->favourite_exists('qtype_questionpy', 'package', $package->id, $usercontext); $group = []; $group[] = $mform->createElement( diff --git a/lang/en/qtype_questionpy.php b/lang/en/qtype_questionpy.php index 4ba0c1a5..05651cef 100644 --- a/lang/en/qtype_questionpy.php +++ b/lang/en/qtype_questionpy.php @@ -66,6 +66,8 @@ $string['change_package'] = 'Change'; $string['open_website'] = 'Open website'; +$string['mark_as_favourite'] = 'Favourite'; +$string['unmark_as_favourite'] = 'Favourite'; // Package search. $string['search_bar'] = 'Search...'; diff --git a/styles.css b/styles.css index e9e5991c..944cce25 100644 --- a/styles.css +++ b/styles.css @@ -95,6 +95,12 @@ justify-content: space-between; } +.qpy-card-action [data-for="favourite-button"][data-is-favourite] .qpy-unfavourite-button-text, +.qpy-card-action [data-for="favourite-button"]:not([data-is-favourite]) .qpy-favourite-button-text { + visibility: hidden; + display: none; +} + .qpy-package-search-area:not(.qpy-loading) .qpy-loading-indicator { visibility: hidden; } diff --git a/templates/package/package_selection.mustache b/templates/package/package_selection.mustache index b20e54ff..25027dfc 100644 --- a/templates/package/package_selection.mustache +++ b/templates/package/package_selection.mustache @@ -23,24 +23,34 @@ * qpy-version-selection, * qpy-version-selection-button + Data attributes required for JS: + * data-for="favouite-button", + * data-is-favourite + Context variables required for this template: + * id - QuestionPy package id, * namespace - QuestionyPy package namespace, * name - QuestionPy package name, * description - QuestionPy package description, * icon - QuestionPy package icon, - * selected - Weather the current QuestionPy package is selected or not, * versions - If 'selected' is true, include only the selected package; else, include every available package, - * url - The url of the QuestionPy package + * url - The url of the QuestionPy package, + * selected - Whether the current QuestionPy package is selected or not, + * isfavourite - Whether the current QuestionPy package is marked as favourite or not, + * contextid - The current context id. Example context (json): { + "id": 0, "namespace": "example_namespace", "name": "ExamplePackage", "description": "This describes the package ExamplePackage.", "icon": "https://picsum.photos/48/48?grayscale", - "selected": true, "versions": [{"hash": "example_hash", "version": "0.1.0"}], - "url": "https://example.com" + "url": "https://example.com", + "selected": true, + "isfavourite": false, + "contextid": 0 } }} {{ {{^selected}} - + {{/selected}} {{#selected}} - + {{/selected}} {{/action}} {{/qtype_questionpy/package/package_base}} {{#js}} -require(['qtype_questionpy/edit_question'], function(module) { - module.initActionButton('qpy-card-{{uniqid}}', {{selected}}); -}); + require(['qtype_questionpy/edit_question'], function(module) { + const card = document.getElementById("qpy-card-{{uniqid}}"); + module.initActionButton(card, {{selected}}); + {{#selected}} + module.initFavouriteButton(card, {{id}}, {{contextid}}); + {{/selected}} + }); {{/js}} \ No newline at end of file diff --git a/version.php b/version.php index 4ce53cc5..4db8fcf3 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'qtype_questionpy'; -$plugin->version = 2024021900; +$plugin->version = 2024022900; $plugin->requires = 2022041901; $plugin->maturity = MATURITY_ALPHA; $plugin->release = '0.1';