webamp/js/components/MilkdropWindow/Presets.js
2018-10-17 21:50:53 -07:00

158 lines
4.2 KiB
JavaScript

function getRandomIndex(arr) {
return Math.floor(Math.random() * arr.length);
}
function getRandomValue(arr) {
return arr[getRandomIndex(arr)];
}
function getLast(arr) {
return arr[arr.length - 1];
}
async function readFileAsText(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function(e) {
resolve(e.target.result);
};
reader.onerror = function(e) {
reject(e);
};
reader.readAsText(file);
});
}
/**
* Track a collection of async loaded presets
*
* Presets can be changed via `next`, `previous` or `selectIndex`. In each case,
* a promise is returned. If the promise resolves to `null` it means the
* selection was canceled by a subsequent request before it could be fulfilled.
* If the request is successful, the promise resolves to the selected preset.
*
* We assume a model where some portion of the preset are supplied at initialization
* and the remainder can be loaded async via the function `getRest`.
*/
export default class Presets {
constructor({
keys,
initialPresets,
getRest,
presetConverterEndpoint,
loadConvertPreset,
randomize = true
}) {
this._keys = keys; // Alphabetical list of preset names
this._presets = initialPresets; // Presets indexed by name
this._getRest = getRest; // An async function to get the rest of the presets
this._presetConverterEndpoint = presetConverterEndpoint;
this._loadConvertPreset = loadConvertPreset;
this._history = []; // Indexes into _keys
this._randomize = randomize;
// Initialize with a key that we already have.
const avaliableKeys = Object.keys(initialPresets);
const currentKey = randomize
? getRandomValue(avaliableKeys)
: avaliableKeys[0];
this._currentIndex = this._keys.indexOf(currentKey);
this._history.push(this._currentIndex);
}
toggleRandomize() {
this._randomize = !this._randomize;
}
setRandomize(val) {
this._randomize = val;
}
addPresets(presets) {
const startIdx = this._keys.length;
this._keys = this._keys.concat(Object.keys(presets));
const endIndx = this._keys.length;
this._presets = Object.assign(this._presets, presets);
return [startIdx, endIndx];
}
loadPresets(presets) {
this._keys = Object.keys(presets);
this._presets = presets;
this._history = [];
return this._keys.length;
}
async next() {
let idx;
if (this._randomize || this._history.length === 0) {
idx = getRandomIndex(this._keys);
} else {
idx = (getLast(this._history) + 1) % this._keys.length;
}
this._history.push(idx);
return this._selectIndex(idx);
}
async previous() {
if (this._history.length > 1) {
this._history.pop();
return this._selectIndex(getLast(this._history));
}
// We are at the very beginning. There is no "previous" preset.
return Promise.resolve();
}
async selectIndex(idx) {
// The public version of this method must add to the history
this._history.push(idx);
return this._selectIndex(idx);
}
async _convertPreset(file) {
const convertPreset = await this._loadConvertPreset();
return convertPreset(file, this._presetConverterEndpoint);
}
async _selectIndex(idx) {
const preset = this._presets[this._keys[idx]];
if (!preset) {
const rest = await this._getRest();
this._presets = Object.assign(this._presets, rest);
if (getLast(this._history) !== idx) {
// This selection must be obsolete. Return null so that
// the caller knows this request got canceled.
return null;
}
}
if (preset && preset.file) {
try {
const fileContents = await readFileAsText(preset.file);
const convertedPreset = await this._convertPreset(fileContents);
this._presets[this._keys[idx]] = convertedPreset;
} catch (e) {
console.error(e);
alert(`Unable to convert MilkDrop preset ${this._keys[idx]}`);
}
}
this._currentIndex = idx;
return this.getCurrent();
}
getKeys() {
return this._keys;
}
getCurrentIndex() {
return this._currentIndex;
}
getCurrent() {
// #matryoshka
return this._presets[this._keys[this._currentIndex]];
}
}