UNPKG

@cassette/core

Version:

A simple, clean, and responsive visual wrapper for the HTML audio tag, built with React.

153 lines (139 loc) 3.85 kB
/* ShuffleManager * * Manages navigation throughout a list which is: * - Sourced from another provided list * - In random order (except to avoid consecutive duplicates) * - Extended endlessly on-the-fly, as needed * - Able to have future history overwritten by non-random choices * - Able to swap source lists and maintain shuffle order for common members */ export class ShuffleManager { constructor(list, options = {}) { this._list = list; this._forwardStack = []; this._backStack = []; this._currentItem = undefined; this._allowBackShuffle = Boolean(options.allowBackShuffle); } findNextItem(currentIndex) { if (currentIndex !== undefined) { this.setCurrentIndex(currentIndex); } this._currentItem = _findNextItem( this._list, this._forwardStack, this._backStack, this._currentItem, true ); return this._currentItem; } findPreviousItem(currentIndex) { if (currentIndex !== undefined) { this.setCurrentIndex(currentIndex); } this._currentItem = _findNextItem( this._list, this._backStack, this._forwardStack, this._currentItem, this._allowBackShuffle ); return this._currentItem; } pickNextItem(index, currentIndex) { if (currentIndex !== undefined) { this.setCurrentIndex(currentIndex); } if (this._list[index] === undefined) { return undefined; } if (this._currentItem !== undefined) { this._backStack.push(this._currentItem); } this._forwardStack.length = 0; this._currentItem = this._list[index]; return this._currentItem; } setList(list) { this._list = list; } setOptions(options) { for (const o of Object.keys(options)) { switch (o) { case 'allowBackShuffle': this[`_${o}`] = Boolean(options[o]); break; default: break; } } } setCurrentIndex(currentIndex) { const item = this._list[currentIndex]; if (this._currentItem !== item) { this.clear(); this._currentItem = item; } } clear() { this._forwardStack.length = 0; this._backStack.length = 0; this._currentItem = undefined; } } function _goForward(n, forwardStack, backStack, currentItem) { let item = currentItem; for (let i = 0; i < n; i++) { if (!forwardStack.length) { // rollback before erroring (note stack reversal) _goForward(i, backStack, forwardStack, item); throw `Moving ${n} places was not possible!`; } backStack.push(item); item = forwardStack.pop(); } return item; } function _allItemsMatch(list, item) { if (!list.length) { return false; } for (let i = 0; i < list.length; i++) { if (item !== list[i]) { return false; } } return true; } function _findNextItem(list, forwardStack, backStack, currentItem, allowMore) { let item = currentItem; if (!list.length) { return undefined; } for (let i = 1; i <= forwardStack.length; i++) { if (list.indexOf(forwardStack[forwardStack.length - i]) !== -1) { return _goForward(i, forwardStack, backStack, item); } } if (!allowMore) { return undefined; } if (_allItemsMatch(list, item)) { // we can serve this as our "next" item but we // won't modify our history since it's the same. return item; } let nextItem; do { nextItem = list[Math.floor(Math.random() * list.length)]; } while (item === nextItem || nextItem === undefined); // if we're skipping items that aren't in our current list we may // have some items in our forwardStack - make sure we move to the front. item = _goForward(forwardStack.length, forwardStack, backStack, item); if (item !== undefined) { backStack.push(item); } return nextItem; } export default ShuffleManager;