/* Copyright (c) 2015 William Toohey * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ (function(window, document) { "use strict"; let TAB_SONGS = 0; let TAB_IMAGES = 1; // For multiple Hues on one page let unique = 0; let getAndIncrementUnique = function() { return unique++; } // NOTE: Any packs referenced need CORS enabled or loads fail let packsURL = "https://cdn.0x40hu.es/getRespacks.php"; function Resources(core, huesWin) { this.core = core; this.hasUI = false; this.resourcePacks = []; this.allSongs = []; this.allImages = []; this.enabledSongs = []; this.enabledImages = []; this.progressState = []; this.progressCallback = null; // For songs/images this.listView = null; this.enabledSongList = null; this.enabledImageList = null; this.packView = { pack: null, name: null, creator: null, size: null, desc: null, songCount: null, imageCount: null, songList: null, imageList: null, packButtons: null, totalSongs: null, totalImages: null }; this.packsView = { respackList: null, remoteList: null, loadRemote: null, progressBar: null, progressStatus: null, progressCurrent: null, progressTop: null, progressPercent: null }; this.currentTab = TAB_SONGS; this.unique = getAndIncrementUnique(); this.remotes = null; this.fileInput = null; this.fileParseQueue = []; if(core.settings.defaults.enableWindow) { this.initUI(); huesWin.addTab("RESOURCES", this.root); } } /* Uses HTTP HEAD requests to get the size of all the linked URLs Returns an Promise.all which will resolve to an array of sizes */ Resources.prototype.getSizes = function(urls) { let promises = []; urls.forEach(url => { let p = new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.open("HEAD", url, true); xhr.onreadystatechange = function() { if (this.readyState == this.DONE) { let bytes = parseInt(xhr.getResponseHeader("Content-Length")); resolve(bytes / 1024 / 1024); } }; xhr.onerror = function() { reject(Error(req.status + ": Could not fetch respack at " + url)); }; xhr.send(); }).catch(error => { // Infinitely more user friendly than the error Same Origin gives if(error.code == 1012) { throw Error("Respack at URL " + url + " is restricted. Check CORS."); } else { throw error; } }); promises.push(p); }); return Promise.all(promises); }; // Array of URLs to load, and a callback for when we're done // Preserves order of URLs being loaded Resources.prototype.addAll = function(urls, progressCallback) { if(progressCallback) { this.progressCallback = progressCallback; this.progressState = Array.apply(null, Array(urls.length)).map(Number.prototype.valueOf,0); } let respackPromises = []; let progressFunc = function(index, progress, pack) { this.progressState[index] = progress; this.updateProgress(pack); }; for(let i = 0; i < urls.length; i++) { let r = new Respack(); respackPromises.push(r.loadFromURL(urls[i], progressFunc.bind(this, i))); } // Start all the promises at once, but add in sequence return respackPromises.reduce((sequence, packPromise) => { return sequence.then(() => { return packPromise; }).then(pack => { this.addPack(pack); }); }, Promise.resolve()); }; Resources.prototype.updateProgress = function(pack) { let total = 0; for(let i = 0; i < this.progressState.length; i++) { total += this.progressState[i]; } total /= this.progressState.length; this.progressCallback(total, pack); }; Resources.prototype.addPack = function(pack) { console.log("Added", pack.name, "to respacks"); let id = this.resourcePacks.length; this.resourcePacks.push(pack); this.addResourcesToArrays(pack); this.rebuildEnabled(); this.updateTotals(); let self = this; this.appendListItem("respacks", pack.name, "res" + id, this.packsView.respackList, function() { pack.enabled = this.checked; self.rebuildEnabled(); }, function(id) { this.selectPack(id); }.bind(this, id) ); }; Resources.prototype.addResourcesToArrays = function(pack) { this.allImages = this.allImages.concat(pack.images); this.allSongs = this.allSongs.concat(pack.songs); }; Resources.prototype.rebuildArrays = function() { this.allSongs = []; this.allImages = []; this.allAnimations = []; for(let i = 0; i < this.resourcePacks.length; i++) { this.addResourcesToArrays(this.resourcePacks[i]); } }; Resources.prototype.rebuildEnabled = function() { this.enabledSongs = []; this.enabledImages = []; for(let i = 0; i < this.resourcePacks.length; i++) { let pack = this.resourcePacks[i]; if (pack.enabled !== true) { continue; } for(let j = 0; j < pack.songs.length; j++) { let song = pack.songs[j]; if (song.enabled && this.enabledSongs.indexOf(song) == -1) { this.enabledSongs.push(song); } } for(let j = 0; j < pack.images.length; j++) { let image = pack.images[j]; if (image.enabled && this.enabledImages.indexOf(image) == -1) { this.enabledImages.push(image); } } } if(this.hasUI) { let songList = this.enabledSongList; while(songList.firstElementChild) { songList.removeChild(songList.firstElementChild); } let imageList = this.enabledImageList; while(imageList.firstElementChild) { imageList.removeChild(imageList.firstElementChild); } for(let i = 0; i < this.enabledSongs.length; i++) { let song = this.enabledSongs[i]; this.appendSimpleListItem(song.title, songList, function(index) { this.core.setSong(index); }.bind(this, i)); } for(let i = 0; i < this.enabledImages.length; i++) { let image = this.enabledImages[i]; this.appendSimpleListItem(image.name, imageList, function(index) { this.core.setImage(index); this.core.setIsFullAuto(false); }.bind(this, i)); } } this.updateTotals(); }; Resources.prototype.removePack = function(pack) { let index = this.resourcePacks.indexOf(pack); if (index != -1) { this.resourcePacks.splice(index, 1); this.rebuildArrays(); } }; Resources.prototype.removeAllPacks = function() { this.resourcePacks = []; this.rebuildArrays(); }; Resources.prototype.getSongNames = function() { let names = []; for(let i = 0; i < this.allSongs.length; i++) { names.push(this.allSongs[i]); } return names; }; Resources.prototype.loadLocal = function() { console.log("Loading local zip(s)"); let files = this.fileInput.files; let p = Promise.resolve(); for(let i = 0; i < files.length; i++) { let r = new Respack(); /*jshint -W083 */ p = p.then(() => { return r.loadFromBlob(files[i], (progress, respack) => { this.localProgress(progress, respack); }); }).then(pack => { this.addPack(pack); this.localComplete(); }); } return p.then(() => { console.log("Local respack parsing complete"); }); }; Resources.prototype.localProgress = function(progress, respack) { if(!this.hasUI) {return;} this.packsView.progressStatus.textContent = "Processing..."; this.packsView.progressBar.style.width = (progress * 100) + "%"; this.packsView.progressCurrent.textContent = respack.filesLoaded; this.packsView.progressTop.textContent = respack.filesToLoad; this.packsView.progressPercent.textContent = Math.round(progress * 100) + "%"; }; Resources.prototype.localComplete = function(progress) { let progStat = this.packsView.progressStatus; progStat.textContent = "Complete"; window.setTimeout(function() {progStat.textContent = "Idle";}, 2000); this.packsView.progressBar.style.width = "100%"; this.packsView.progressCurrent.textContent = "0b"; this.packsView.progressTop.textContent = "0b"; this.packsView.progressPercent.textContent = "0%"; }; Resources.prototype.initUI = function() { this.root = document.createElement("div"); this.root.className = "respacks"; let packsContainer = document.createElement("div"); packsContainer.className = "respacks__manager"; let packHeader = document.createElement("div"); packHeader.textContent = "Current respacks"; packHeader.className = "respacks__header"; let packList = document.createElement("div"); packList.className = "resource-list"; this.packsView.respackList = packList; // so we don't use it out of scope in the next if let remoteHeader = null; let remoteList = null; if(!this.core.settings.defaults.disableRemoteResources) { remoteHeader = document.createElement("div"); remoteHeader.textContent = "Remote respacks"; remoteHeader.className = "respacks__header"; remoteList = document.createElement("div"); remoteList.className = "resource-list resource-list--fill"; packList.classList.add("resource-list--fill"); this.appendSimpleListItem("Click to load the list", remoteList, this.loadRemotes.bind(this)); this.packsView.remoteList = remoteList; } let buttons = document.createElement("div"); buttons.className = "respacks-buttons"; let loadRemote = document.createElement("div"); loadRemote.className = "hues-button hidden"; loadRemote.textContent = "LOAD REMOTE"; loadRemote.onclick = this.loadCurrentRemote.bind(this); let loadLocal = document.createElement("div"); loadLocal.className = "hues-button"; loadLocal.textContent = "LOAD ZIPS"; loadLocal.onclick = () => {this.fileInput.click();}; buttons.appendChild(loadLocal); buttons.appendChild(loadRemote); this.packsView.loadRemote = loadRemote; this.fileInput = document.createElement("input"); this.fileInput.type ="file"; this.fileInput.accept="application/zip"; this.fileInput.multiple = true; this.fileInput.onchange = this.loadLocal.bind(this); let progressContainer = document.createElement("div"); progressContainer.className = "progress-container respacks-bottom-container"; let progressBar = document.createElement("div"); progressBar.className = "progress-bar"; let progressFilled = document.createElement("span"); progressFilled.className = "progress-bar--filled"; progressBar.appendChild(progressFilled); let progressStatus = document.createElement("div"); progressStatus.textContent = "Idle"; let progressTexts = document.createElement("div"); progressTexts.className = "stat-text"; let progressCurrent = document.createElement("div"); progressCurrent.textContent = "0b"; let progressTop = document.createElement("div"); progressTop.textContent = "0b"; let progressPercent = document.createElement("div"); progressPercent.textContent = "0%"; progressTexts.appendChild(progressCurrent); progressTexts.appendChild(progressTop); progressTexts.appendChild(progressPercent); this.packsView.progressBar = progressFilled; this.packsView.progressStatus = progressStatus; this.packsView.progressCurrent = progressCurrent; this.packsView.progressTop = progressTop; this.packsView.progressPercent = progressPercent; progressContainer.appendChild(progressStatus); progressContainer.appendChild(progressBar); progressContainer.appendChild(progressTexts); packsContainer.appendChild(packHeader); packsContainer.appendChild(packList); if(!this.core.settings.defaults.disableRemoteResources) { packsContainer.appendChild(remoteHeader); packsContainer.appendChild(remoteList); } packsContainer.appendChild(buttons); packsContainer.appendChild(progressContainer); let indivView = document.createElement("div"); indivView.className = "respacks__display"; let packName = document.createElement("div"); packName.textContent = "