Move to ES2015 classes. Makes HuesUI especially nice

master
William Toohey 9 years ago
parent f4f62d167b
commit 4145470d6a
  1. 4
      package.json
  2. 793
      src/js/HuesCanvas.js
  3. 1828
      src/js/HuesCore.js
  4. 2516
      src/js/HuesEditor.js
  5. 4
      src/js/HuesInfo.js
  6. 384
      src/js/HuesSettings.js
  7. 2139
      src/js/HuesUI.js
  8. 258
      src/js/HuesWindow.js
  9. 1502
      src/js/ResourceManager.js
  10. 851
      src/js/ResourcePack.js
  11. 958
      src/js/SoundManager.js

@ -20,7 +20,7 @@
}, },
"homepage": "https://github.com/mon/0x40-web#readme", "homepage": "https://github.com/mon/0x40-web#readme",
"devDependencies": { "devDependencies": {
"babel-preset-es2015": "^6.6.0", "babel-preset-es2015": "^6.9.0",
"del": "^2.2.0", "del": "^2.2.0",
"gulp": "^3.9.1", "gulp": "^3.9.1",
"gulp-autoprefixer": "^3.1.0", "gulp-autoprefixer": "^3.1.0",
@ -33,6 +33,6 @@
"gulp-plumber": "^1.1.0", "gulp-plumber": "^1.1.0",
"gulp-sourcemaps": "^1.6.0", "gulp-sourcemaps": "^1.6.0",
"gulp-uglify": "^1.5.3", "gulp-uglify": "^1.5.3",
"jshint": "^2.9.1" "jshint": "^2.9.2"
} }
} }

