diff --git a/amd/build/package_search/components/area.min.js b/amd/build/package_search/components/area.min.js index 99cd28f7..d12626a1 100644 --- a/amd/build/package_search/components/area.min.js +++ b/amd/build/package_search/components/area.min.js @@ -1,3 +1,3 @@ -define("qtype_questionpy/package_search/components/area",["exports","qtype_questionpy/package_search/component","qtype_questionpy/package_search/components/container","qtype_questionpy/package_search/components/search_bar"],(function(_exports,_component,_container,_search_bar){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_component=_interopRequireDefault(_component),_container=_interopRequireDefault(_container),_search_bar=_interopRequireDefault(_search_bar);class _default extends _component.default{getWatchers(){return[{watch:"general.loading:updated",handler:this.updateStatus}]}create(descriptor){new _search_bar.default({element:this.getElement('[data-for="search-bar-container"'),name:"search_bar",reactive:descriptor.reactive}),new _container.default({element:this.getElement('[data-for="package-container"'),name:"container",reactive:descriptor.reactive})}stateReady(){this.reactive.dispatch("searchPackages")}updateStatus(){const loading=this.getState().general.loading;this.element.classList.toggle("qpy-loading",loading)}}return _exports.default=_default,_exports.default})); +define("qtype_questionpy/package_search/components/area",["exports","qtype_questionpy/package_search/component","qtype_questionpy/package_search/components/container","qtype_questionpy/package_search/components/upload","qtype_questionpy/package_search/components/search_bar"],(function(_exports,_component,_container,_upload,_search_bar){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_component=_interopRequireDefault(_component),_container=_interopRequireDefault(_container),_upload=_interopRequireDefault(_upload),_search_bar=_interopRequireDefault(_search_bar);class _default extends _component.default{getWatchers(){return[{watch:"general.loading:updated",handler:this.updateStatus}]}create(descriptor){new _search_bar.default({element:this.getElement('[data-for="search-bar-container"'),name:"search_bar",reactive:descriptor.reactive}),new _upload.default({element:this.getElement('[data-for="upload-button"]'),name:"upload_button",reactive:descriptor.reactive}),new _container.default({element:this.getElement('[data-for="package-container"'),name:"container",reactive:descriptor.reactive})}stateReady(){this.reactive.dispatch("searchPackages")}updateStatus(){const loading=this.getState().general.loading;this.element.classList.toggle("qpy-loading",loading)}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=area.min.js.map \ No newline at end of file diff --git a/amd/build/package_search/components/area.min.js.map b/amd/build/package_search/components/area.min.js.map index 4936008f..e48e31db 100644 --- a/amd/build/package_search/components/area.min.js.map +++ b/amd/build/package_search/components/area.min.js.map @@ -1 +1 @@ -{"version":3,"file":"area.min.js","sources":["../../../src/package_search/components/area.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/area\n */\n\nimport Component from 'qtype_questionpy/package_search/component';\nimport Container from 'qtype_questionpy/package_search/components/container';\nimport SearchBar from 'qtype_questionpy/package_search/components/search_bar';\n\nexport default class extends Component {\n getWatchers() {\n return [\n {watch: `general.loading:updated`, handler: this.updateStatus},\n ];\n }\n\n create(descriptor) {\n // Register search bar.\n new SearchBar({\n element: this.getElement('[data-for=\"search-bar-container\"'),\n name: \"search_bar\",\n reactive: descriptor.reactive,\n });\n // Register package container.\n new Container({\n element: this.getElement('[data-for=\"package-container\"'),\n name: \"container\",\n reactive: descriptor.reactive,\n });\n }\n\n stateReady() {\n // Initial loading of the packages.\n this.reactive.dispatch(\"searchPackages\");\n }\n\n /**\n * Adds or removes the `qpy-loading` class from the search area.\n */\n updateStatus() {\n const loading = this.getState().general.loading;\n this.element.classList.toggle(\"qpy-loading\", loading);\n }\n}\n"],"names":["Component","getWatchers","watch","handler","this","updateStatus","create","descriptor","SearchBar","element","getElement","name","reactive","Container","stateReady","dispatch","loading","getState","general","classList","toggle"],"mappings":"2lBAyB6BA,mBACzBC,oBACW,CACH,CAACC,gCAAkCC,QAASC,KAAKC,eAIzDC,OAAOC,gBAECC,oBAAU,CACVC,QAASL,KAAKM,WAAW,oCACzBC,KAAM,aACNC,SAAUL,WAAWK,eAGrBC,mBAAU,CACVJ,QAASL,KAAKM,WAAW,iCACzBC,KAAM,YACNC,SAAUL,WAAWK,WAI7BE,kBAESF,SAASG,SAAS,kBAM3BV,qBACUW,QAAUZ,KAAKa,WAAWC,QAAQF,aACnCP,QAAQU,UAAUC,OAAO,cAAeJ"} \ No newline at end of file +{"version":3,"file":"area.min.js","sources":["../../../src/package_search/components/area.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/area\n */\n\nimport Component from 'qtype_questionpy/package_search/component';\nimport Container from 'qtype_questionpy/package_search/components/container';\nimport UploadButton from 'qtype_questionpy/package_search/components/upload';\nimport SearchBar from 'qtype_questionpy/package_search/components/search_bar';\n\nexport default class extends Component {\n getWatchers() {\n return [\n {watch: `general.loading:updated`, handler: this.updateStatus},\n ];\n }\n\n create(descriptor) {\n // Register search bar.\n // TODO: register component inside mustache template.\n new SearchBar({\n element: this.getElement('[data-for=\"search-bar-container\"'),\n name: \"search_bar\",\n reactive: descriptor.reactive,\n });\n // Register upload button.\n new UploadButton({\n element: this.getElement('[data-for=\"upload-button\"]'),\n name: \"upload_button\",\n reactive: descriptor.reactive,\n });\n // Register package container.\n // TODO: register component inside mustache template.\n new Container({\n element: this.getElement('[data-for=\"package-container\"'),\n name: \"container\",\n reactive: descriptor.reactive,\n });\n }\n\n stateReady() {\n // Initial loading of the packages.\n this.reactive.dispatch(\"searchPackages\");\n }\n\n /**\n * Adds or removes the `qpy-loading` class from the search area.\n */\n updateStatus() {\n const loading = this.getState().general.loading;\n this.element.classList.toggle(\"qpy-loading\", loading);\n }\n}\n"],"names":["Component","getWatchers","watch","handler","this","updateStatus","create","descriptor","SearchBar","element","getElement","name","reactive","UploadButton","Container","stateReady","dispatch","loading","getState","general","classList","toggle"],"mappings":"+rBA0B6BA,mBACzBC,oBACW,CACH,CAACC,gCAAkCC,QAASC,KAAKC,eAIzDC,OAAOC,gBAGCC,oBAAU,CACVC,QAASL,KAAKM,WAAW,oCACzBC,KAAM,aACNC,SAAUL,WAAWK,eAGrBC,gBAAa,CACbJ,QAASL,KAAKM,WAAW,8BACzBC,KAAM,gBACNC,SAAUL,WAAWK,eAIrBE,mBAAU,CACVL,QAASL,KAAKM,WAAW,iCACzBC,KAAM,YACNC,SAAUL,WAAWK,WAI7BG,kBAESH,SAASI,SAAS,kBAM3BX,qBACUY,QAAUb,KAAKc,WAAWC,QAAQF,aACnCR,QAAQW,UAAUC,OAAO,cAAeJ"} \ No newline at end of file diff --git a/amd/build/package_search/components/container.min.js.map b/amd/build/package_search/components/container.min.js.map index 0a2e73cb..22fb7806 100644 --- a/amd/build/package_search/components/container.min.js.map +++ b/amd/build/package_search/components/container.min.js.map @@ -1 +1 @@ -{"version":3,"file":"container.min.js","sources":["../../../src/package_search/components/container.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/container\n */\n\nimport * as templates from 'core/templates';\nimport Component from 'qtype_questionpy/package_search/component';\nimport TabHeader from 'qtype_questionpy/package_search/components/tab_header';\nimport TabContent from 'qtype_questionpy/package_search/components/tab_content';\n\nexport default class extends Component {\n async create(descriptor) {\n // Register header and content of tabs.\n for (const category of [\"all\", \"recentlyused\", \"favourites\", \"mine\"]) {\n new TabHeader({\n element: this.getElement(`[data-for=\"${category}-header\"]`),\n name: `category_${category}_header`,\n reactive: descriptor.reactive,\n category: category,\n });\n new TabContent({\n element: this.getElement(`[data-for=\"${category}-content\"]`),\n name: `category_${category}_header`,\n reactive: descriptor.reactive,\n category: category,\n });\n }\n\n // Prefetch the package template for faster rendering.\n templates.prefetchTemplates([\"qtype_questionpy/package/package_selection\"]);\n }\n}\n"],"names":["Component","descriptor","category","TabHeader","element","this","getElement","name","reactive","TabContent","templates","prefetchTemplates"],"mappings":"wjDA0B6BA,gCACZC,gBAEJ,MAAMC,WAAY,CAAC,MAAO,eAAgB,aAAc,YACrDC,oBAAU,CACVC,QAASC,KAAKC,gCAAyBJ,uBACvCK,wBAAkBL,oBAClBM,SAAUP,WAAWO,SACrBN,SAAUA,eAEVO,qBAAW,CACXL,QAASC,KAAKC,gCAAyBJ,wBACvCK,wBAAkBL,oBAClBM,SAAUP,WAAWO,SACrBN,SAAUA,WAKlBQ,UAAUC,kBAAkB,CAAC"} \ No newline at end of file +{"version":3,"file":"container.min.js","sources":["../../../src/package_search/components/container.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/container\n */\n\nimport * as templates from 'core/templates';\nimport Component from 'qtype_questionpy/package_search/component';\nimport TabHeader from 'qtype_questionpy/package_search/components/tab_header';\nimport TabContent from 'qtype_questionpy/package_search/components/tab_content';\n\nexport default class extends Component {\n async create(descriptor) {\n // Register header and content of tabs.\n // TODO: register components inside mustache template.\n for (const category of [\"all\", \"recentlyused\", \"favourites\", \"mine\"]) {\n new TabHeader({\n element: this.getElement(`[data-for=\"${category}-header\"]`),\n name: `category_${category}_header`,\n reactive: descriptor.reactive,\n category: category,\n });\n new TabContent({\n element: this.getElement(`[data-for=\"${category}-content\"]`),\n name: `category_${category}_header`,\n reactive: descriptor.reactive,\n category: category,\n });\n }\n\n // Prefetch the package template for faster rendering.\n templates.prefetchTemplates([\"qtype_questionpy/package/package_selection\"]);\n }\n}\n"],"names":["Component","descriptor","category","TabHeader","element","this","getElement","name","reactive","TabContent","templates","prefetchTemplates"],"mappings":"wjDA0B6BA,gCACZC,gBAGJ,MAAMC,WAAY,CAAC,MAAO,eAAgB,aAAc,YACrDC,oBAAU,CACVC,QAASC,KAAKC,gCAAyBJ,uBACvCK,wBAAkBL,oBAClBM,SAAUP,WAAWO,SACrBN,SAAUA,eAEVO,qBAAW,CACXL,QAASC,KAAKC,gCAAyBJ,wBACvCK,wBAAkBL,oBAClBM,SAAUP,WAAWO,SACrBN,SAAUA,WAKlBQ,UAAUC,kBAAkB,CAAC"} \ 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 index 4e3bfc0d..9b36679f 100644 --- a/amd/build/package_search/components/package.min.js +++ b/amd/build/package_search/components/package.min.js @@ -1,3 +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})); +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"]',DOWNLOAD_BUTTON:'[data-for="download-button"]',VERSION_SELECTION:".qpy-version-selection"}}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())})),this.addEventListener(this.getElement(this.selectors.VERSION_SELECTION),"change",(()=>{this.setUpDownloadButton()})),this.setUpDownloadButton()}setUpDownloadButton(){const selection=this.getElement(this.selectors.VERSION_SELECTION),option=selection.options[selection.selectedIndex],button=this.getElement(this.selectors.DOWNLOAD_BUTTON);button.classList.toggle("d-none",!option.hasAttribute("data-is-mine")),button.href=option.dataset.fileurl}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 index f52e8d13..76d1820d 100644 --- a/amd/build/package_search/components/package.min.js.map +++ b/amd/build/package_search/components/package.min.js.map @@ -1 +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 +{"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 DOWNLOAD_BUTTON: '[data-for=\"download-button\"]',\n VERSION_SELECTION: '.qpy-version-selection',\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 this.addEventListener(this.getElement(this.selectors.VERSION_SELECTION), \"change\", () => {\n this.setUpDownloadButton();\n });\n\n this.setUpDownloadButton();\n }\n\n setUpDownloadButton() {\n const selection = this.getElement(this.selectors.VERSION_SELECTION);\n const option = selection.options[selection.selectedIndex];\n const button = this.getElement(this.selectors.DOWNLOAD_BUTTON);\n button.classList.toggle(\"d-none\", !option.hasAttribute(\"data-is-mine\"));\n button.href = option.dataset.fileurl;\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","DOWNLOAD_BUTTON","VERSION_SELECTION","isFavourite","getState","get","isfavourite","stateReady","addEventListener","getElement","reactive","dispatch","setUpDownloadButton","selection","option","options","selectedIndex","button","classList","toggle","hasAttribute","href","dataset","fileurl","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,gCAClBC,gBAAiB,+BACjBC,kBAAmB,0BAI3BC,qBACWX,KAAKY,qBAAcZ,KAAKC,sBAAoBY,IAAIb,KAAKE,WAAWY,YAG3EC,kBACSC,iBAAiBhB,KAAKiB,WAAWjB,KAAKO,UAAUC,kBAAmB,SAAS,UACxEU,SAASC,SAAS,YAAanB,KAAKE,WAAYF,KAAKW,uBAGzDK,iBAAiBhB,KAAKiB,WAAWjB,KAAKO,UAAUG,mBAAoB,UAAU,UAC1EU,8BAGJA,sBAGTA,4BACUC,UAAYrB,KAAKiB,WAAWjB,KAAKO,UAAUG,mBAC3CY,OAASD,UAAUE,QAAQF,UAAUG,eACrCC,OAASzB,KAAKiB,WAAWjB,KAAKO,UAAUE,iBAC9CgB,OAAOC,UAAUC,OAAO,UAAWL,OAAOM,aAAa,iBACvDH,OAAOI,KAAOP,OAAOQ,QAAQC,uCAIvBpB,YAAcX,KAAKW,mBACpBM,WAAWjB,KAAKO,UAAUC,kBAAkBwB,gBAAgB,oBAAqBrB"} \ 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 e0157e10..94edd05e 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';\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 +{"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 // TODO: register component inside mustache template.\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 // TODO: register component inside mustache template.\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 // TODO: register component inside mustache template.\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,iCAKVC,YAAcT,KAAKU,WAAWV,KAAKK,UAAUE,MAC/CE,iBACIE,cAAK,CACLC,QAASH,YACTI,oBAAcb,KAAKC,UACnBa,SAAUV,WAAWU,eAMzBC,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,OAE1EG,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/upload.min.js b/amd/build/package_search/components/upload.min.js new file mode 100644 index 00000000..e15d8b98 --- /dev/null +++ b/amd/build/package_search/components/upload.min.js @@ -0,0 +1,3 @@ +define("qtype_questionpy/package_search/components/upload",["exports","qtype_questionpy/package_search/component","core_form/modalform","core/str"],(function(_exports,_component,_modalform,strings){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)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_component=_interopRequireDefault(_component),_modalform=_interopRequireDefault(_modalform),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);class _default extends _component.default{stateReady(){this.addEventListener(this.element,"click",this.openUploadForm)}openUploadForm(event){const element=event.target,modalForm=new _modalform.default({formClass:"qtype_questionpy\\form\\package_upload",args:{contextid:this.reactive.options.contextid},modalConfig:{title:strings.get_string("upload_package","qtype_questionpy")},saveButtonText:strings.get_string("upload"),returnFocus:element});modalForm.addEventListener(modalForm.events.SUBMIT_BUTTON_PRESSED,(()=>this._packageIsUploading())),modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,(()=>this._packageWasUploaded())),modalForm.show()}_packageIsUploading(){}_packageWasUploaded(){this.reactive.dispatch("packageUploaded"),$(this.reactive.target).find('[data-for="mine-header"]').tab("show")}}return _exports.default=_default,_exports.default})); + +//# sourceMappingURL=upload.min.js.map \ No newline at end of file diff --git a/amd/build/package_search/components/upload.min.js.map b/amd/build/package_search/components/upload.min.js.map new file mode 100644 index 00000000..c7bab2a9 --- /dev/null +++ b/amd/build/package_search/components/upload.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"upload.min.js","sources":["../../../src/package_search/components/upload.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/upload\n */\n\nimport Component from 'qtype_questionpy/package_search/component';\n\nimport ModalForm from 'core_form/modalform';\nimport * as strings from 'core/str';\n\nexport default class extends Component {\n stateReady() {\n this.addEventListener(this.element, \"click\", this.openUploadForm);\n }\n\n /**\n *\n * @param {MouseEvent} event\n */\n openUploadForm(event) {\n const element = event.target;\n const modalForm = new ModalForm({\n formClass: \"qtype_questionpy\\\\form\\\\package_upload\",\n args: {contextid: this.reactive.options.contextid},\n modalConfig: {\n title: strings.get_string(\"upload_package\", \"qtype_questionpy\"),\n },\n saveButtonText: strings.get_string(\"upload\"),\n returnFocus: element,\n });\n modalForm.addEventListener(modalForm.events.SUBMIT_BUTTON_PRESSED, () => this._packageIsUploading());\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, () => this._packageWasUploaded());\n modalForm.show();\n }\n\n _packageIsUploading() {\n // TODO: show loading icon.\n }\n\n _packageWasUploaded() {\n this.reactive.dispatch(\"packageUploaded\");\n $(this.reactive.target).find('[data-for=\"mine-header\"]').tab('show');\n }\n\n}\n"],"names":["Component","stateReady","addEventListener","this","element","openUploadForm","event","target","modalForm","ModalForm","formClass","args","contextid","reactive","options","modalConfig","title","strings","get_string","saveButtonText","returnFocus","events","SUBMIT_BUTTON_PRESSED","_packageIsUploading","FORM_SUBMITTED","_packageWasUploaded","show","dispatch","$","find","tab"],"mappings":"44CA0B6BA,mBACzBC,kBACSC,iBAAiBC,KAAKC,QAAS,QAASD,KAAKE,gBAOtDA,eAAeC,aACLF,QAAUE,MAAMC,OAChBC,UAAY,IAAIC,mBAAU,CAC5BC,UAAW,yCACXC,KAAM,CAACC,UAAWT,KAAKU,SAASC,QAAQF,WACxCG,YAAa,CACTC,MAAOC,QAAQC,WAAW,iBAAkB,qBAEhDC,eAAgBF,QAAQC,WAAW,UACnCE,YAAahB,UAEjBI,UAAUN,iBAAiBM,UAAUa,OAAOC,uBAAuB,IAAMnB,KAAKoB,wBAC9Ef,UAAUN,iBAAiBM,UAAUa,OAAOG,gBAAgB,IAAMrB,KAAKsB,wBACvEjB,UAAUkB,OAGdH,uBAIAE,2BACSZ,SAASc,SAAS,mBACvBC,EAAEzB,KAAKU,SAASN,QAAQsB,KAAK,4BAA4BC,IAAI"} \ 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 d8a0bdc4..2522f2fa 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","qtype_questionpy/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)}_setLoading(stateManager,loading){const state=stateManager.state;if(state.loading===loading)return;const isReadonly=stateManager.readonly;isReadonly&&stateManager.setReadOnly(!1),state.general.loading=loading,isReadonly&&stateManager.setReadOnly(!0)}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),this._setLoading(stateManager,!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)}stateManager.setReadOnly(!0)}catch(exception){await _notification.default.exception(exception)}finally{this._setLoading(stateManager,!1)}}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){const state=stateManager.state;try{this._setLoading(stateManager,!0);if(!await(0,_utils.favouritePackage)(packageid,favourite,this.options.contextid))return;stateManager.setReadOnly(!1);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)}finally{this._setLoading(stateManager,!1)}}},_exports.default})); +define("qtype_questionpy/package_search/mutations",["exports","core/ajax","core/notification","qtype_questionpy/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)}_setLoading(stateManager,loading){const state=stateManager.state;if(state.loading===loading)return;const isReadonly=stateManager.readonly;isReadonly&&stateManager.setReadOnly(!1),state.general.loading=loading,isReadonly&&stateManager.setReadOnly(!0)}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),this._setLoading(stateManager,!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)}stateManager.setReadOnly(!0)}catch(exception){await _notification.default.exception(exception)}finally{this._setLoading(stateManager,!1)}}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){const state=stateManager.state;try{this._setLoading(stateManager,!0);if(!await(0,_utils.favouritePackage)(packageid,favourite,this.options.contextid))return;stateManager.setReadOnly(!1);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)}finally{this._setLoading(stateManager,!1)}}async packageUploaded(stateManager){await this.changeSort(stateManager,"date","desc")}},_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 896653fb..20864bad 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';\nimport {favouritePackage} from 'qtype_questionpy/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 * Sets the `loading` property.\n *\n * It only communicates changes to the watchers if the `StateManager` is currently readonly.\n *\n * @param {StateManager} stateManager\n * @param {boolean} loading\n * @private\n */\n _setLoading(stateManager, loading) {\n const state = stateManager.state;\n if (state.loading === loading) {\n return;\n }\n const isReadonly = stateManager.readonly;\n if (isReadonly) {\n stateManager.setReadOnly(false);\n }\n state.general.loading = loading;\n if (isReadonly) {\n stateManager.setReadOnly(true);\n }\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 this._setLoading(stateManager, 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 stateManager.setReadOnly(true);\n } catch (exception) {\n await Notification.exception(exception);\n } finally {\n this._setLoading(stateManager, false);\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 const state = stateManager.state;\n try {\n this._setLoading(stateManager, true);\n const successful = await favouritePackage(packageid, favourite, this.options.contextid);\n if (!successful) {\n return;\n }\n stateManager.setReadOnly(false);\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 } finally {\n this._setLoading(stateManager, false);\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","_setLoading","stateManager","loading","isReadonly","readonly","setReadOnly","results","index","entries","result","packages","count","total","exception","Notification","searchPackages","packageid","favourite","pkg","get","isfavourite","favourites","isFirstPage","isLastPage","Math","floor","existsOnePackage","changePage"],"mappings":"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"} \ 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 'qtype_questionpy/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 * Sets the `loading` property.\n *\n * It only communicates changes to the watchers if the `StateManager` is currently readonly.\n *\n * @param {StateManager} stateManager\n * @param {boolean} loading\n * @private\n */\n _setLoading(stateManager, loading) {\n const state = stateManager.state;\n if (state.loading === loading) {\n return;\n }\n const isReadonly = stateManager.readonly;\n if (isReadonly) {\n stateManager.setReadOnly(false);\n }\n state.general.loading = loading;\n if (isReadonly) {\n stateManager.setReadOnly(true);\n }\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 this._setLoading(stateManager, 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 stateManager.setReadOnly(true);\n } catch (exception) {\n await Notification.exception(exception);\n } finally {\n this._setLoading(stateManager, false);\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 const state = stateManager.state;\n try {\n this._setLoading(stateManager, true);\n const successful = await favouritePackage(packageid, favourite, this.options.contextid);\n if (!successful) {\n return;\n }\n stateManager.setReadOnly(false);\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 } finally {\n this._setLoading(stateManager, false);\n }\n }\n\n async packageUploaded(stateManager) {\n await this.changeSort(stateManager, 'date', 'desc');\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","_setLoading","stateManager","loading","isReadonly","readonly","setReadOnly","results","index","entries","result","packages","count","total","exception","Notification","searchPackages","packageid","favourite","pkg","get","isfavourite","favourites","isFirstPage","isLastPage","Math","floor","existsOnePackage","changePage","changeSort"],"mappings":"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"} \ No newline at end of file diff --git a/amd/src/package_search/components/area.js b/amd/src/package_search/components/area.js index 6e604993..7e5d3465 100644 --- a/amd/src/package_search/components/area.js +++ b/amd/src/package_search/components/area.js @@ -21,6 +21,7 @@ import Component from 'qtype_questionpy/package_search/component'; import Container from 'qtype_questionpy/package_search/components/container'; +import UploadButton from 'qtype_questionpy/package_search/components/upload'; import SearchBar from 'qtype_questionpy/package_search/components/search_bar'; export default class extends Component { @@ -38,6 +39,12 @@ export default class extends Component { name: "search_bar", reactive: descriptor.reactive, }); + // Register upload button. + new UploadButton({ + element: this.getElement('[data-for="upload-button"]'), + name: "upload_button", + reactive: descriptor.reactive, + }); // Register package container. // TODO: register component inside mustache template. new Container({ diff --git a/amd/src/package_search/components/package.js b/amd/src/package_search/components/package.js index d48329eb..8d56136c 100644 --- a/amd/src/package_search/components/package.js +++ b/amd/src/package_search/components/package.js @@ -33,6 +33,8 @@ export default class extends Component { this.category = descriptor.category; this.selectors = { FAVOURITE_BUTTON: '[data-for="favourite-button"]', + DOWNLOAD_BUTTON: '[data-for="download-button"]', + VERSION_SELECTION: '.qpy-version-selection', }; } @@ -44,6 +46,20 @@ export default class extends Component { this.addEventListener(this.getElement(this.selectors.FAVOURITE_BUTTON), "click", () => { this.reactive.dispatch("favourite", this.packageid, !this.isFavourite()); }); + + this.addEventListener(this.getElement(this.selectors.VERSION_SELECTION), "change", () => { + this.setUpDownloadButton(); + }); + + this.setUpDownloadButton(); + } + + setUpDownloadButton() { + const selection = this.getElement(this.selectors.VERSION_SELECTION); + const option = selection.options[selection.selectedIndex]; + const button = this.getElement(this.selectors.DOWNLOAD_BUTTON); + button.classList.toggle("d-none", !option.hasAttribute("data-is-mine")); + button.href = option.dataset.fileurl; } async favouriteChanged() { diff --git a/amd/src/package_search/components/upload.js b/amd/src/package_search/components/upload.js new file mode 100644 index 00000000..d146c353 --- /dev/null +++ b/amd/src/package_search/components/upload.js @@ -0,0 +1,61 @@ +/* + * 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/upload + */ + +import Component from 'qtype_questionpy/package_search/component'; + +import ModalForm from 'core_form/modalform'; +import * as strings from 'core/str'; + +export default class extends Component { + stateReady() { + this.addEventListener(this.element, "click", this.openUploadForm); + } + + /** + * + * @param {MouseEvent} event + */ + openUploadForm(event) { + const element = event.target; + const modalForm = new ModalForm({ + formClass: "qtype_questionpy\\form\\package_upload", + args: {contextid: this.reactive.options.contextid}, + modalConfig: { + title: strings.get_string("upload_package", "qtype_questionpy"), + }, + saveButtonText: strings.get_string("upload"), + returnFocus: element, + }); + modalForm.addEventListener(modalForm.events.SUBMIT_BUTTON_PRESSED, () => this._packageIsUploading()); + modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, () => this._packageWasUploaded()); + modalForm.show(); + } + + _packageIsUploading() { + // TODO: show loading icon. + } + + _packageWasUploaded() { + this.reactive.dispatch("packageUploaded"); + $(this.reactive.target).find('[data-for="mine-header"]').tab('show'); + } + +} diff --git a/amd/src/package_search/mutations.js b/amd/src/package_search/mutations.js index 51277444..64b4c46f 100644 --- a/amd/src/package_search/mutations.js +++ b/amd/src/package_search/mutations.js @@ -221,4 +221,8 @@ export default class { this._setLoading(stateManager, false); } } + + async packageUploaded(stateManager) { + await this.changeSort(stateManager, 'date', 'desc'); + } } diff --git a/classes/api/api.php b/classes/api/api.php index e74695d7..a98c8e03 100644 --- a/classes/api/api.php +++ b/classes/api/api.php @@ -224,20 +224,22 @@ public function score_attempt(string $packagehash, string $questionstate, string } /** - * Get the Package information from the server. + * Get a {@see package_raw} from a file. * - * @param string $filename * @param string $filepath - * @return http_response_container + * @return package_raw * @throws moodle_exception */ - public static function package_extract_info(string $filename, string $filepath): http_response_container { - $curlfile = curl_file_create($filepath, $filename); + public static function extract_package_info(string $filepath): package_raw { + $connector = connector::default(); + $data = [ - 'package' => $curlfile, + 'package' => curl_file_create($filepath), ]; - $connector = connector::default(); - return $connector->post("/package-extract-info", $data); + + $response = $connector->post("/package-extract-info", $data); + $response->assert_2xx(); + return array_converter::from_array(package_raw::class, $response->get_data()); } /** diff --git a/classes/external/remove_packages.php b/classes/external/remove_packages.php index fcf46617..cb5a275a 100644 --- a/classes/external/remove_packages.php +++ b/classes/external/remove_packages.php @@ -49,16 +49,25 @@ public static function execute_parameters(): external_function_parameters { * @throws moodle_exception */ public static function execute(): array { - global $DB; + global $DB, $USER; $transaction = $DB->start_delegated_transaction(); // Only delete package versions that were not uploaded by a user. + $versions = package_version::get_records(['userid' => null]); + $DB->delete_records('qtype_questionpy_package'); + $DB->delete_records('qtype_questionpy_pkgversion'); + $DB->delete_records('qtype_questionpy_tags'); + $DB->delete_records('qtype_questionpy_language'); + /* + $DB->delete_records('qtype_questionpy_lastused'); + //$versions = package_version::get_records(); foreach ($versions as $version) { $version->delete(); + $version->delete($USER->id); } - + */ $transaction->allow_commit(); return [ diff --git a/classes/external/search_packages.php b/classes/external/search_packages.php index 7cba6837..c5ebe3b0 100644 --- a/classes/external/search_packages.php +++ b/classes/external/search_packages.php @@ -30,6 +30,7 @@ use external_value; use invalid_parameter_exception; use moodle_exception; +use moodle_url; use qtype_questionpy\localizer; /** @@ -176,20 +177,28 @@ private static function get_tags_and_versions(array $packageids, array $contexti ]; } - // Get relevant package versions. + /* + Get relevant package versions. + TODO: if a package was uploaded by a user and also was uploaded by another person in the same context + and/or by the server, prefer the user uploaded package. + */ $versionsraw = $DB->get_records_sql(" - SELECT id, packageid, hash, version, userid + SELECT id, packageid, contextid, hash, version, userid, filename FROM {qtype_questionpy_pkgversion} WHERE packageid {$inpackagesql} AND (userid IS NULL OR userid {$inusersql} OR contextid {$incontextsql}) ", array_merge($inpackageparams, $incontextparams, $inuserparams)); $versions = []; foreach ($versionsraw as $version) { + $ismine = $version->userid === $USER->id; + $fileurl = $ismine ? moodle_url::make_pluginfile_url($version->contextid, 'qtype_questionpy', 'package', 0, '/', + $version->filename)->out() : null; $versions[$version->packageid][] = [ 'id' => $version->id, 'hash' => $version->hash, 'version' => $version->version, - 'ismine' => $version->userid === $USER->id, + 'ismine' => $ismine, + 'fileurl' => $fileurl, ]; } @@ -513,6 +522,7 @@ public static function execute_returns(): external_single_structure { 'hash' => new external_value(PARAM_ALPHANUM), 'version' => new external_value(PARAM_TEXT), 'ismine' => new external_value(PARAM_BOOL), + 'fileurl' => new external_value(PARAM_URL), ])), 'author' => new external_value(PARAM_RAW), 'name' => new external_value(PARAM_TEXT), diff --git a/classes/form/package_upload.php b/classes/form/package_upload.php index b6653b0f..71e1030e 100644 --- a/classes/form/package_upload.php +++ b/classes/form/package_upload.php @@ -26,90 +26,109 @@ defined('MOODLE_INTERNAL') || die; +use context; +use core_form\dynamic_form; use moodle_exception; -use qtype_questionpy\localizer; -use qtype_questionpy\package\package; -use qtype_questionpy\package\package_version; +use moodle_url; +use qtype_questionpy\api\api; require_once($CFG->libdir . "/formslib.php"); /** - * QuestionPy package upload form definition. + * Dynamic QuestionPy package upload form. * * @copyright 2022 Alexander Schmitz, TU Berlin, innoCampus - www.questionpy.org * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class package_upload extends \moodleform { +class package_upload extends dynamic_form { /** - * Build the form definition. + * Builds the form definition. * * @throws moodle_exception */ protected function definition() { - global $OUTPUT; - $mform = $this->_form; - $contextid = $this->_customdata['contextid']; - - // Create group which contains selectable QuestionPy packages. - $group = []; - - $languages = localizer::get_preferred_languages(); - $versions = package_version::get_records(['contextid' => $contextid]); - - foreach ($versions as $version) { - // Get localized package texts. - $packagearray = package::get_by_version($version->id)->as_localized_array($languages); - $group[] = $mform->createElement('text', 'questionpy_package_hash', - $OUTPUT->render_from_template('qtype_questionpy/package', $packagearray), - '', '' - ); - } - $mform->addGroup($group, 'questionpy_package_container', '', '
'); - $mform->setType('questionpy_package_container', PARAM_TEXT); + $mform->addElement('hidden', 'contextid'); + $mform->setType('contextid', PARAM_INT); $maxkb = get_config('qtype_questionpy', 'max_package_size_kb'); $mform->addElement('filepicker', 'qpy_package', get_string('file'), null, ['maxbytes' => $maxkb * 1024, 'accepted_types' => ['.qpy']]); + $mform->addRule('qpy_package', null, 'required'); + } + + /** + * @throws moodle_exception + */ + protected function get_context_for_dynamic_submission(): context { + $contextid = $this->optional_param('contextid', null, PARAM_INT); + return context::instance_by_id($contextid); + } - $this->add_action_buttons(); + /** + * @throws moodle_exception + */ + protected function check_access_for_dynamic_submission(): void { + $context = $this->get_context_for_dynamic_submission(); + require_capability('qtype/questionpy:uploadpackages', $context); } /** - * Load in existing data as form defaults. Usually new entry defaults are stored directly in - * form definition (new entry form); this function is used to load in data where values - * already exist and data is being edited (edit entry form). - * - * note: $slashed param removed - * - * @param array $data - * @param array $files - * @return array $errors + * @throws moodle_exception */ - public function validation($data, $files) { - $errors = parent::validation($data, $files); - if (!self::file_uploaded($data['qpy_package'])) { - $errors["qpy_package"] = get_string('formerror_noqpy_package', 'qtype_questionpy'); + public function process_dynamic_submission(): void { + $contextid = $this->optional_param('contextid', null, PARAM_INT); + + // Get file storage. + $filestorage = get_file_storage(); + + // Get filename. + $filename = $this->get_new_filename('qpy_package'); + $filename = $filestorage->get_unused_filename($contextid, 'qtype_questionpy', 'package', 0, '/', $filename); + if (strlen($filename) > 255) { + throw new moodle_exception('file_name_too_long_error', 'qtype_questionpy'); + } + + // Save file inside current file area. + $file = $this->save_stored_file('qpy_package', $contextid, 'qtype_questionpy', 'package', 0, '/', $filename); + if (!$file) { + throw new moodle_exception('cannotuploadfile'); + } + + try { + // Store the package in the database. + $path = $filestorage->get_file_system()->get_local_path_from_storedfile($file); + $rawpackage = api::extract_package_info($path); + $rawpackage->store($contextid, true, $filename); + } catch (moodle_exception $exception) { + $file->delete(); + throw $exception; } - return $errors; } /** - * Checks to see if a file has been uploaded. - * - * @param string $draftitemid The draft id - * @return bool True if files exist, false if not. + * @throws moodle_exception */ - public static function file_uploaded($draftitemid) { - $draftareafiles = file_get_drafarea_files($draftitemid); - do { - $draftareafile = array_shift($draftareafiles->list); - } while ($draftareafile !== null && $draftareafile->filename == '.'); - if ($draftareafile === null) { - return false; + public function set_data_for_dynamic_submission(): void { + $contextid = $this->optional_param('contextid', null, PARAM_INT); + + // Set the context id to the course context id if the context is part of a course. + $context = context::instance_by_id($contextid); + if ($coursecontext = $context->get_course_context(false)) { + $contextid = $coursecontext->id; } - return true; + + $this->set_data([ + 'contextid' => $contextid, + ]); + } + + /** + * @throws moodle_exception + */ + protected function get_page_url_for_dynamic_submission(): moodle_url { + return $this->get_context_for_dynamic_submission()->get_url(); } } diff --git a/classes/package/package_raw.php b/classes/package/package_raw.php index bef6793f..d9eebcab 100644 --- a/classes/package/package_raw.php +++ b/classes/package/package_raw.php @@ -19,6 +19,7 @@ use moodle_exception; use qtype_questionpy\array_converter\array_converter; use qtype_questionpy\array_converter\converter_config; +use stored_file; defined('MOODLE_INTERNAL') || die; @@ -79,10 +80,11 @@ public function __construct(string $hash, string $shortname, string $namespace, * * @param int $contextid * @param bool $withuserid + * @param string|null $filename * @return int the ID of the inserted record in the DB * @throws moodle_exception */ - public function store(int $contextid = 0, bool $withuserid = true): int { + public function store(int $contextid = 0, bool $withuserid = true, string $filename = null): int { global $DB, $USER; $transaction = $DB->start_delegated_transaction(); @@ -134,13 +136,13 @@ public function store(int $contextid = 0, bool $withuserid = true): int { } } else { // Package does already exist - check if the version also exists. - $pkgversionid = $DB->get_field('qtype_questionpy_pkgversion', 'id', [ + $pkgversion = $DB->get_record('qtype_questionpy_pkgversion', [ 'packageid' => $packageid, 'version' => $this->version, ]); - if ($pkgversionid) { - return $pkgversionid; + if ($pkgversion && $pkgversion->hash !== $this->hash) { + throw new moodle_exception('same_version_different_hash_error', 'qtype_questionpy'); } } // Add the package version. @@ -152,6 +154,7 @@ public function store(int $contextid = 0, bool $withuserid = true): int { 'version' => $this->version, 'timecreated' => $timestamp, 'userid' => $withuserid ? $USER->id : null, + 'filename' => $filename, ]); $transaction->allow_commit(); diff --git a/classes/package/package_version.php b/classes/package/package_version.php index 5141bdcc..76ca30c2 100644 --- a/classes/package/package_version.php +++ b/classes/package/package_version.php @@ -17,6 +17,7 @@ namespace qtype_questionpy\package; use moodle_exception; +use moodle_url; use qtype_questionpy\array_converter\array_converter; /** @@ -50,6 +51,11 @@ class package_version { */ public string $version; + /** + * @var string|null path name hash + */ + public ?string $pathnamehash; + /** * Retrieves a package version by its id. * @@ -94,18 +100,38 @@ public static function get_records(?array $conditions = null): array { return $packages; } + public function download_package_file() { + if (is_null($this->pathnamehash) || ($file = get_file_storage()->get_file_by_hash($this->pathnamehash))) { + throw new moodle_exception('storedfilecannotread'); + } + return moodle_url::make_pluginfile_url( + $file->get_contextid(), + $file->get_component(), + $file->get_filearea(), + $file->get_itemid(), + $file->get_filepath(), + $file->get_filename(), + true + ); + } + /** * Deletes the package version from the database. * If the package has only one version, the package related data is also deleted. * + * @param int|null $userid * @throws moodle_exception */ - public function delete(): void { + public function delete(int $userid = null): void { global $DB; $transaction = $DB->start_delegated_transaction(); $versioncount = $DB->count_records('qtype_questionpy_pkgversion', ['packageid' => $this->packageid]); - $DB->delete_records('qtype_questionpy_pkgversion', ['hash' => $this->hash, 'packageid' => $this->packageid]); + $DB->delete_records('qtype_questionpy_pkgversion', [ + 'hash' => $this->hash, + 'packageid' => $this->packageid, + 'userid' => $userid, + ]); if ($versioncount === 1) { // Only one package version exists, therefore we also delete package related data. diff --git a/db/install.xml b/db/install.xml index 8b5ddc0b..8c1d7a19 100644 --- a/db/install.xml +++ b/db/install.xml @@ -63,6 +63,7 @@ + diff --git a/edit_questionpy_form.php b/edit_questionpy_form.php index a53560e8..3b07c3b1 100644 --- a/edit_questionpy_form.php +++ b/edit_questionpy_form.php @@ -61,8 +61,6 @@ protected function definition_package_selection(MoodleQuickForm $mform): void { 'questionpy_package_container', get_string('selection_required', 'qtype_questionpy'), 'required' ); - - $mform->addElement('button', 'uploadlink', 'QPy Package upload form', $uploadlink); } /** diff --git a/lang/en/qtype_questionpy.php b/lang/en/qtype_questionpy.php index 05651cef..8b710522 100644 --- a/lang/en/qtype_questionpy.php +++ b/lang/en/qtype_questionpy.php @@ -53,7 +53,9 @@ $string['server_info_requests_in_queue'] = 'Requests in queue'; // Package upload. -$string['formerror_noqpy_package'] = 'Selected file must be of type .qpy'; +$string['upload_package'] = 'Upload a package'; +$string['file_name_too_long_error'] = 'The filename "{a}" is too long.'; +$string['same_version_different_hash_error'] = 'A package with the same version but different hash already exists.'; // Package selection. $string['selection_title'] = 'Select QuestionPy Package'; diff --git a/lib.php b/lib.php index a3d9da2e..79043e29 100644 --- a/lib.php +++ b/lib.php @@ -23,20 +23,49 @@ */ /** - * Checks file access for QuestionPy questions. - * @package qtype_questionpy - * @category files + * Serve files from the QuestionPy file areas. + * * @param stdClass $course course object * @param stdClass $cm course module object * @param stdClass $context context object * @param string $filearea file area * @param array $args extra arguments - * @param bool $forcedownload whether or not force download + * @param bool $forcedownload whether to force download * @param array $options additional options affecting the file serving * @return bool + * @throws moodle_exception */ -function qtype_questionpy_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=[]) { - global $CFG; - require_once($CFG->libdir . '/questionlib.php'); - question_pluginfile($course, $context, 'qtype_questionpy', $filearea, $args, $forcedownload, $options); +function qtype_questionpy_pluginfile($course, $cm, $context, string $filearea, array $args, + bool $forcedownload, array $options=[]): bool { + global $USER; + + // We currently only store files inside the package file area. + if ($filearea !== 'package') { + return false; + } + + require_login($course, true, $cm); + + // Extract the item id. + $itemid = array_shift($args); + + // Extract the filename and filepath. + $filename = array_pop($args); + $filepath = '/'; + if (!empty($args)) { + $filepath .= implode('/', $args) . '/'; + } + + // Get the file. + $filestorage = get_file_storage(); + $file = $filestorage->get_file($context->id, 'qtype_questionpy', $filearea, $itemid, $filepath, $filename); + + // Check if package was found and uploaded by the current user. + if (!$file || $file->get_userid() !== $USER->id) { + return false; + } + + // Package was found and is accessible by the current user - send it. + send_stored_file($file, DAYSECS, 0, $forcedownload, $options); + return true; } diff --git a/renderer.php b/renderer.php index 4e2afc6a..ba389957 100644 --- a/renderer.php +++ b/renderer.php @@ -118,4 +118,20 @@ public function package_upload_link(context $context) { return $this->action_link($link, 'qpy_package_upload', $action, null); } + + /** + * + */ + public function get_file_download_link(string $pathnamehash): moodle_url { + $filestorage = get_file_storage(); + $file = $filestorage->get_file_by_hash($pathnamehash); + return moodle_url::make_pluginfile_url( + $file->get_contextid(), + $file->get_component(), + $file->get_filearea(), + $file->get_itemid(), + $file->get_filepath(), + $file->get_filename(), + ); + } } diff --git a/templates/package/package_selection.mustache b/templates/package/package_selection.mustache index 25027dfc..d8005ac3 100644 --- a/templates/package/package_selection.mustache +++ b/templates/package/package_selection.mustache @@ -25,7 +25,8 @@ Data attributes required for JS: * data-for="favouite-button", - * data-is-favourite + * data-is-favourite, + * data-for="download-button" Context variables required for this template: * id - QuestionPy package id, @@ -46,9 +47,9 @@ "name": "ExamplePackage", "description": "This describes the package ExamplePackage.", "icon": "https://picsum.photos/48/48?grayscale", - "versions": [{"hash": "example_hash", "version": "0.1.0"}], - "url": "https://example.com", "selected": true, + "versions": [{"hash": "example_hash", "version": "0.1.0", "ismine": true, "fileurl": "www.example.com/package.qpy"}], + "url": "https://example.com" "isfavourite": false, "contextid": 0 } @@ -59,7 +60,7 @@
diff --git a/templates/package_search/area.mustache b/templates/package_search/area.mustache index a493202f..1eb4b311 100644 --- a/templates/package_search/area.mustache +++ b/templates/package_search/area.mustache @@ -39,9 +39,13 @@ } }}
- -
- +
+ +
+ +
+ +
diff --git a/upload_view.php b/upload_view.php deleted file mode 100644 index 17c20fb6..00000000 --- a/upload_view.php +++ /dev/null @@ -1,104 +0,0 @@ -. - -/** - * Upload view for the QuestionPy packages. - * - * @package qtype_questionpy - * @copyright 2022 Alexander Schmitz, TU Berlin, innoCampus - www.questionpy.org - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -use core\output\notification; -use qtype_questionpy\api\api; -use qtype_questionpy\array_converter\array_converter; -use qtype_questionpy\package\package_raw; - -require_once(dirname(__FILE__) . '/../../../config.php'); - -global $PAGE; -$courseid = optional_param('courseid', 2, PARAM_INT); - -require_login($courseid); -$context = context_course::instance($courseid); - -require_capability('qtype/questionpy:uploadpackages', $context); -$PAGE->set_context($context); -$thisurl = new moodle_url('/question/type/questionpy/upload_view.php', ['courseid' => $courseid]); -$PAGE->set_url($thisurl); -$PAGE->set_pagelayout('popup'); -$PAGE->set_title(get_string('pluginname', 'qtype_questionpy')); -$output = $PAGE->get_renderer('core'); -echo $output->header(get_string('pluginname', 'qtype_questionpy')); - -$customdata = [ - 'courseid' => $courseid, - 'contextid' => $context->id, -]; -$mform = new \qtype_questionpy\form\package_upload(null, $customdata); -$fs = get_file_storage(); - -if ($mform->is_cancelled()) { - // This redirect shows a warning, but should be ok (see: https://tracker.moodle.org/browse/CONTRIB-5857). - redirect(new moodle_url('/course/view.php', ['id' => $courseid]), 'Upload form cancelled.'); -} else if ($fromform = $mform->get_data()) { - // File has been submitted in the form. Check if a file with this name already exists in this context. - $filename = $mform->get_new_filename('qpy_package'); - if ($fs->file_exists($context->id, 'qtype_questionpy', 'package', - 0, '/', $filename)) { - redirect($thisurl, "File with this name already exists in this context.", 500, - notification::NOTIFY_WARNING); - } - - // Store file locally with the File API. - $storedfile = $mform->save_stored_file('qpy_package', $context->id, 'qtype_questionpy', - 'package', 0, '/', $filename); - $package = null; - - try { - // Try to post the file to the server. - $filesystem = $fs->get_file_system(); - $path = $filesystem->get_local_path_from_storedfile($storedfile, true); - $response = api::package_extract_info($filename, $path); - - if ($response->code != 201) { - throw new moodle_exception('serverconnection', 'error', '', null, - "Server response code: $response->code \n Server response: {$response->get_data()}"); - } - - // Save package info in the DB. - $package = array_converter::from_array(package_raw::class, $response->get_data()); - $package->store($context->id); - - } catch (moodle_exception $e) { - // If anything goes wrong while saving the file, rollback. - $storedfile->delete(); - $errormessage = $e->getMessage(); - if ($package) { - try { - $package->delete_from_db(); - } catch (moodle_exception $ex) { - $errormessage = $errormessage . $ex->getMessage(); - } - } - notice($errormessage, $thisurl); - } - notice("Package saved.", $thisurl); -} else { - $mform->display(); -} - -echo $output->footer(); diff --git a/version.php b/version.php index 4ce53cc5..8d199bb3 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'qtype_questionpy'; -$plugin->version = 2024021900; +$plugin->version = 2025031401; $plugin->requires = 2022041901; $plugin->maturity = MATURITY_ALPHA; $plugin->release = '0.1';