{"version":3,"file":"sync.min.js","sources":["https:\/\/moodle.sonsbeekmedia.nl\/caie_39\/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 = this.sync.querySelector('[data-action=\"export\"]');\n const importFileinput = this.sync.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 if (importFileinput.dataset.initialized == 1) {\n return;\n }\n importFileinput.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 }\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\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 \/\/ 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 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.names = [];\n this.names.names = 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}"],"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","importFileinput","importFileButton","closest","initialized","addEventListener","exportNames","e","file","target","files","reader","FileReader","readAsText","value","classList","remove","getCompatibility","compatibility","isCompatible","data","undefined","itemListCustom","tool","version","storeconfig","imagefields","customBackground","getCustomData","background","fields","split","index","field","getFileContent","datestamp","Date","toLocaleString","weekday","year","month","day","hour","minute","blobContent","JSON","stringify","blob","Blob","url","URL","createObjectURL","a","document","createElement","href","dateshort","toISOString","slice","download","click","revokeObjectURL","startsWith","imageUrl","response","fetch","readBlob","parse","reject","error","readAsDataURL","delay","closeButton","json","importBackground","importStoreConfigFields","importNames","importImageFields","importForeignImages","includes","location","reload","resetGame","backgroundBlob","removeButtonAction","setCustomData","setStoredBackground","namesArray","Array","isArray","length","updateListeners","Object","values"],"mappings":"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\/C,iBACrBlE,KAAKyB,KAAKtB,QAAQ+D,mBACZ\/C,QAAQC,gBAEbmD,OAASvE,KAAKyB,KAAKtB,QAAQ+D,YAAYM,MAAM,SAC9C,MAAME,SAASR,YACXK,OAAOiD,SAAS9C,cAGf1E,KAAKqB,YAAYyG,cAAcpD,MAAOR,YAAYQ,eAErDvD,QAAQC,kCAQK+C,gBAChBA,kBAAiDL,IAAlC9D,KAAKyB,KAAKtB,QAAQgE,YAA2B,OACtDI,OAASvE,KAAKyB,KAAKtB,QAAQgE,YAAYK,MAAM,SAC9C,MAAME,SAASP,YACXI,OAAOiD,SAAS9C,cAGf1E,KAAKqB,YAAYyG,cAAcpD,MAAOP,YAAYO,eAGzDvD,QAAQC,oCAQO+C,iBAEtBA,YAAckE,OAAOC,OAAOnE,oBAEyBL,IAAlC9D,KAAKyB,KAAKtB,QAAQgE,YAA2B,OACtDI,OAASvE,KAAKyB,KAAKtB,QAAQgE,YAAYK,MAAM,SAC\/CC,MAAQ,MACP,MAAMC,SAASH,YACWT,IAAvBK,YAAYM,cACNzE,KAAKqB,YAAYyG,cAAcvD,OAAOG,OAAQP,YAAYO,QAEpED,eAGDtD,QAAQC"}