{"version":3,"file":"sync.min.js","sources":["https:\/\/moodle.sonsbeekmedia.nl\/caie_test\/mod\/teachingtools\/amd\/src\/names\/sync.js"],"sourcesContent":["\/\/ This file is part of Moodle - http:\/\/moodle.org\/\n\/\/\n\/\/ Moodle is free software: you can redistribute it and\/or modify\n\/\/ it under the terms of the GNU General Public License as published by\n\/\/ the Free Software Foundation, either version 3 of the License, or\n\/\/ (at your option) any later version.\n\/\/\n\/\/ Moodle is distributed in the hope that it will be useful,\n\/\/ but WITHOUT ANY WARRANTY; without even the implied warranty of\n\/\/ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\/\/ GNU General Public License for more details.\n\/\/\n\/\/ You should have received a copy of the GNU General Public License\n\/\/ along with Moodle. If not, see .\n\n\/**\n * Manage the importing and exporting of game data.\n *\n * @module mod_teachingtools\/names\/sync.js\n * @class Sync\n * @copyright 2023 Sonsbeekmedia\n * @author Bas Brands \n * @license http:\/\/www.gnu.org\/copyleft\/gpl.html GNU GPL v3 or later\n *\/\n\nimport * as Toast from 'core\/toast';\nimport StoreConfigElement from 'mod_teachingtools\/names\/store_config_element';\nimport {get_string as getString} from 'core\/str';\nimport BackGroundElement from 'mod_teachingtools\/names\/background_element';\nimport ToolData from 'mod_teachingtools\/tooldata';\nimport Notification from 'core\/notification';\n\nexport default class Sync {\n\n constructor(names, rootElement, app) {\n this.names = names;\n this.rootElement = rootElement;\n this.storeConfig = new StoreConfigElement(names, rootElement);\n this.backGroundElement = new BackGroundElement(this.names, this.rootElement);\n this.sync = this.rootElement.querySelector('[data-region=\"sync\"]');\n this.isTeacher = this.rootElement.dataset.teacher;\n this.app = app;\n this.reloadApps = ['openai'];\n this.hasfeed = false;\n const feedurl = this.rootElement.dataset.feedurl;\n if (feedurl && feedurl == 1) {\n this.hasfeed = true;\n }\n this.Toast = Toast;\n this.addEventListeners();\n }\n\n \/**\n * Change toast to console.log\n * @param {boolean} fake - True to change to console.log, false to change to Toast.\n *\/\n changeToast(fake) {\n if (fake) {\n this.Toast = {\n add: (message, options) => {\n window.console.log(message);\n window.console.log(options);\n },\n };\n } else {\n this.Toast = Toast;\n }\n }\n\n update(names) {\n this.names.names = names;\n }\n\n addEventListeners() {\n if (!this.sync) {\n return;\n }\n const exportButton = document.querySelector('[data-action=\"export\"]');\n if (!exportButton) {\n return;\n }\n const importFileinput = document.querySelector('[data-action=\"importupload\"]');\n const importFileButton = importFileinput.closest('.custom-upload-wrapper').querySelector('.btn');\n\n if (exportButton.dataset.initialized == 1) {\n return;\n }\n exportButton.dataset.initialized = 1;\n\n exportButton.addEventListener('click', () => {\n this.exportNames();\n });\n\n importFileinput.addEventListener('change', (e) => {\n const file = e.target.files[0];\n if (!file) {\n return;\n }\n const reader = new FileReader();\n reader.addEventListener('load', async() => {\n const fileInfo = {\n content: reader.result,\n name: file.name,\n type: file.type,\n };\n this.importItems(fileInfo);\n });\n reader.readAsText(file);\n \/\/ Reset the input.\n e.target.value = '';\n });\n\n \/\/ On focus on the inmportFileinput, add the focus class to the button, on blur remove it.\n importFileinput.addEventListener('focus', () => {\n importFileButton.classList.add('active');\n });\n importFileinput.addEventListener('blur', () => {\n importFileButton.classList.remove('active');\n });\n document.addEventListener('generatesharetoken', async() => {\n const config = await this.getToolData();\n const configString = JSON.stringify(config);\n const result = await ToolData.generateShareToken(this.rootElement.dataset.cmid, this.app, configString);\n if (result.success !== 1) {\n this.Toast.add(\n getString('generatesharetokenfail', 'mod_teachingtools'), {type: 'danger', delay: 10000, closeButton: true}\n );\n return;\n } else {\n const input = document.querySelector('[data-region=\"shareurlinput\"]');\n const shareUrl = input.dataset.shareurl;\n input.value = `${shareUrl}?token=${result.sharetoken}`;\n\n const pptlink = document.querySelector('[data-region=\"pptlink\"]');\n if (pptlink) {\n const pptUrl = pptlink.dataset.ppturl;\n pptlink.href = `${pptUrl}?token=${result.sharetoken}`;\n pptlink.classList.remove('d-none');\n }\n\n const pwalink = document.querySelector('[data-region=\"pwalink\"]');\n if (pwalink) {\n const pwaUrl = pwalink.dataset.pwaurl;\n pwalink.href = `${pwaUrl}?token=${result.sharetoken}`;\n pptlink.classList.remove('d-none');\n }\n }\n });\n }\n\n getFeedData = async() => {\n let feedstatus = false;\n if (!this.hasfeed) {\n feedstatus = false;\n } else {\n const cmid = this.rootElement.dataset.cmid;\n feedstatus = await ToolData.getFeed(cmid).then(async(result) => {\n if (result.success !== 1) {\n return 'feederror';\n }\n if (result.feed) {\n const fileInfo = {\n content: result.feed,\n name: 'feed.txt',\n type: 'text\/plain',\n };\n this.changeToast(true);\n await this.importItems(fileInfo);\n this.changeToast(false);\n }\n return false;\n }).catch(Notification.exception);\n }\n return new Promise((resolve) => {\n resolve(feedstatus);\n });\n };\n\n \/**\n * Get the compatibility of the dataset.\n * @return {string} The compatibility.\n *\/\n getCompatibility() {\n const compatibility = this.sync.dataset.compatibility;\n if (compatibility === 'full') {\n return 'itemlist';\n } else if (compatibility === 'pipeseparated') {\n return 'itemlistResponse';\n } else if (compatibility === 'images') {\n return 'itemlistImages';\n }\n return 'itemlistCustom';\n }\n\n \/**\n * Check if the dataset is compatible with the current app. itemListCustom is never compatible.\n * @param {object} data The data to check.\n * @return {boolean} True if compatible, false if not.\n *\/\n isCompatible(data) {\n const compatibility = this.getCompatibility();\n if (data[compatibility] === undefined) {\n return false;\n }\n if (data.itemListCustom !== undefined) {\n return false;\n }\n return true;\n }\n\n \/**\n * Return the names as a json file.\n *\/\n async exportNames() {\n const data = await this.getToolData();\n\n \/\/ Create a date stamp in the following format: \"at 08:25 on 5 July 2023\"\n const date = new Date();\n const datestamp = date.toLocaleString('en-GB', {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n });\n\n let blobContent = await getString('filewarning', 'mod_teachingtools', datestamp);\n blobContent += '\\n';\n blobContent += await getString('filewarning2', 'mod_teachingtools');\n blobContent += '\\n\\n---\\n';\n \/\/ Add the data.\n blobContent += JSON.stringify(data);\n\n \/\/ Prepare the download.\n const blob = new Blob([blobContent], {type: 'text\/plain'});\n const url = window.URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n const dateshort = new Date().toISOString().slice(0, 10);\n a.download = `${this.app}-${dateshort}.txt`;\n a.click();\n window.URL.revokeObjectURL(url);\n\n this.Toast.add(`File ${this.app}-${dateshort}.txt downloaded`, {type: 'success'});\n }\n\n \/**\n * Get the tool data. (migrated from exportNames)\n * @return {object} - The tool data.\n *\/\n async getToolData() {\n const data = {\n tool: this.app,\n version: this.sync.dataset.version\n };\n\n const compatibility = this.getCompatibility();\n data[compatibility] = this.names.names;\n\n data.storeconfig = [];\n data.imagefields = [];\n\n let customBackground = await this.storeConfig.getCustomData('backgroundimage');\n\n if (customBackground === null) {\n customBackground = '';\n }\n data.background = customBackground;\n\n \/\/ Get the custom storeconfig fields.\n if (this.sync.dataset.storeconfig !== undefined) {\n const storeconfig = [];\n const fields = this.sync.dataset.storeconfig.split(';');\n\n for (const index in fields) {\n const field = fields[index];\n const value = await this.storeConfig.getCustomData(field);\n if (value !== null) {\n storeconfig[field] = value;\n }\n }\n data.storeconfig = {...storeconfig};\n }\n\n \/\/ Get the imagefields.\n if (this.sync.dataset.imagefields !== undefined) {\n const imagefields = [];\n const fields = this.sync.dataset.imagefields.split(';');\n\n for (const index in fields) {\n const field = fields[index];\n imagefields[field] = await this.getFileContent(field);\n }\n data.imagefields = {...imagefields};\n }\n\n return data;\n }\n\n \/**\n * Get the file content from the URL and add it as a blob to the file object.\n * @param {string} field - The field to get the file content from.\n *\/\n async getFileContent(field) {\n const file = await this.storeConfig.getCustomData(field);\n if (file === null) {\n return '';\n }\n \/\/ Check if the file is an Object.\n if (typeof file === 'object') {\n \/\/ Check if the file.content is an URL that starts with http and not with data.\n if (file.content.startsWith('http')) {\n const imageUrl = file.content;\n file.blob = '';\n \/\/ Download the image.\n const response = await fetch(imageUrl);\n const blob = await response.blob();\n const reader = await this.readBlob(blob);\n file.content = reader;\n }\n return file;\n } else {\n const json = JSON.parse(file);\n return json;\n }\n }\n\n \/**\n * Read the blob and return the content.\n * @param {object} blob - The blob to read.\n *\/\n async readBlob(blob) {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.addEventListener('loadend', () => {\n resolve(reader.result);\n });\n reader.addEventListener('error', () => {\n reject(reader.error);\n });\n reader.readAsDataURL(blob);\n });\n }\n\n \/**\n * Import the items from a json file.\n *\n * Validate the file first, then import.\n * @param {object} fileInfo - The file info from the FileReader.\n *\/\n async importItems(fileInfo) {\n \/\/ Validate the file.\n if (fileInfo.type !== 'text\/plain') {\n this.Toast.add(getString('invalidfiletype', 'mod_teachingtools'), {type: 'danger', delay: 10000, closeButton: true});\n return;\n }\n\n \/\/ Get the JSON data after the ---.\n const json = fileInfo.content.split('---')[1];\n \/\/ Parse the JSON.\n const data = JSON.parse(json);\n\n const compatibility = this.getCompatibility();\n \/\/ Validate the data.\n if (!data || !data[compatibility] || !data.tool) {\n this.Toast.add(getString('invalidfilecontent', 'mod_teachingtools'), {type: 'danger', delay: 10000, closeButton: true});\n return;\n }\n\n if (data.tool !== this.app && !this.isCompatible(data)) {\n this.Toast.add(getString('invalidtool', 'mod_teachingtools'), {type: 'danger', delay: 10000, closeButton: true});\n return;\n }\n if (data.version !== this.sync.dataset.version) {\n this.Toast.add(getString('invalidversion', 'mod_teachingtools'), {type: 'danger', delay: 10000, closeButton: true});\n }\n\n \/\/ Import the background image.\n await this.importBackground(data.background);\n\n \/\/ Restore the custom storeconfig fields.\n await this.importStoreConfigFields(data.storeconfig);\n\n \/\/ Import the names, stop if this fails.\n if (!this.importNames(data[compatibility])) {\n return;\n }\n\n \/\/ Restore the imagefields.\n if (this.isCompatible(data)) {\n if (data.tool === this.app) {\n await this.importImageFields(data.imagefields);\n } else {\n await this.importForeignImages(data.imagefields);\n }\n }\n\n \/\/ In case the tool is listed in the reloadApps array, reload the app.\n if (this.reloadApps.includes(this.app)) {\n window.location.reload();\n }\n\n \/\/ Reset the game.\n this.names.resetGame();\n }\n\n \/**\n * Import the background image.\n * @param {string} backgroundBlob - The background image blob.\n * @return {boolean} - True if the background image was imported, false otherwise.\n *\/\n async importBackground(backgroundBlob) {\n if (backgroundBlob === '') {\n this.backGroundElement.removeButtonAction();\n return false;\n }\n \/\/ Store the default background image.\n await this.storeConfig.setCustomData('backgroundimage', backgroundBlob);\n const result = await this.backGroundElement.setStoredBackground();\n if (result) {\n this.Toast.add(getString('setbackgroundsuccess', 'mod_teachingtools'),\n {type: 'success', delay: 5000, closeButton: true});\n return true;\n }\n return false;\n }\n\n \/**\n * Import the names array.\n * @param {array} namesArray - The names array.\n * @return {boolean} - True if the names array was imported, false otherwise.\n *\/\n importNames(namesArray) {\n \/\/ Check if data.items is an array.\n if (!Array.isArray(namesArray)) {\n this.Toast.add(getString('invalidfilecontent', 'mod_teachingtools'), {type: 'danger', delay: 10000, closeButton: true});\n return false;\n }\n if (namesArray.length !== 0) {\n this.names.add(namesArray);\n this.names.updateListeners();\n this.Toast.add(getString('setnamessuccess', 'mod_teachingtools'),\n {type: 'success', delay: 10000, closeButton: true});\n }\n return true;\n }\n\n \/**\n * Import the custom storeconfig fields.\n * @param {array} storeconfig - The storeconfig array.\n * @return {Promise} - The promise when the storeconfig fields are imported.\n *\/\n async importStoreConfigFields(storeconfig) {\n if (!this.sync.dataset.storeconfig) {\n return Promise.resolve();\n }\n const fields = this.sync.dataset.storeconfig.split(';');\n for (const field in storeconfig) {\n if (!fields.includes(field)) {\n continue;\n }\n await this.storeConfig.setCustomData(field, storeconfig[field]);\n }\n return Promise.resolve();\n }\n\n \/**\n * Import the imagefields.\n * @param {object} imagefields - The imagefields array.\n * @return {Promise} - The promise when the imagefields are imported.\n *\/\n async importImageFields(imagefields) {\n if (imagefields && this.sync.dataset.imagefields !== undefined) {\n const fields = this.sync.dataset.imagefields.split(';');\n for (const field in imagefields) {\n if (!fields.includes(field)) {\n continue;\n }\n await this.storeConfig.setCustomData(field, imagefields[field]);\n }\n }\n return Promise.resolve();\n }\n\n \/**\n * EXPERIMENTAL! Import the imagefields.\n * @param {object} imagefields - The imagefields array.\n * @return {Promise} - The promise when the imagefields are imported.\n *\/\n async importForeignImages(imagefields) {\n \/\/ Turn the imagefields into an array.\n imagefields = Object.values(imagefields);\n\n if (imagefields && this.sync.dataset.imagefields !== undefined) {\n const fields = this.sync.dataset.imagefields.split(';');\n let index = 0;\n for (const field in fields) {\n if (imagefields[index] !== undefined) {\n await this.storeConfig.setCustomData(fields[field], imagefields[field]);\n }\n index++;\n }\n }\n return Promise.resolve();\n }\n\n \/**\n * Read the token config and set it as the app config.\n * @param {string} token - The token to read.\n * @return {Promise} - The promise when the config is set.\n *\/\n async loadToken(token) {\n const result = await ToolData.getShareToken(token, this.rootElement.dataset.cmid);\n if (result.success !== 1) {\n this.Toast.add(getString('tokennotfound', 'mod_teachingtools'), {type: 'danger', delay: 10000, closeButton: true});\n return;\n }\n const fileInfo = {\n content: '---' + result.data,\n name: token,\n type: 'text\/plain',\n };\n this.changeToast(true);\n await this.importItems(fileInfo);\n }\n}"],"names":["constructor","names","rootElement","app","async","feedstatus","this","hasfeed","cmid","dataset","ToolData","getFeed","then","result","success","feed","fileInfo","content","name","type","changeToast","importItems","catch","Notification","exception","Promise","resolve","storeConfig","StoreConfigElement","backGroundElement","BackGroundElement","sync","querySelector","isTeacher","teacher","reloadApps","feedurl","Toast","addEventListeners","fake","add","message","options","window","console","log","update","exportButton","document","importFileinput","importFileButton","closest","initialized","addEventListener","exportNames","e","file","target","files","reader","FileReader","readAsText","value","classList","remove","config","getToolData","configString","JSON","stringify","generateShareToken","input","shareUrl","shareurl","sharetoken","pptlink","pptUrl","ppturl","href","pwalink","pwaUrl","pwaurl","delay","closeButton","getCompatibility","compatibility","isCompatible","data","undefined","itemListCustom","datestamp","Date","toLocaleString","weekday","year","month","day","hour","minute","blobContent","blob","Blob","url","URL","createObjectURL","a","createElement","dateshort","toISOString","slice","download","click","revokeObjectURL","tool","version","storeconfig","imagefields","customBackground","getCustomData","background","fields","split","index","field","getFileContent","startsWith","imageUrl","response","fetch","readBlob","parse","reject","error","readAsDataURL","json","importBackground","importStoreConfigFields","importNames","importImageFields","importForeignImages","includes","location","reload","resetGame","backgroundBlob","removeButtonAction","setCustomData","setStoredBackground","namesArray","Array","isArray","length","updateListeners","Object","values","token","getShareToken"],"mappings":"yoDAkCIA,YAAYC,MAAOC,YAAaC,6BAoHlBC,cACNC,YAAa,KACZC,KAAKC,QAEH,OACGC,KAAOF,KAAKJ,YAAYO,QAAQD,KACtCH,iBAAmBK,kBAASC,QAAQH,MAAMI,MAAKR,MAAAA,YACpB,IAAnBS,OAAOC,cACA,eAEPD,OAAOE,KAAM,OACPC,SAAW,CACbC,QAASJ,OAAOE,KAChBG,KAAM,WACNC,KAAM,mBAELC,aAAY,SACXd,KAAKe,YAAYL,eAClBI,aAAY,UAEd,KACRE,MAAMC,sBAAaC,gBAlBtBnB,YAAa,SAoBV,IAAIoB,SAASC,UAChBA,QAAQrB,2JA3IPJ,MAAQA,WACRC,YAAcA,iBACdyB,YAAc,IAAIC,8BAAmB3B,MAAOC,kBAC5C2B,kBAAoB,IAAIC,4BAAkBxB,KAAKL,MAAOK,KAAKJ,kBAC3D6B,KAAOzB,KAAKJ,YAAY8B,cAAc,6BACtCC,UAAY3B,KAAKJ,YAAYO,QAAQyB,aACrC\/B,IAAMA,SACNgC,WAAa,CAAC,eACd5B,SAAU,QACT6B,QAAU9B,KAAKJ,YAAYO,QAAQ2B,QACrCA,SAAsB,GAAXA,eACN7B,SAAU,QAEd8B,MAAQA,WACRC,oBAOTlB,YAAYmB,WAECF,MADLE,KACa,CACTC,IAAK,CAACC,QAASC,WACXC,OAAOC,QAAQC,IAAIJ,SACnBE,OAAOC,QAAQC,IAAIH,WAIdL,MAIrBS,OAAO7C,YACEA,MAAMA,MAAQA,MAGvBqC,wBACShC,KAAKyB,kBAGJgB,aAAeC,SAAShB,cAAc,8BACvCe,0BAGCE,gBAAkBD,SAAShB,cAAc,gCACzCkB,iBAAmBD,gBAAgBE,QAAQ,0BAA0BnB,cAAc,QAEjD,GAApCe,aAAatC,QAAQ2C,cAGzBL,aAAatC,QAAQ2C,YAAc,EAEnCL,aAAaM,iBAAiB,SAAS,UAC9BC,iBAGTL,gBAAgBI,iBAAiB,UAAWE,UAClCC,KAAOD,EAAEE,OAAOC,MAAM,OACvBF,kBAGCG,OAAS,IAAIC,WACnBD,OAAON,iBAAiB,QAAQjD,gBACtBY,SAAW,CACbC,QAAS0C,OAAO9C,OAChBK,KAAMsC,KAAKtC,KACXC,KAAMqC,KAAKrC,WAEVE,YAAYL,aAErB2C,OAAOE,WAAWL,MAElBD,EAAEE,OAAOK,MAAQ,MAIrBb,gBAAgBI,iBAAiB,SAAS,KACtCH,iBAAiBa,UAAUvB,IAAI,aAEnCS,gBAAgBI,iBAAiB,QAAQ,KACrCH,iBAAiBa,UAAUC,OAAO,aAEtChB,SAASK,iBAAiB,sBAAsBjD,gBACtC6D,aAAe3D,KAAK4D,cACpBC,aAAeC,KAAKC,UAAUJ,QAC9BpD,aAAeH,kBAAS4D,mBAAmBhE,KAAKJ,YAAYO,QAAQD,KAAMF,KAAKH,IAAKgE,iBACnE,IAAnBtD,OAAOC,QAKJ,OACGyD,MAAQvB,SAAShB,cAAc,iCAC\/BwC,SAAWD,MAAM9D,QAAQgE,SAC\/BF,MAAMT,gBAAWU,2BAAkB3D,OAAO6D,kBAEpCC,QAAU3B,SAAShB,cAAc,8BACnC2C,QAAS,OACHC,OAASD,QAAQlE,QAAQoE,OAC\/BF,QAAQG,eAAUF,yBAAgB\/D,OAAO6D,YACzCC,QAAQZ,UAAUC,OAAO,gBAGvBe,QAAU\/B,SAAShB,cAAc,8BACnC+C,QAAS,OACHC,OAASD,QAAQtE,QAAQwE,OAC\/BF,QAAQD,eAAUE,yBAAgBnE,OAAO6D,YACzCC,QAAQZ,UAAUC,OAAO,qBApBxB3B,MAAMG,KACP,mBAAU,yBAA0B,qBAAsB,CAACrB,KAAM,SAAU+D,MAAO,IAAOC,aAAa,QAyDtHC,yBACUC,cAAgB\/E,KAAKyB,KAAKtB,QAAQ4E,oBAClB,SAAlBA,cACO,WACkB,kBAAlBA,cACA,mBACkB,WAAlBA,cACA,iBAEJ,iBAQXC,aAAaC,kBAEmBC,IAAxBD,KADkBjF,KAAK8E,0BAICI,IAAxBD,KAAKE,yCAUHF,WAAajF,KAAK4D,cAIlBwB,WADO,IAAIC,MACMC,eAAe,QAAS,CAC3CC,QAAS,OACTC,KAAM,UACNC,MAAO,OACPC,IAAK,UACLC,KAAM,UACNC,OAAQ,gBAGRC,kBAAoB,mBAAU,cAAe,oBAAqBT,WACtES,aAAe,KACfA,mBAAqB,mBAAU,eAAgB,qBAC\/CA,aAAe,YAEfA,aAAe\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"}