@ -25,149 +25,186 @@
/* Takes root element to attach to, and an audio context element for /* Takes root element to attach to, and an audio context element for
getting the current time with reasonable accuracy */ getting the current time with reasonable accuracy */
function HuesCanvas(root, audioContext, core) { class HuesCanvas {
this.audio = audioContext; constructor(root, audioContext, core) {
core.addEventListener("newimage", this.setImage.bind(this)); this.audio = audioContext;
core.addEventListener("newcolour", this.setColour.bind(this)); core.addEventListener("newimage", this.setImage.bind(this));
core.addEventListener("beat", this.beat.bind(this)); core.addEventListener("newcolour", this.setColour.bind(this));
core.addEventListener("invert", this.setInvert.bind(this)); core.addEventListener("beat", this.beat.bind(this));
core.addEventListener("settingsupdated", this.settingsUpdated.bind(this)); core.addEventListener("invert", this.setInvert.bind(this));
core.addEventListener("frame", this.animationLoop.bind(this)); core.addEventListener("settingsupdated", this.settingsUpdated.bind(this));
this.core = core; core.addEventListener("frame", this.animationLoop.bind(this));
this.core = core;
this.needsRedraw = false;
this.colour = 0xFFFFFF;
this.image = null;
this.smartAlign = true; // avoid string comparisons every frame
this.animTimeout = null;
this.animFrame = null;
this.lastBeat = 0;
// set later
this.blurDecay = null;
this.blurAmount = null;
this.blurIterations = null;
this.blurDelta = null;
this.blurAlpha = null;
// dynamic
this.blurStart = 0;
this.blurDistance = 0;
this.xBlur = false;
this.yBlur = false;
// trippy mode
this.trippyStart = [0, 0]; // x, y
this.trippyRadii = [0, 0]; // x, y
// force trippy mode
this.trippyOn = false;
this.trippyRadius = 0;
this.blackout = false;
this.blackoutColour = "#000"; // for the whiteout case we must store this
this.blackoutTimeout = null;
this.invert = false;
this.colourFade = false;
this.colourFadeStart=0;
this.colourFadeLength=0;
this.oldColour=0xFFFFFF;
this.newColour=0xFFFFFF;
this.blendMode = "hard-light";
// Chosen because they look decent
this.setBlurAmount("medium");
this.setBlurQuality("high");
this.setBlurDecay("fast");
this.canvas = document.createElement('canvas');
this.context = this.canvas.getContext("2d");
this.canvas.width = 1280;
this.canvas.height = 720;
this.canvas.className = "hues-canvas";
root.appendChild(this.canvas);
this.offCanvas = document.createElement('canvas');
this.offContext = this.offCanvas.getContext('2d');
window.addEventListener('resize', this.resize.bind(this));
this.resize();
}
HuesCanvas.prototype.setInvert = function(invert) { this.needsRedraw = false;
this.invert = invert; this.colour = 0xFFFFFF;
this.needsRedraw = true; this.image = null;
}; this.smartAlign = true; // avoid string comparisons every frame
HuesCanvas.prototype.settingsUpdated = function() { this.animTimeout = null;
this.setSmartAlign(localStorage["smartAlign"]); this.animFrame = null;
this.setBlurAmount(localStorage["blurAmount"]); this.lastBeat = 0;
this.setBlurDecay(localStorage["blurDecay"]);
this.setBlurQuality(localStorage["blurQuality"]); // set later
this.trippyOn = localStorage["trippyMode"] == "on"; this.blurDecay = null;
}; this.blurAmount = null;
this.blurIterations = null;
HuesCanvas.prototype.resize = function() { this.blurDelta = null;
// height is max 720px, we expand width to suit this.blurAlpha = null;
let height = this.core.root.clientHeight; // dynamic
let ratio = this.core.root.clientWidth / height; this.blurStart = 0;
this.canvas.height = Math.min(height, 720); this.blurDistance = 0;
this.canvas.width = Math.ceil(this.canvas.height * ratio); this.xBlur = false;
this.offCanvas.height = this.canvas.height; this.yBlur = false;
this.offCanvas.width = this.canvas.width;
this.trippyRadius = Math.max(this.canvas.width, this.canvas.height) / 2; // trippy mode
this.needsRedraw = true; this.trippyStart = [0, 0]; // x, y
}; this.trippyRadii = [0, 0]; // x, y
// force trippy mode
HuesCanvas.prototype.redraw = function() { this.trippyOn = false;
let offset; // for centering/right/left align this.trippyRadius = 0;
let bOpacity;
let width = this.canvas.width; this.blackout = false;
let height = this.canvas.height; this.blackoutColour = "#000"; // for the whiteout case we must store this
this.blackoutTimeout = null;
let cTime = this.audio.currentTime;
// white BG for the hard light filter this.invert = false;
this.context.globalAlpha = 1;
this.context.globalCompositeOperation = "source-over"; this.colourFade = false;
if(this.blackout) { this.colourFadeStart=0;
// original is 3 frames at 30fps, this is close this.colourFadeLength=0;
bOpacity = (cTime - this.blackoutStart)*10; this.oldColour=0xFFFFFF;
if(bOpacity > 1) { // optimise the draw this.newColour=0xFFFFFF;
this.context.fillStyle = this.blackoutColour;
this.blendMode = "hard-light";
// Chosen because they look decent
this.setBlurAmount("medium");
this.setBlurQuality("high");
this.setBlurDecay("fast");
this.canvas = document.createElement('canvas');
this.context = this.canvas.getContext("2d");
this.canvas.width = 1280;
this.canvas.height = 720;
this.canvas.className = "hues-canvas";
root.appendChild(this.canvas);
this.offCanvas = document.createElement('canvas');
this.offContext = this.offCanvas.getContext('2d');
window.addEventListener('resize', this.resize.bind(this));
this.resize();
}
setInvert(invert) {
this.invert = invert;
this.needsRedraw = true;
}
settingsUpdated() {
this.setSmartAlign(localStorage["smartAlign"]);
this.setBlurAmount(localStorage["blurAmount"]);
this.setBlurDecay(localStorage["blurDecay"]);
this.setBlurQuality(localStorage["blurQuality"]);
this.trippyOn = localStorage["trippyMode"] == "on";
}
resize() {
// height is max 720px, we expand width to suit
let height = this.core.root.clientHeight;
let ratio = this.core.root.clientWidth / height;
this.canvas.height = Math.min(height, 720);
this.canvas.width = Math.ceil(this.canvas.height * ratio);
this.offCanvas.height = this.canvas.height;
this.offCanvas.width = this.canvas.width;
this.trippyRadius = Math.max(this.canvas.width, this.canvas.height) / 2;
this.needsRedraw = true;
}
redraw() {
let offset; // for centering/right/left align
let bOpacity;
let width = this.canvas.width;
let height = this.canvas.height;
let cTime = this.audio.currentTime;
// white BG for the hard light filter
this.context.globalAlpha = 1;
this.context.globalCompositeOperation = "source-over";
if(this.blackout) {
// original is 3 frames at 30fps, this is close
bOpacity = (cTime - this.blackoutStart)*10;
if(bOpacity > 1) { // optimise the draw
this.context.fillStyle = this.blackoutColour;
this.context.fillRect(0,0,width,height);
this.needsRedraw = false;
this.drawInvert();
return;
}
} else {
this.context.fillStyle = "#FFF";
this.context.fillRect(0,0,width,height); this.context.fillRect(0,0,width,height);
this.needsRedraw = false;
this.drawInvert();
return;
} }
} else {
this.context.fillStyle = "#FFF";
this.context.fillRect(0,0,width,height);
}
if(this.image && (this.image.bitmap || this.image.bitmaps)) { if(this.image && (this.image.bitmap || this.image.bitmaps)) {
let bitmap = this.image.animated ? let bitmap = this.image.animated ?
this.image.bitmaps[this.animFrame] : this.image.bitmap; this.image.bitmaps[this.animFrame] : this.image.bitmap;
let drawHeight = bitmap.height * (height / bitmap.height); let drawHeight = bitmap.height * (height / bitmap.height);
let drawWidth = (bitmap.width / bitmap.height) * drawHeight; let drawWidth = (bitmap.width / bitmap.height) * drawHeight;
if(this.smartAlign) { if(this.smartAlign) {
switch(this.image.align) { switch(this.image.align) {
case "left": case "left":
offset = 0; offset = 0;
break; break;
case "right": case "right":
offset = width - drawWidth; offset = width - drawWidth;
break; break;
default: default:
offset = width/2 - drawWidth/2; offset = width/2 - drawWidth/2;
break; break;
}
} else {
offset = width/2 - drawWidth/2;
} }
if(this.xBlur || this.yBlur) {
this.drawBlur(bitmap, offset, drawWidth, drawHeight);
}else {
this.context.globalAlpha = 1;
this.context.drawImage(bitmap, offset, 0, drawWidth, drawHeight);
}
}
if(this.trippyStart[0] || this.trippyStart[1]) {
this.drawTrippy(width, height);
} else { } else {
offset = width/2 - drawWidth/2; this.offContext.fillStyle = this.intToHex(this.colour);
this.offContext.fillRect(0,0,width,height);
} }
if(this.xBlur || this.yBlur) { this.context.globalAlpha = 0.7;
this.context.globalAlpha = this.blurAlpha; this.context.globalCompositeOperation = this.blendMode;
this.context.drawImage(this.offCanvas, 0, 0);
if(this.blackout) {
this.context.globalAlpha = bOpacity;
this.context.fillStyle = this.blackoutColour;
this.context.fillRect(0,0,width,height);
this.needsRedraw = true;
} else {
this.needsRedraw = false;
} }
this.drawInvert();
}
drawInvert() {
if(this.invert) {
this.context.globalAlpha = 1;
this.context.globalCompositeOperation = "difference";
this.context.fillStyle = "#FFF";
this.context.fillRect(0,0,this.canvas.width,this.canvas.height);
}
}
drawBlur(bitmap, offset, drawWidth, drawHeight) {
this.context.globalAlpha = this.blurAlpha;
if(this.xBlur) { if(this.xBlur) {
if(this.blurIterations < 0) { if(this.blurIterations < 0) {
this.context.globalAlpha = 1; this.context.globalAlpha = 1;
@ -188,13 +225,11 @@ HuesCanvas.prototype.redraw = function() {
this.context.drawImage(bitmap, offset, Math.floor(this.blurDistance * i), drawWidth, drawHeight); this.context.drawImage(bitmap, offset, Math.floor(this.blurDistance * i), drawWidth, drawHeight);
} }
} }
} else {
this.context.globalAlpha = 1;
this.context.drawImage(bitmap, offset, 0, drawWidth, drawHeight);
} }
} }
if(this.trippyStart[0] || this.trippyStart[1]) { // draws the correct trippy colour circles onto the offscreen canvas
drawTrippy(width, height) {
// x blur moves inwards from the corners, y comes out // x blur moves inwards from the corners, y comes out
// So the base colour is inverted for y, normal for x // So the base colour is inverted for y, normal for x
// Thus if the y start is more recent, we invert // Thus if the y start is more recent, we invert
@ -222,289 +257,265 @@ HuesCanvas.prototype.redraw = function() {
this.offContext.closePath(); this.offContext.closePath();
invert = !invert; invert = !invert;
} }
} else {
this.offContext.fillStyle = this.intToHex(this.colour);
this.offContext.fillRect(0,0,width,height);
} }
this.context.globalAlpha = 0.7;
this.context.globalCompositeOperation = this.blendMode;
this.context.drawImage(this.offCanvas, 0, 0);
if(this.blackout) {
this.context.globalAlpha = bOpacity;
this.context.fillStyle = this.blackoutColour;
this.context.fillRect(0,0,width,height);
this.needsRedraw = true;
} else {
this.needsRedraw = false;
}
this.drawInvert();
};
HuesCanvas.prototype.drawInvert = function() { /* Second fastest method from
if(this.invert) { http://stackoverflow.com/questions/10073699/pad-a-number-with-leading-zeros-in-javascript
this.context.globalAlpha = 1; It stil does millions of ops per second, and isn't ugly like the integer if/else */
this.context.globalCompositeOperation = "difference"; intToHex(num) {
this.context.fillStyle = "#FFF"; return '#' + ("00000"+num.toString(16)).slice(-6);
this.context.fillRect(0,0,this.canvas.width,this.canvas.height);
} }
};
animationLoop() {
/* Second fastest method from if (this.colourFade) {
http://stackoverflow.com/questions/10073699/pad-a-number-with-leading-zeros-in-javascript let delta = this.audio.currentTime - this.colourFadeStart;
It stil does millions of ops per second, and isn't ugly like the integer if/else */ let fadeVal = delta / this.colourFadeLength;
HuesCanvas.prototype.intToHex = function(num) { if (fadeVal >= 1) {
return '#' + ("00000"+num.toString(16)).slice(-6); this.stopFade();
}; this.colour = this.newColour;
} else {
HuesCanvas.prototype.animationLoop = function() { this.mixColours(fadeVal);
if (this.colourFade) { }
let delta = this.audio.currentTime - this.colourFadeStart; this.needsRedraw = true;
let fadeVal = delta / this.colourFadeLength;
if (fadeVal >= 1) {
this.stopFade();
this.colour = this.newColour;
} else {
this.mixColours(fadeVal);
} }
this.needsRedraw = true; if(this.blackoutTimeout && this.audio.currentTime > this.blackoutTimeout) {
} this.clearBlackout();
if(this.blackoutTimeout && this.audio.currentTime > this.blackoutTimeout) { }
this.clearBlackout(); if(this.image && this.image.animated){
} if(this.image.beatsPerAnim && this.core.currentSong && this.core.currentSong.charsPerBeat) {
if(this.image && this.image.animated){ let a = this.animFrame;
if(this.image.beatsPerAnim && this.core.currentSong && this.core.currentSong.charsPerBeat) { this.syncAnim();
let a = this.animFrame; if(this.animFrame != a) {
this.syncAnim(); this.needsRedraw = true;
if(this.animFrame != a) { // If you change to a non-synced song, this needs to be reset
this.animTimeout = this.audio.currentTime;
}
} else if(this.animTimeout < this.audio.currentTime) {
this.animFrame++;
this.animFrame %= this.image.frameDurations.length;
// Don't rebase to current time otherwise we may lag
this.animTimeout += this.image.frameDurations[this.animFrame]/1000;
this.needsRedraw = true; this.needsRedraw = true;
// If you change to a non-synced song, this needs to be reset
this.animTimeout = this.audio.currentTime;
} }
} else if(this.animTimeout < this.audio.currentTime) { }
this.animFrame++; if(this.blurStart) {
this.animFrame %= this.image.frameDurations.length; // flash offsets blur gen by a frame
// Don't rebase to current time otherwise we may lag let delta = this.audio.currentTime - this.blurStart + (1/30);
this.animTimeout += this.image.frameDurations[this.animFrame]/1000; this.blurDistance = this.blurAmount * Math.exp(-this.blurDecay * delta);
// Update UI
let dist = this.blurDistance / this.blurAmount;
if(this.xBlur)
this.core.blurUpdated(dist, 0);
else
this.core.blurUpdated(0, dist);
}
if(this.trippyStart[0] || this.trippyStart[1]) {
for(let i = 0; i < 2; i++) {
this.trippyRadii[i] = Math.floor((this.audio.currentTime - this.trippyStart[i]) * this.trippyRadius) * 2;
if(this.trippyRadii[i] > this.trippyRadius) {
this.trippyStart[i] = 0;
this.trippyRadii[i] = 0;
continue;
}
// x comes from outside the window
if(i % 2 === 0) {
this.trippyRadii[i] = this.trippyRadius - this.trippyRadii[i];
}
}
this.needsRedraw = true; this.needsRedraw = true;
} }
}
if(this.blurStart) {
// flash offsets blur gen by a frame
let delta = this.audio.currentTime - this.blurStart + (1/30);
this.blurDistance = this.blurAmount * Math.exp(-this.blurDecay * delta);
// Update UI if(this.blurStart && this.blurDistance < 1) {
let dist = this.blurDistance / this.blurAmount; this.core.blurUpdated(0, 0);
if(this.xBlur) this.blurDistance = 0;
this.core.blurUpdated(dist, 0); this.blurStart = 0;
else this.xBlur = this.yBlur = false;
this.core.blurUpdated(0, dist); this.redraw();
} else if(this.blurStart) {
this.redraw();
} else if(this.needsRedraw){
this.redraw();
}
} }
if(this.trippyStart[0] || this.trippyStart[1]) {
for(let i = 0; i < 2; i++) { setImage(image) {
this.trippyRadii[i] = Math.floor((this.audio.currentTime - this.trippyStart[i]) * this.trippyRadius) * 2; if(this.image == image) {
if(this.trippyRadii[i] > this.trippyRadius) { return;
this.trippyStart[i] = 0;
this.trippyRadii[i] = 0;
continue;
}
// x comes from outside the window
if(i % 2 === 0) {
this.trippyRadii[i] = this.trippyRadius - this.trippyRadii[i];
}
} }
this.needsRedraw = true; this.needsRedraw = true;
this.image = image;
// Null images don't need anything interesting done to them
if(!image || (!image.bitmap && !image.bitmaps)) {
return;
}
if(image.animated) {
this.animBeat = null;
this.animFrame = 0;
this.animTimeout = this.audio.currentTime + image.frameDurations[0]/1000;
if(image.beatsPerAnim && this.core.currentSong && this.core.currentSong.charsPerBeat) {
this.syncAnim();
}
}
} }
if(this.blurStart && this.blurDistance < 1) { beat() {
this.core.blurUpdated(0, 0); this.lastBeat = this.audio.currentTime;
this.blurDistance = 0; }
this.blurStart = 0;
this.xBlur = this.yBlur = false; syncAnim() {
this.redraw(); let song = this.core.currentSong;
} else if(this.blurStart) { if(!song) { // fallback to default
this.redraw(); return;
} else if(this.needsRedraw){ }
this.redraw(); let index = this.core.beatIndex;
// When animation has more frames than song has beats, or part thereof
if(this.lastBeat && this.core.getBeatLength()) {
let interp = (this.audio.currentTime - this.lastBeat) / this.core.getBeatLength();
index += Math.min(interp, 1);
}
// This loops A-OK because the core's beatIndex never rolls over for a new loop
let beatLoc = (index / song.charsPerBeat) % this.image.beatsPerAnim;
let aLen = this.image.bitmaps.length;
this.animFrame = Math.floor(aLen * (beatLoc / this.image.beatsPerAnim));
if(this.image.syncOffset) {
this.animFrame += this.image.syncOffset;
}
// Because negative mods are different in JS
this.animFrame = ((this.animFrame % aLen) + aLen) % aLen;
} }
};
HuesCanvas.prototype.setImage = function(image) { setColour(colour, isFade) {
if(this.image == image) { if(colour.c == this.colour) {
return; return;
}
if(isFade) {
this.newColour = colour.c;
} else {
this.stopFade();
this.colour = colour.c;
}
this.needsRedraw = true;
} }
this.needsRedraw = true;
this.image = image; doBlackout(whiteout) {
// Null images don't need anything interesting done to them if (typeof(whiteout)==='undefined') whiteout = false;
if(!image || (!image.bitmap && !image.bitmaps)) { if(whiteout) {
return; this.blackoutColour = "#FFF";
} else {
this.blackoutColour = "#000";
}
this.blackoutTimeout = 0; // indefinite
// Don't restart the blackout animation if we're already blacked out
if(!this.blackout) {
this.blackoutStart = this.audio.currentTime;
}
this.blackout = true;
this.needsRedraw = true;
if(localStorage["blackoutUI"] == "on") {
this.core.userInterface.hide();
}
} }
if(image.animated) {
this.animBeat = null; // for song changes
this.animFrame = 0; clearBlackout() {
this.animTimeout = this.audio.currentTime + image.frameDurations[0]/1000; this.blackout = false;
if(image.beatsPerAnim && this.core.currentSong && this.core.currentSong.charsPerBeat) { this.blackoutTimeout = 0;
this.syncAnim(); this.needsRedraw = true;
if(localStorage["blackoutUI"] == "on") {
this.core.userInterface.show();
} }
} }
};
HuesCanvas.prototype.beat = function() { doShortBlackout(beatTime) {
this.lastBeat = this.audio.currentTime; this.doBlackout();
}; this.blackoutTimeout = this.audio.currentTime + beatTime / 1.7;
// looks better if we go right to black
this.blackoutStart = 0;
}
doColourFade(length) {
this.colourFade = true;
this.colourFadeLength = length;
this.colourFadeStart = this.audio.currentTime;
this.oldColour = this.colour;
}
HuesCanvas.prototype.syncAnim = function() { stopFade() {
let song = this.core.currentSong; this.colourFade = false;
if(!song) { // fallback to default this.colourFadeStart = 0;
return; this.colourFadeLength = 0;
} }
let index = this.core.beatIndex;
// When animation has more frames than song has beats, or part thereof mixColours(percent) {
if(this.lastBeat && this.core.getBeatLength()) { percent = Math.min(1, percent);
let interp = (this.audio.currentTime - this.lastBeat) / this.core.getBeatLength(); let oldR = this.oldColour >> 16 & 0xFF;
index += Math.min(interp, 1); let oldG = this.oldColour >> 8 & 0xFF;
let oldB = this.oldColour & 0xFF;
let newR = this.newColour >> 16 & 0xFF;
let newG = this.newColour >> 8 & 0xFF;
let newB = this.newColour & 0xFF;
let mixR = oldR * (1 - percent) + newR * percent;
let mixG = oldG * (1 - percent) + newG * percent;
let mixB = oldB * (1 - percent) + newB * percent;
this.colour = mixR << 16 | mixG << 8 | mixB;
}
doXBlur() {
this.blurStart = this.audio.currentTime;
if(this.trippyOn)
this.trippyStart[0] = this.blurStart;
this.blurDistance = this.blurAmount;
this.xBlur = true;
this.yBlur = false;
this.needsRedraw = true;
} }
// This loops A-OK because the core's beatIndex never rolls over for a new loop
let beatLoc = (index / song.charsPerBeat) % this.image.beatsPerAnim;
let aLen = this.image.bitmaps.length; doYBlur() {
this.animFrame = Math.floor(aLen * (beatLoc / this.image.beatsPerAnim)); this.blurStart = this.audio.currentTime;
if(this.image.syncOffset) { if(this.trippyOn)
this.animFrame += this.image.syncOffset; this.trippyStart[1] = this.blurStart;
this.blurDistance = this.blurAmount;
this.xBlur = false;
this.yBlur = true;
this.needsRedraw = true;
} }
// Because negative mods are different in JS
this.animFrame = ((this.animFrame % aLen) + aLen) % aLen;
};
HuesCanvas.prototype.setColour = function(colour, isFade) { doTrippyX() {
if(colour.c == this.colour) { let saveTrippy = this.trippyOn;
return; // force trippy
this.trippyOn = true;
this.doXBlur();
this.trippyOn = saveTrippy;
} }
if(isFade) {
this.newColour = colour.c; doTrippyY() {
} else { let saveTrippy = this.trippyOn;
this.stopFade(); // force trippy
this.colour = colour.c; this.trippyOn = true;
this.doYBlur();
this.trippyOn = saveTrippy;
} }
this.needsRedraw = true;
}; setBlurDecay(decay) {
this.blurDecay = {"slow" : 7.8, "medium" : 14.1, "fast" : 20.8, "faster!" : 28.7}[decay];
HuesCanvas.prototype.doBlackout = function(whiteout) {
if (typeof(whiteout)==='undefined') whiteout = false;
if(whiteout) {
this.blackoutColour = "#FFF";
} else {
this.blackoutColour = "#000";
} }
this.blackoutTimeout = 0; // indefinite
// Don't restart the blackout animation if we're already blacked out setBlurQuality(quality) {
if(!this.blackout) { this.blurIterations = {"low" : -1, "medium" : 11, "high" : 19, "extreme" : 35}[quality];
this.blackoutStart = this.audio.currentTime; this.blurDelta = 1 / (this.blurIterations/2);
this.blurAlpha = 1 / (this.blurIterations/2);
} }
this.blackout = true;
this.needsRedraw = true; setBlurAmount(amount) {
if(localStorage["blackoutUI"] == "on") { this.blurAmount = {"low" : 48, "medium" : 96, "high" : 384}[amount];
this.core.userInterface.hide();
} }
};
setSmartAlign(align) {
// for song changes this.smartAlign = align == "on";
HuesCanvas.prototype.clearBlackout = function() {
this.blackout = false;
this.blackoutTimeout = 0;
this.needsRedraw = true;
if(localStorage["blackoutUI"] == "on") {
this.core.userInterface.show();
} }
}; }
HuesCanvas.prototype.doShortBlackout = function(beatTime) {
this.doBlackout();
this.blackoutTimeout = this.audio.currentTime + beatTime / 1.7;
// looks better if we go right to black
this.blackoutStart = 0;
};
HuesCanvas.prototype.doColourFade = function(length) {
this.colourFade = true;
this.colourFadeLength = length;
this.colourFadeStart = this.audio.currentTime;
this.oldColour = this.colour;
};
HuesCanvas.prototype.stopFade = function() {
this.colourFade = false;
this.colourFadeStart = 0;
this.colourFadeLength = 0;
};
HuesCanvas.prototype.mixColours = function(percent) {
percent = Math.min(1, percent);
let oldR = this.oldColour >> 16 & 0xFF;
let oldG = this.oldColour >> 8 & 0xFF;
let oldB = this.oldColour & 0xFF;
let newR = this.newColour >> 16 & 0xFF;
let newG = this.newColour >> 8 & 0xFF;
let newB = this.newColour & 0xFF;
let mixR = oldR * (1 - percent) + newR * percent;
let mixG = oldG * (1 - percent) + newG * percent;
let mixB = oldB * (1 - percent) + newB * percent;
this.colour = mixR << 16 | mixG << 8 | mixB;
};
HuesCanvas.prototype.doXBlur = function() {
this.blurStart = this.audio.currentTime;
if(this.trippyOn)
this.trippyStart[0] = this.blurStart;
this.blurDistance = this.blurAmount;
this.xBlur = true;
this.yBlur = false;
this.needsRedraw = true;
};
HuesCanvas.prototype.doYBlur = function() {
this.blurStart = this.audio.currentTime;
if(this.trippyOn)
this.trippyStart[1] = this.blurStart;
this.blurDistance = this.blurAmount;
this.xBlur = false;
this.yBlur = true;
this.needsRedraw = true;
};
HuesCanvas.prototype.doTrippyX = function() {
let saveTrippy = this.trippyOn;
// force trippy
this.trippyOn = true;
this.doXBlur();
this.trippyOn = saveTrippy;
};
HuesCanvas.prototype.doTrippyY = function() {
let saveTrippy = this.trippyOn;
// force trippy
this.trippyOn = true;
this.doYBlur();
this.trippyOn = saveTrippy;
};
HuesCanvas.prototype.setBlurDecay = function(decay) {
this.blurDecay = {"slow" : 7.8, "medium" : 14.1, "fast" : 20.8, "faster!" : 28.7}[decay];
};
HuesCanvas.prototype.setBlurQuality = function(quality) {
this.blurIterations = {"low" : -1, "medium" : 11, "high" : 19, "extreme" : 35}[quality];
this.blurDelta = 1 / (this.blurIterations/2);
this.blurAlpha = 1 / (this.blurIterations/2);
};
HuesCanvas.prototype.setBlurAmount = function(amount) {
this.blurAmount = {"low" : 48, "medium" : 96, "high" : 384}[amount];
};
HuesCanvas.prototype.setSmartAlign = function(align) {
this.smartAlign = align == "on";
};
window.HuesCanvas = HuesCanvas; window.HuesCanvas = HuesCanvas;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -26,7 +26,7 @@
/* HuesInfo.js populates the INFO tab in the Hues Window. /* HuesInfo.js populates the INFO tab in the Hues Window.
*/ */
let beatGlossary = [ const beatGlossary = [
"x Vertical blur (snare)", "x Vertical blur (snare)",
"o Horizontal blur (bass)", "o Horizontal blur (bass)",
"- No blur", "- No blur",
@ -44,7 +44,7 @@ let beatGlossary = [
"I Invert & change image" "I Invert & change image"
]; ];
let shortcuts = [ const shortcuts = [
"↑↓ Change song", "↑↓ Change song",
"←→ Change image", "←→ Change image",
"[N] Random song", "[N] Random song",

@ -26,7 +26,7 @@
/* If you're modifying settings for your hues, DON'T EDIT THIS /* If you're modifying settings for your hues, DON'T EDIT THIS
- Go to the HTML and edit the `defaults` object instead! - Go to the HTML and edit the `defaults` object instead!
*/ */
HuesSettings.prototype.defaultSettings = { const defaultSettings = {
// Location relative to root - where do the audio/zip workers live // Location relative to root - where do the audio/zip workers live
// This is required because Web Workers need an absolute path // This is required because Web Workers need an absolute path
workersPath : "lib/workers/", workersPath : "lib/workers/",
@ -80,7 +80,7 @@ HuesSettings.prototype.defaultSettings = {
}; };
// Don't get saved to localStorage // Don't get saved to localStorage
HuesSettings.prototype.ephemeralSettings = [ const ephemeralSettings = [
"load", "load",
"autoplay", "autoplay",
"overwriteLocal", "overwriteLocal",
@ -100,7 +100,7 @@ HuesSettings.prototype.ephemeralSettings = [
]; ];
// To dynamically build the UI like the cool guy I am // To dynamically build the UI like the cool guy I am
HuesSettings.prototype.settingsCategories = { const settingsCategories = {
"Functionality" : [ "Functionality" : [
"autoSong", "autoSong",
"autoSongShuffle", "autoSongShuffle",
@ -126,7 +126,7 @@ HuesSettings.prototype.settingsCategories = {
] ]
}; };
HuesSettings.prototype.settingsOptions = { const settingsOptions = {
smartAlign : { smartAlign : {
name : "Smart Align images", name : "Smart Align images",
options : ["off", "on"] options : ["off", "on"]
@ -218,223 +218,225 @@ HuesSettings.prototype.settingsOptions = {
} }
}; };
function HuesSettings(defaults) { class HuesSettings {
this.eventListeners = { constructor(defaults) {
/* callback updated() this.eventListeners = {
* /* callback updated()
* Called when settings are updated *
*/ * Called when settings are updated
updated : [] */
}; updated : []
};
this.hasUI = false;
this.hasUI = false;
this.settingCheckboxes = {};
this.settingCheckboxes = {};
this.textCallbacks = [];
this.visCallbacks = []; this.textCallbacks = [];
this.visCallbacks = [];
for(let attr in this.defaultSettings) { for(let attr in defaultSettings) {
if(this.defaultSettings.hasOwnProperty(attr)) { if(defaultSettings.hasOwnProperty(attr)) {
if(defaults[attr] === undefined) { if(defaults[attr] === undefined) {
defaults[attr] = this.defaultSettings[attr]; defaults[attr] = defaultSettings[attr];
} }
// don't write to local if it's a temp settings // don't write to local if it's a temp settings
if(this.ephemeralSettings.indexOf(attr) != -1) { if(ephemeralSettings.indexOf(attr) != -1) {
continue; continue;
} }
if(defaults.overwriteLocal) { if(defaults.overwriteLocal) {
localStorage[attr] = defaults[attr]; localStorage[attr] = defaults[attr];
}
// populate defaults, ignoring current
if(localStorage[attr] === undefined) {
localStorage[attr] = defaults[attr];
}
} }
// populate defaults, ignoring current }
if(localStorage[attr] === undefined) {
localStorage[attr] = defaults[attr];
}
}
}
this.defaults = defaults; this.defaults = defaults;
} }
HuesSettings.prototype.initUI = function(huesWin) { initUI(huesWin) {
let root = document.createElement("div"); let root = document.createElement("div");
root.className = "hues-options"; root.className = "hues-options";
// Don't make in every loop // Don't make in every loop
let intValidator = function(self, variable) { let intValidator = function(self, variable) {
this.value = this.value.replace(/\D/g,''); this.value = this.value.replace(/\D/g,'');
if(this.value === "" || this.value < 1) { if(this.value === "" || this.value < 1) {
this.value = ""; this.value = "";
return; return;
} }
localStorage[variable] = this.value; localStorage[variable] = this.value;
self.updateConditionals(); self.updateConditionals();
self.callEventListeners("updated"); self.callEventListeners("updated");
}; };
// To order things nicely // To order things nicely
for(let cat in this.settingsCategories) { for(let cat in settingsCategories) {
if(this.settingsCategories.hasOwnProperty(cat)) { if(settingsCategories.hasOwnProperty(cat)) {
let catContainer = document.createElement("div"); let catContainer = document.createElement("div");
catContainer.textContent = cat; catContainer.textContent = cat;
catContainer.className = "settings-category"; catContainer.className = "settings-category";
let cats = this.settingsCategories[cat]; let cats = settingsCategories[cat];
for(let i = 0; i < cats.length; i++) { for(let i = 0; i < cats.length; i++) {
let setName = cats[i]; let setName = cats[i];
let setContainer = document.createElement("div"); let setContainer = document.createElement("div");
let setting = this.settingsOptions[setName]; let setting = settingsOptions[setName];
setContainer.textContent = setting.name; setContainer.textContent = setting.name;
setContainer.className = "settings-individual"; setContainer.className = "settings-individual";
let buttonContainer = document.createElement("div"); let buttonContainer = document.createElement("div");
buttonContainer.className = "settings-buttons"; buttonContainer.className = "settings-buttons";
for(let j = 0; j < setting.options.length; j++) { for(let j = 0; j < setting.options.length; j++) {
let option = setting.options[j]; let option = setting.options[j];
if(typeof option === "string") { if(typeof option === "string") {
let checkbox = document.createElement("input"); let checkbox = document.createElement("input");
// Save checkbox so we can update UI stuff // Save checkbox so we can update UI stuff
this.settingCheckboxes[setName + "-" + option] = checkbox; this.settingCheckboxes[setName + "-" + option] = checkbox;
checkbox.className = "settings-checkbox"; checkbox.className = "settings-checkbox";
checkbox.type = "radio"; checkbox.type = "radio";
checkbox.value = option; checkbox.value = option;
let unique = 0; let unique = 0;
// Lets us have multiple hues on 1 page // Lets us have multiple hues on 1 page
let id = setName + "-" + option + "-"; let id = setName + "-" + option + "-";
while(document.getElementById(id + unique)) { while(document.getElementById(id + unique)) {
unique++; unique++;
}
checkbox.name = setName + "-" + unique;
checkbox.id = id + unique;
if(localStorage[setName] == option) {
checkbox.checked = true;
}
checkbox.onclick = function(self) {
self.set(setName, this.value);
}.bind(checkbox, this);
buttonContainer.appendChild(checkbox);
// So we can style this nicely
let label = document.createElement("label");
label.className = "settings-label";
label.htmlFor = checkbox.id;
label.textContent = option.toUpperCase();
buttonContainer.appendChild(label);
} else { // special option
if(option.type == "varText") {
let text = document.createElement("span");
text.textContent = option.text();
buttonContainer.appendChild(text);
this.textCallbacks.push({func:option.text, element:text});
} else if(option.type == "input") {
let input = document.createElement("input");
input.setAttribute("type", "text");
input.className = "settings-input";
input.value = localStorage[option.variable];
// TODO: support more than just positive ints when the need arises
if(option.inputType == "int") {
input.oninput = intValidator.bind(input, this, option.variable);
} }
input.autofocus = false; checkbox.name = setName + "-" + unique;
buttonContainer.appendChild(input); checkbox.id = id + unique;
if(option.visiblity) { if(localStorage[setName] == option) {
this.visCallbacks.push({func:option.visiblity, element:input}); checkbox.checked = true;
input.style.visibility = option.visiblity() ? "visible" : "hidden"; }
checkbox.onclick = function(self) {
self.set(setName, this.value);
}.bind(checkbox, this);
buttonContainer.appendChild(checkbox);
// So we can style this nicely
let label = document.createElement("label");
label.className = "settings-label";
label.htmlFor = checkbox.id;
label.textContent = option.toUpperCase();
buttonContainer.appendChild(label);
} else { // special option
if(option.type == "varText") {
let text = document.createElement("span");
text.textContent = option.text();
buttonContainer.appendChild(text);
this.textCallbacks.push({func:option.text, element:text});
} else if(option.type == "input") {
let input = document.createElement("input");
input.setAttribute("type", "text");
input.className = "settings-input";
input.value = localStorage[option.variable];
// TODO: support more than just positive ints when the need arises
if(option.inputType == "int") {
input.oninput = intValidator.bind(input, this, option.variable);
}
input.autofocus = false;
buttonContainer.appendChild(input);
if(option.visiblity) {
this.visCallbacks.push({func:option.visiblity, element:input});
input.style.visibility = option.visiblity() ? "visible" : "hidden";
}
} }
} }
}
}
setContainer.appendChild(buttonContainer);
catContainer.appendChild(setContainer);
} }
setContainer.appendChild(buttonContainer); root.appendChild(catContainer);
catContainer.appendChild(setContainer);
} }
root.appendChild(catContainer);
} }
huesWin.addTab("OPTIONS", root);
this.hasUI = true;
} }
huesWin.addTab("OPTIONS", root);
this.hasUI = true;
};
HuesSettings.prototype.get = function(setting) { get(setting) {
if(this.defaults.hasOwnProperty(setting)) { if(this.defaults.hasOwnProperty(setting)) {
if(this.ephemeralSettings.indexOf(setting) != -1) { if(ephemeralSettings.indexOf(setting) != -1) {
return this.defaults[setting]; return this.defaults[setting];
} else {
return localStorage[setting];
}
} else { } else {
return localStorage[setting]; console.log("WARNING: Attempted to fetch invalid setting:", setting);
return null;
} }
} else {
console.log("WARNING: Attempted to fetch invalid setting:", setting);
return null;
} }
};
// Set a named index to its named value, returns false if name doesn't exist // Set a named index to its named value, returns false if name doesn't exist
HuesSettings.prototype.set = function(setting, value) { set(setting, value) {
value = value.toLowerCase(); value = value.toLowerCase();
let opt = this.settingsOptions[setting]; let opt = settingsOptions[setting];
if(!opt || opt.options.indexOf(value) == -1) { if(!opt || opt.options.indexOf(value) == -1) {
console.log(value, "is not a valid value for", setting); console.log(value, "is not a valid value for", setting);
return false; return false;
}
// for updating the UI selection
try {
this.settingCheckboxes[setting + "-" + value].checked = true;
} catch(e) {}
localStorage[setting] = value;
this.updateConditionals();
this.callEventListeners("updated");
return true;
} }
// for updating the UI selection
try {
this.settingCheckboxes[setting + "-" + value].checked = true;
} catch(e) {}
localStorage[setting] = value;
this.updateConditionals();
this.callEventListeners("updated");
return true;
};
HuesSettings.prototype.updateConditionals = function() { updateConditionals() {
// update any conditionally formatted settings text // update any conditionally formatted settings text
for(let i = 0; i < this.textCallbacks.length; i++) { for(let i = 0; i < this.textCallbacks.length; i++) {
let text = this.textCallbacks[i]; let text = this.textCallbacks[i];
text.element.textContent = text.func(); text.element.textContent = text.func();
} }
for(let i = 0; i < this.visCallbacks.length; i++) { for(let i = 0; i < this.visCallbacks.length; i++) {
let callback = this.visCallbacks[i]; let callback = this.visCallbacks[i];
callback.element.style.visibility = callback.func() ? "visible" : "hidden"; callback.element.style.visibility = callback.func() ? "visible" : "hidden";
}
} }
};
// Note: This is not defaults as per defaultSettings, but those merged with // Note: This is not defaults as per defaultSettings, but those merged with
// the defaults given in the initialiser // the defaults given in the initialiser
HuesSettings.prototype.setDefaults = function() { setDefaults() {
for(let attr in this.defaults) { for(let attr in this.defaults) {
if(this.defaults.hasOwnProperty(attr)) { if(this.defaults.hasOwnProperty(attr)) {
if(this.ephemeralSettings.indexOf(attr) != -1) { if(ephemeralSettings.indexOf(attr) != -1) {
continue; continue;
}
localStorage[attr] = this.defaults[attr];
} }
localStorage[attr] = this.defaults[attr];
} }
} }
};
HuesSettings.prototype.callEventListeners = function(ev) { callEventListeners(ev) {
let args = Array.prototype.slice.call(arguments, 1); let args = Array.prototype.slice.call(arguments, 1);
this.eventListeners[ev].forEach(function(callback) { this.eventListeners[ev].forEach(function(callback) {
callback.apply(null, args); callback.apply(null, args);
}); });
}; }
HuesSettings.prototype.addEventListener = function(ev, callback) { addEventListener(ev, callback) {
ev = ev.toLowerCase(); ev = ev.toLowerCase();
if (typeof(this.eventListeners[ev]) !== "undefined") { if (typeof(this.eventListeners[ev]) !== "undefined") {
this.eventListeners[ev].push(callback); this.eventListeners[ev].push(callback);
} else { } else {
throw Error("Unknown event: " + ev); throw Error("Unknown event: " + ev);
}
} }
};
HuesSettings.prototype.removeEventListener = function(ev, callback) { removeEventListener(ev, callback) {
ev = ev.toLowerCase(); ev = ev.toLowerCase();
if (typeof(this.eventListeners[ev]) !== "undefined") { if (typeof(this.eventListeners[ev]) !== "undefined") {
this.eventListeners[ev] = this.eventListeners[ev].filter(function(a) { this.eventListeners[ev] = this.eventListeners[ev].filter(function(a) {
return (a !== callback); return (a !== callback);
}); });
} else { } else {
throw Error("Unknown event: " + ev); throw Error("Unknown event: " + ev);
}
} }
}; }
window.HuesSettings = HuesSettings; window.HuesSettings = HuesSettings;

File diff suppressed because it is too large Load Diff

@ -22,149 +22,151 @@
(function(window, document) { (function(window, document) {
"use strict"; "use strict";
function HuesWindow(root, defaults) { class HuesWindow {
this.eventListeners = { constructor(root, defaults) {
/* callback windowshown(shown) this.eventListeners = {
* /* callback windowshown(shown)
* When the window is shown, hidden or toggled this fires. *
* 'shown' is true if the window was made visible, false otherwise * When the window is shown, hidden or toggled this fires.
*/ * 'shown' is true if the window was made visible, false otherwise
windowshown : [], */
/* callback tabselected(tabName) windowshown : [],
* /* callback tabselected(tabName)
* The name of the tab that was selected *
*/ * The name of the tab that was selected
tabselected : [] */
}; tabselected : []
};
this.hasUI = defaults.enableWindow;
this.hasUI = defaults.enableWindow;
if(!this.hasUI)
return; if(!this.hasUI)
return;
this.window = document.createElement("div");
this.window.className = "hues-win-helper"; this.window = document.createElement("div");
root.appendChild(this.window); this.window.className = "hues-win-helper";
root.appendChild(this.window);
let actualWindow = document.createElement("div");
actualWindow.className = "hues-win"; let actualWindow = document.createElement("div");
this.window.appendChild(actualWindow); actualWindow.className = "hues-win";
this.window.appendChild(actualWindow);
let closeButton = document.createElement("div");
closeButton.className = "hues-win__closebtn"; let closeButton = document.createElement("div");
closeButton.onclick = this.hide.bind(this); closeButton.className = "hues-win__closebtn";
actualWindow.appendChild(closeButton); closeButton.onclick = this.hide.bind(this);
actualWindow.appendChild(closeButton);
this.tabContainer = document.createElement("div");
this.tabContainer.className = "hues-win__tabs"; this.tabContainer = document.createElement("div");
actualWindow.appendChild(this.tabContainer); this.tabContainer.className = "hues-win__tabs";
actualWindow.appendChild(this.tabContainer);
this.contentContainer = document.createElement("div");
this.contentContainer.className = "hues-win__content"; this.contentContainer = document.createElement("div");
actualWindow.appendChild(this.contentContainer); this.contentContainer.className = "hues-win__content";
actualWindow.appendChild(this.contentContainer);
this.contents = [];
this.tabs = []; this.contents = [];
this.tabNames = []; this.tabs = [];
this.tabNames = [];
if(defaults.showWindow) { if(defaults.showWindow) {
this.show(); this.show();
} else { } else {
this.hide(); this.hide();
}
} }
}
HuesWindow.prototype.addTab = function(tabName, tabContent) { addTab(tabName, tabContent) {
if(!this.hasUI) if(!this.hasUI)
return; return;
let label = document.createElement("div"); let label = document.createElement("div");
label.textContent = tabName; label.textContent = tabName;
label.className = "tab-label"; label.className = "tab-label";
label.onclick = this.selectTab.bind(this, tabName); label.onclick = this.selectTab.bind(this, tabName);
this.tabContainer.appendChild(label); this.tabContainer.appendChild(label);
this.tabs.push(label); this.tabs.push(label);
this.tabNames.push(tabName); this.tabNames.push(tabName);
let content = document.createElement("div"); let content = document.createElement("div");
content.className = "tab-content"; content.className = "tab-content";
content.appendChild(tabContent); content.appendChild(tabContent);
this.contentContainer.appendChild(content); this.contentContainer.appendChild(content);
this.contents.push(content); this.contents.push(content);
};
HuesWindow.prototype.selectTab = function(tabName, dontShowWin) {
if(!this.hasUI)
return;
if(!dontShowWin) {
this.show();
} }
for(let i = 0; i < this.tabNames.length; i++) {
let name = this.tabNames[i]; selectTab(tabName, dontShowWin) {
if(tabName.toLowerCase() == name.toLowerCase()) { if(!this.hasUI)
this.contents[i].classList.add("tab-content--active"); return;
this.tabs[i].classList.add("tab-label--active"); if(!dontShowWin) {
this.callEventListeners("tabselected", name); this.show();
} else { }
this.contents[i].classList.remove("tab-content--active"); for(let i = 0; i < this.tabNames.length; i++) {
this.tabs[i].classList.remove("tab-label--active"); let name = this.tabNames[i];
if(tabName.toLowerCase() == name.toLowerCase()) {
this.contents[i].classList.add("tab-content--active");
this.tabs[i].classList.add("tab-label--active");
this.callEventListeners("tabselected", name);
} else {
this.contents[i].classList.remove("tab-content--active");
this.tabs[i].classList.remove("tab-label--active");
}
} }
} }
};
HuesWindow.prototype.hide = function() { hide() {
if(!this.hasUI) if(!this.hasUI)
return; return;
this.window.classList.add("hidden"); this.window.classList.add("hidden");
this.callEventListeners("windowshown", false); this.callEventListeners("windowshown", false);
}; }
HuesWindow.prototype.show = function() { show() {
if(!this.hasUI) if(!this.hasUI)
return; return;
this.window.classList.remove("hidden"); this.window.classList.remove("hidden");
this.callEventListeners("windowshown", true); this.callEventListeners("windowshown", true);
}; }
HuesWindow.prototype.toggle = function() { toggle() {
if(!this.hasUI) if(!this.hasUI)
return; return;
if(this.window.classList.contains("hidden")) { if(this.window.classList.contains("hidden")) {
this.show(); this.show();
} else { } else {
this.hide(); this.hide();
}
} }
};
HuesWindow.prototype.callEventListeners = function(ev) { callEventListeners(ev) {
let args = Array.prototype.slice.call(arguments, 1); let args = Array.prototype.slice.call(arguments, 1);
this.eventListeners[ev].forEach(function(callback) { this.eventListeners[ev].forEach(function(callback) {
callback.apply(null, args); callback.apply(null, args);
}); });
}; }
HuesWindow.prototype.addEventListener = function(ev, callback) { addEventListener(ev, callback) {
ev = ev.toLowerCase(); ev = ev.toLowerCase();
if (typeof(this.eventListeners[ev]) !== "undefined") { if (typeof(this.eventListeners[ev]) !== "undefined") {
this.eventListeners[ev].push(callback); this.eventListeners[ev].push(callback);
} else { } else {
throw Error("Unknown event: " + ev); throw Error("Unknown event: " + ev);
}
} }
};
HuesWindow.prototype.removeEventListener = function(ev, callback) { removeEventListener(ev, callback) {
ev = ev.toLowerCase(); ev = ev.toLowerCase();
if (typeof(this.eventListeners[ev]) !== "undefined") { if (typeof(this.eventListeners[ev]) !== "undefined") {
this.eventListeners[ev] = this.eventListeners[ev].filter(function(a) { this.eventListeners[ev] = this.eventListeners[ev].filter(function(a) {
return (a !== callback); return (a !== callback);
}); });
} else { } else {
throw Error("Unknown event: " + ev); throw Error("Unknown event: " + ev);
}
} }
}; }
window.HuesWindow = HuesWindow; window.HuesWindow = HuesWindow;

File diff suppressed because it is too large Load Diff

@ -22,513 +22,514 @@
(function(window, document) { (function(window, document) {
"use strict"; "use strict";
let debugConsole = false; const debugConsole = false;
function debug() { function debug() {
if(debugConsole) { if(debugConsole) {
console.log.apply(window.console, arguments); console.log.apply(window.console, arguments);
} }
} }
function Respack() { const audioExtensions = new RegExp("\\.(mp3|ogg|wav)$", "i");
this.songs = []; const imageExtensions = new RegExp("\\.(png|gif|jpg|jpeg)$", "i");
this.songQueue = []; const animRegex = new RegExp("(.*?)_\\d+$");
this.images = [];
this.imageQueue = [];
this.name = "<no name>"; class Respack {
this.author = "<unknown>"; constructor() {
this.description = "<no description>"; this.songs = [];
this.link = null; this.songQueue = [];
this.images = [];
this.imageQueue = [];
this.size = -1; this.name = "<no name>";
this.downloaded = -1; this.author = "<unknown>";
this.enabled = true; this.description = "<no description>";
this.link = null;
this._xmlQueue = []; this.size = -1;
this.downloaded = -1;
this.enabled = true;
this.totalFiles = -1; this._xmlQueue = [];
// For zip parsing progress events
this.progressCallback = null;
this.filesToLoad = 0;
this.filesLoaded = 0;
this.loadedFromURL = false;
}
Respack.prototype.audioExtensions = new RegExp("\\.(mp3|ogg|wav)$", "i"); this.totalFiles = -1;
Respack.prototype.imageExtensions = new RegExp("\\.(png|gif|jpg|jpeg)$", "i");
Respack.prototype.animRegex = new RegExp("(.*?)_\\d+$");
Respack.prototype.updateProgress = function(override) { // For zip parsing progress events
if(this.progressCallback) { this.progressCallback = null;
let percent = this.filesLoaded / this.filesToLoad; this.filesToLoad = 0;
if(this.loadedFromURL) { this.filesLoaded = 0;
percent = (percent / 2) + 0.5; this.loadedFromURL = false;
}
this.progressCallback(typeof override === "number" ? override : percent, this);
} }
};
Respack.prototype.loadFromURL = function(url, progress) { updateProgress(override) {
this.loadedFromURL = true; if(this.progressCallback) {
if(progress) { let percent = this.filesLoaded / this.filesToLoad;
this.progressCallback = progress; if(this.loadedFromURL) {
percent = (percent / 2) + 0.5;
}
this.progressCallback(typeof override === "number" ? override : percent, this);
}
} }
return this.getBlob(url) loadFromURL(url, progress) {
.then(response => { this.loadedFromURL = true;
return this.loadFromBlob(response); if(progress) {
}); this.progressCallback = progress;
}; }
Respack.prototype.getBlob = function(url, progress) { return this.getBlob(url)
if(progress) { .then(response => {
this.progressCallback = progress; return this.loadFromBlob(response);
});
} }
return new Promise ((resolve, reject) => {
let req = new XMLHttpRequest(); getBlob(url, progress) {
req.open('GET', url, true); if(progress) {
req.responseType = 'blob'; this.progressCallback = progress;
req.onload = () => { }
if(req.status == 200) { return new Promise ((resolve, reject) => {
resolve(req.response); let req = new XMLHttpRequest();
} else { req.open('GET', url, true);
req.responseType = 'blob';
req.onload = () => {
if(req.status == 200) {
resolve(req.response);
} else {
reject(Error(req.status + ": Could not fetch respack at " + url));
}
};
req.onerror = function() {
reject(Error(req.status + ": Could not fetch respack at " + url)); reject(Error(req.status + ": Could not fetch respack at " + url));
} };
}; req.onprogress = event => {
req.onerror = function() { if (event.lengthComputable) {
reject(Error(req.status + ": Could not fetch respack at " + url)); this.size = event.total;
}; this.downloaded = event.loaded;
req.onprogress = event => { let percent = event.loaded / event.total;
if (event.lengthComputable) { if(this.progressCallback) {
this.size = event.total; this.progressCallback(percent / 2, this); // because of processing too
this.downloaded = event.loaded; }
let percent = event.loaded / event.total;
if(this.progressCallback) {
this.progressCallback(percent / 2, this); // because of processing too
} }
};
req.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;
} }
}; });
req.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;
}
});
};
Respack.prototype.loadFromBlob = function(blob, progress) { loadFromBlob(blob, progress) {
if(progress) { if(progress) {
this.progressCallback = progress; this.progressCallback = progress;
}
// We don't get progress events for loading the zip, set 0 progress
this.updateProgress(this.loadedFromURL ? 0.5 : 0);
return new Promise((resolve, reject) => {
this.size = blob.size;
let file = new zip.fs.FS();
file.importBlob(blob,
() => {
resolve(file);
},
error => { // failure
reject(Error("Respack error:", error.toString()));
}
);
}).then(zip => {
return this.parseZip(zip);
}).then(() => {
return this;
});
} }
// We don't get progress events for loading the zip, set 0 progress
this.updateProgress(this.loadedFromURL ? 0.5 : 0);
return new Promise((resolve, reject) => {
this.size = blob.size;
let file = new zip.fs.FS();
file.importBlob(blob,
() => {
resolve(file);
},
error => { // failure
reject(Error("Respack error:", error.toString()));
}
);
}).then(zip => {
return this.parseZip(zip);
}).then(() => {
return this;
});
};
Respack.prototype.parseZip = function(zip) { parseZip(zip) {
let entries = zip.entries; let entries = zip.entries;
this.totalFiles = 0; this.totalFiles = 0;
// Progress events // Progress events
this.filesToLoad = 0; this.filesToLoad = 0;
this.filesLoaded = 0; this.filesLoaded = 0;
// Get everything started // Get everything started
for(let i = 0; i < entries.length; i++) { for(let i = 0; i < entries.length; i++) {
if(!entries[i].directory && entries[i].name) { if(!entries[i].directory && entries[i].name) {
this.totalFiles++; this.totalFiles++;
this.parseFile(entries[i]); this.parseFile(entries[i]);
}
} }
return this.parseSongQueue()
.then(() => {
return this.parseImageQueue();
}).then(() => {
return this.parseXML();
}).then(() => {
// Cleanup
this._xmlQueue = [];
console.log("Loaded", this.name, "successfully with", this.songs.length,
"songs and", this.images.length, "images.");
});
} }
return this.parseSongQueue() parseFile(file) {
.then(() => { let name = file.name;
return this.parseImageQueue(); if (name.match(audioExtensions)) {
}).then(() => { this.songQueue.push(this.parseSong(file));
return this.parseXML(); this.filesToLoad++;
}).then(() => { } else if (name.match(imageExtensions)) {
// Cleanup this.imageQueue.push(this.parseImage(file));
this._xmlQueue = []; this.filesToLoad++;
console.log("Loaded", this.name, "successfully with", this.songs.length, } else if(name.toLowerCase().endsWith(".xml")){
"songs and", this.images.length, "images."); this._xmlQueue.push(this.loadXML(file));
}); }
}; }
parseSong(file) {
let name = file.name.replace(audioExtensions, "");
debug("parsing song: " + name);
if (this.containsSong(name)) {
let oldSong = this.getSong(name);
debug("WARNING: Song", name, "already exists! Conflict with", name, "and", oldSong.name);
} else {
let newSong = {"name":name,
"title":null,
"rhythm":null,
"source":null,
//"crc":this.quickCRC(file), TODO
"sound":null,
"enabled":true,
"filename":file.name,
"charsPerBeat": null};
let extension = file.name.split('.').pop().toLowerCase();
let mime = "";
switch(extension) {
case "mp3":
mime = "audio/mpeg3";
break;
case "ogg":
mime = "audio/ogg";
break;
case "wav":
mime = "audio/wav";
break;
default:
mime = "application/octet-stream";
}
this.songs.push(newSong);
return new Promise((resolve, reject) => {
file.getBlob(mime, sound => {
resolve(sound);
});
}).then(blob => {
return new Promise((resolve, reject) => {
// Because blobs are crap
let fr = new FileReader();
fr.onload = () => {
resolve(fr.result);
};
fr.readAsArrayBuffer(blob);
});
}).then(sound => {
newSong.sound = sound;
this.filesLoaded++;
this.updateProgress();
});
}
}
Respack.prototype.parseFile = function(file) { parseSongQueue() {
let name = file.name; return this.songQueue.reduce((sequence, songPromise) => {
if (name.match(this.audioExtensions)) { return sequence.then(() => {
this.songQueue.push(this.parseSong(file)); // Maintain order
this.filesToLoad++; return songPromise;
} else if (name.match(this.imageExtensions)) { });
this.imageQueue.push(this.parseImage(file)); }, Promise.resolve());
this.filesToLoad++;
} }
else if(name.toLowerCase().endsWith(".xml")){
this._xmlQueue.push(this.loadXML(file)); parseImage(file) {
let match;
let name = file.name.replace(imageExtensions, "");
let img;
// Animation
if((match = name.match(new RegExp("^(.*)_(\\d+)$")))) {
img = this.getImage(match[1]);
if(!img) { // make a fresh one
img = {"name":match[1],
"fullname":match[1],
"align":"center",
//"crc":this.quickCRC(file),
"bitmaps":[],
"frameDurations":[33],
"source":null,
"enabled":true,
"animated":true,
"beatsPerAnim": null};
this.images.push(img);
}
// Normal image
} else if (!this.containsImage(name)) {
img = {"name":name,
"fullname":name,
"bitmap":null,
"align":"center",
//"crc":this.quickCRC(file),
"source":null,
"enabled":true,
"filename":file.name,
"animated":false};
this.images.push(img);
} else {
let existing = this.getImage(name);
console.log("WARNING: Image", name, "already exists! Conflict with", file.name, "and", existing.name);
return;
}
return this.loadImage(file, img);
} }
};
Respack.prototype.parseSong = function(file) { loadImage(imgFile, imageObj) {
let name = file.name.replace(this.audioExtensions, ""); let extension = imgFile.name.split('.').pop().toLowerCase();
debug("parsing song: " + name);
if (this.containsSong(name)) {
let oldSong = this.getSong(name);
debug("WARNING: Song", name, "already exists! Conflict with", name, "and", oldSong.name);
} else {
let newSong = {"name":name,
"title":null,
"rhythm":null,
"source":null,
//"crc":this.quickCRC(file), TODO
"sound":null,
"enabled":true,
"filename":file.name,
"charsPerBeat": null};
let extension = file.name.split('.').pop().toLowerCase();
let mime = ""; let mime = "";
switch(extension) { switch(extension) {
case "mp3": case "png":
mime = "audio/mpeg3"; mime = "image/png";
break; break;
case "ogg": case "gif":
mime = "audio/ogg"; mime = "image/gif";
break; break;
case "wav": case "jpg":
mime = "audio/wav"; case "jpeg":
mime = "image/jpeg";
break; break;
default: default:
mime = "application/octet-stream"; mime = "application/octet-stream";
} }
this.songs.push(newSong);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
file.getBlob(mime, sound => { imgFile.getData64URI(mime, resolve);
resolve(sound); }).then(bitmap => {
});
}).then(blob => {
return new Promise((resolve, reject) => {
// Because blobs are crap
let fr = new FileReader();
fr.onload = () => {
resolve(fr.result);
};
fr.readAsArrayBuffer(blob);
});
}).then(sound => {
newSong.sound = sound;
this.filesLoaded++; this.filesLoaded++;
this.updateProgress(); this.updateProgress();
return {bitmap: bitmap, img: imageObj};
}); });
} }
};
Respack.prototype.parseSongQueue = function() {
return this.songQueue.reduce((sequence, songPromise) => {
return sequence.then(() => {
// Maintain order
return songPromise;
});
}, Promise.resolve());
};
Respack.prototype.parseImage = function(file) { parseImageQueue() {
let match; return this.imageQueue.reduce((sequence, imagePromise) => {
let name = file.name.replace(this.imageExtensions, ""); return sequence.then(() => {
let img; // Maintain order
return imagePromise;
// Animation }).then(response => {
if((match = name.match(new RegExp("^(.*)_(\\d+)$")))) { // Don't crash if the respack had duplicate images
img = this.getImage(match[1]); if(!response)
if(!img) { // make a fresh one return;
img = {"name":match[1], let newImg = new Image();
"fullname":match[1], newImg.src = response.bitmap;
"align":"center", if (response.img.animated) {
//"crc":this.quickCRC(file), response.img.bitmaps.push(newImg);
"bitmaps":[], } else {
"frameDurations":[33], response.img.bitmap = newImg;
"source":null, }
"enabled":true, });
"animated":true, }, Promise.resolve());
"beatsPerAnim": null};
this.images.push(img);
}
// Normal image
} else if (!this.containsImage(name)) {
img = {"name":name,
"fullname":name,
"bitmap":null,
"align":"center",
//"crc":this.quickCRC(file),
"source":null,
"enabled":true,
"filename":file.name,
"animated":false};
this.images.push(img);
} else {
let existing = this.getImage(name);
console.log("WARNING: Image", name, "already exists! Conflict with", file.name, "and", existing.name);
return;
}
return this.loadImage(file, img);
};
Respack.prototype.loadImage = function(imgFile, imageObj) {
let extension = imgFile.name.split('.').pop().toLowerCase();
let mime = "";
switch(extension) {
case "png":
mime = "image/png";
break;
case "gif":
mime = "image/gif";
break;
case "jpg":
case "jpeg":
mime = "image/jpeg";
break;
default:
mime = "application/octet-stream";
} }
return new Promise((resolve, reject) => {
imgFile.getData64URI(mime, resolve);
}).then(bitmap => {
this.filesLoaded++;
this.updateProgress();
return {bitmap: bitmap, img: imageObj};
});
};
Respack.prototype.parseImageQueue = function() {
return this.imageQueue.reduce((sequence, imagePromise) => {
return sequence.then(() => {
// Maintain order
return imagePromise;
}).then(response => {
// Don't crash if the respack had duplicate images
if(!response)
return;
let newImg = new Image();
newImg.src = response.bitmap;
if (response.img.animated) {
response.img.bitmaps.push(newImg);
} else {
response.img.bitmap = newImg;
}
});
}, Promise.resolve());
};
Respack.prototype.loadXML = function(file) { loadXML(file) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
file.getText(text => { file.getText(text => {
//XML parser will complain about a bare '&', but some respacks use &amp //XML parser will complain about a bare '&', but some respacks use &amp
text = text.replace(/&amp;/g, '&'); text = text.replace(/&amp;/g, '&');
text = text.replace(/&/g, '&amp;'); text = text.replace(/&/g, '&amp;');
let parser = new DOMParser(); let parser = new DOMParser();
let dom = parser.parseFromString(text, "text/xml"); let dom = parser.parseFromString(text, "text/xml");
resolve(dom); resolve(dom);
}); });
});
};
Respack.prototype.parseXML = function() {
for(let i = 0; i < this._xmlQueue.length; i++) {
this._xmlQueue[i] = this._xmlQueue[i].then(dom => {
switch(dom.documentElement.nodeName) {
case "songs":
if(this.songs.length > 0)
this.parseSongFile(dom);
break;
case "images":
if(this.images.length > 0)
this.parseImageFile(dom);
break;
case "info":
this.parseInfoFile(dom);
break;
default:
console.log("XML found with no songs, images or info");
break;
}
}); });
} }
return Promise.all(this._xmlQueue);
};
// Save some chars parseXML() {
Element.prototype.getTag = function(tag, def) { for(let i = 0; i < this._xmlQueue.length; i++) {
let t = this.getElementsByTagName(tag)[0]; this._xmlQueue[i] = this._xmlQueue[i].then(dom => {
return t ? t.textContent : (def ? def : null); switch(dom.documentElement.nodeName) {
}; case "songs":
if(this.songs.length > 0)
this.parseSongFile(dom);
break;
case "images":
if(this.images.length > 0)
this.parseImageFile(dom);
break;
case "info":
this.parseInfoFile(dom);
break;
default:
console.log("XML found with no songs, images or info");
break;
}
});
}
return Promise.all(this._xmlQueue);
}
Respack.prototype.parseSongFile = function(dom) { parseSongFile(dom) {
debug(" - Parsing songFile"); debug(" - Parsing songFile");
let newSongs = []; let newSongs = [];
let el = dom.documentElement.firstElementChild; let el = dom.documentElement.firstElementChild;
for(; el; el = el.nextElementSibling) { for(; el; el = el.nextElementSibling) {
let song = this.getSong(el.attributes[0].value); let song = this.getSong(el.attributes[0].value);
if(song) { if(song) {
song.title = el.getTag("title"); song.title = el.getTag("title");
if(!song.title) { if(!song.title) {
song.title = "<no name>"; song.title = "<no name>";
debug(" WARNING!", song.name, "has no title!"); debug(" WARNING!", song.name, "has no title!");
} }
song.rhythm = el.getTag("rhythm"); song.rhythm = el.getTag("rhythm");
if(!song.rhythm) { if(!song.rhythm) {
song.rhythm = "..no..rhythm.."; song.rhythm = "..no..rhythm..";
debug(" WARNING!!", song.name, "has no rhythm!!"); debug(" WARNING!!", song.name, "has no rhythm!!");
} }
song.buildupName = el.getTag("buildup"); song.buildupName = el.getTag("buildup");
if(song.buildupName) { if(song.buildupName) {
debug(" Finding a buildup '" + song.buildupName + "' for ", song.name); debug(" Finding a buildup '" + song.buildupName + "' for ", song.name);
let build = this.getSong(song.buildupName); let build = this.getSong(song.buildupName);
if(build) { if(build) {
song.buildup = build.sound; song.buildup = build.sound;
song.buildupPlayed = false; song.buildupPlayed = false;
// get rid of the junk // get rid of the junk
this.songs.splice(this.songs.indexOf(build), 1); this.songs.splice(this.songs.indexOf(build), 1);
} else { } else {
debug(" WARNING!", "Didn't find a buildup '" + song.buildupName + "'!"); debug(" WARNING!", "Didn't find a buildup '" + song.buildupName + "'!");
}
} }
}
song.buildupRhythm = el.getTag("buildupRhythm"); song.buildupRhythm = el.getTag("buildupRhythm");
song.independentBuild = el.getTag("independentBuild"); song.independentBuild = el.getTag("independentBuild");
song.source = el.getTag("source"); song.source = el.getTag("source");
song.charsPerBeat = parseFloat(el.getTag("charsPerBeat")); song.charsPerBeat = parseFloat(el.getTag("charsPerBeat"));
// Because PackShit breaks everything // Because PackShit breaks everything
if(this.name == "PackShit") { if(this.name == "PackShit") {
song.forceTrim = true; song.forceTrim = true;
}
newSongs.push(song);
debug(" [I] " + song.name, ": '" + song.title + "' added to songs");
} else {
debug(" WARNING!", "songs.xml: <song> element", i + 1,
"- no song '" + el.attributes[0].value + "' found");
} }
newSongs.push(song);
debug(" [I] " + song.name, ": '" + song.title + "' added to songs");
} else {
debug(" WARNING!", "songs.xml: <song> element", i + 1,
"- no song '" + el.attributes[0].value + "' found");
} }
} for(let i = 0; i < this.songs.length; i++) {
for(let i = 0; i < this.songs.length; i++) { if(newSongs.indexOf(this.songs[i]) == -1) {
if(newSongs.indexOf(this.songs[i]) == -1) { debug(" WARNING!", "We have a file for", this.songs[i].name, "but no information for it");
debug(" WARNING!", "We have a file for", this.songs[i].name, "but no information for it"); }
} }
this.songs = newSongs;
} }
this.songs = newSongs;
};
Respack.prototype.parseInfoFile = function(dom) { parseInfoFile(dom) {
debug(" - Parsing infoFile"); debug(" - Parsing infoFile");
let info = dom.documentElement; let info = dom.documentElement;
// self reference strings to avoid changing strings twice in future // self reference strings to avoid changing strings twice in future
this.name = info.getTag("name", this.name); this.name = info.getTag("name", this.name);
this.author = info.getTag("author", this.author); this.author = info.getTag("author", this.author);
this.description = info.getTag("description", this.description); this.description = info.getTag("description", this.description);
this.link = info.getTag("link", this.link); this.link = info.getTag("link", this.link);
}; }
Respack.prototype.parseImageFile = function(dom) { parseImageFile(dom) {
debug(" - Parsing imagefile"); debug(" - Parsing imagefile");
let newImages = []; let newImages = [];
let el = dom.documentElement.firstElementChild; let el = dom.documentElement.firstElementChild;
for(; el; el = el.nextElementSibling) { for(; el; el = el.nextElementSibling) {
let image = this.getImage(el.attributes[0].value); let image = this.getImage(el.attributes[0].value);
if(image) { if(image) {
image.fullname = el.getTag("fullname"); image.fullname = el.getTag("fullname");
if(!image.fullname) { if(!image.fullname) {
debug(" WARNING!", image.name, "has no full name!"); debug(" WARNING!", image.name, "has no full name!");
} }
image.source = el.getTag("source"); image.source = el.getTag("source");
// self reference defaults to avoid changing strings twice in future // self reference defaults to avoid changing strings twice in future
image.align = el.getTag("align", image.align); image.align = el.getTag("align", image.align);
image.beatsPerAnim = parseFloat(el.getTag("beatsPerAnim")); image.beatsPerAnim = parseFloat(el.getTag("beatsPerAnim"));
image.syncOffset = parseFloat(el.getTag("syncOffset")); image.syncOffset = parseFloat(el.getTag("syncOffset"));
let frameDur = el.getTag("frameDuration"); let frameDur = el.getTag("frameDuration");
if(frameDur) { if(frameDur) {
image.frameDurations = []; image.frameDurations = [];
let strSplit = frameDur.split(","); let strSplit = frameDur.split(",");
for(let j = 0; j < strSplit.length; j++) { for(let j = 0; j < strSplit.length; j++) {
image.frameDurations.push(parseInt(strSplit[j])); image.frameDurations.push(parseInt(strSplit[j]));
}
while (image.frameDurations.length < image.bitmaps.length) {
image.frameDurations.push(image.frameDurations[image.frameDurations.length - 1]);
}
debug("Frame durations:", image.frameDurations);
}
debug(" [I] " + image.name, ":", image.fullname, "added to images");
if (image.bitmap || image.bitmaps) {
newImages.push(image);
} }
while (image.frameDurations.length < image.bitmaps.length) { else {
image.frameDurations.push(image.frameDurations[image.frameDurations.length - 1]); debug(" WARNING!!", "Image", image.name, "has no bitmap nor animation frames!");
} }
debug("Frame durations:", image.frameDurations); } else {
debug(" WARNING!", "images.xml: no image '" + el.attributes[0].value + "' found");
} }
debug(" [I] " + image.name, ":", image.fullname, "added to images"); }
if (image.bitmap || image.bitmaps) { for(let i = 0; i < this.images.length; i++) {
let image = this.images[i];
// Add all images with no info
if(newImages.indexOf(image) == -1) {
newImages.push(image); newImages.push(image);
} }
else {
debug(" WARNING!!", "Image", image.name, "has no bitmap nor animation frames!");
}
} else {
debug(" WARNING!", "images.xml: no image '" + el.attributes[0].value + "' found");
}
}
for(let i = 0; i < this.images.length; i++) {
let image = this.images[i];
// Add all images with no info
if(newImages.indexOf(image) == -1) {
newImages.push(image);
} }
newImages.sort(function(a, b) {
return a.name.localeCompare(b.name);
});
this.images = newImages;
} }
newImages.sort(function(a, b) {
return a.name.localeCompare(b.name);
});
this.images = newImages;
};
Respack.prototype.containsSong = function(name) { containsSong(name) {
return this.getSong(name) !== null; return this.getSong(name) !== null;
}; }
Respack.prototype.containsImage = function(name) { containsImage(name) {
return this.getImage(name) !== null; return this.getImage(name) !== null;
}; }
Respack.prototype.getSong = function(name) { getSong(name) {
for(let i = 0; i < this.songs.length; i++) { for(let i = 0; i < this.songs.length; i++) {
if (name == this.songs[i].name) { if (name == this.songs[i].name) {
return this.songs[i]; return this.songs[i];
}
} }
return null;
} }
return null;
};
Respack.prototype.getImage = function(name) { getImage(name) {
for(let i = 0; i < this.images.length; i++) { for(let i = 0; i < this.images.length; i++) {
if (name == this.images[i].name) { if (name == this.images[i].name) {
return this.images[i]; return this.images[i];
}
} }
return null;
} }
return null; }
};
window.Respack = Respack; window.Respack = Respack;
// Save some chars
Element.prototype.getTag = function(tag, def) {
let t = this.getElementsByTagName(tag)[0];
return t ? t.textContent : (def ? def : null);
};
})(window, document); })(window, document);

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save