UNPKG

vscroll

Version:
344 lines 11.1 kB
import { Cache } from './buffer/cache'; import { CheckBufferCall } from './buffer/checkCall'; import { Reactive } from './reactive'; import { Direction } from '../inputs/index'; export class Buffer { constructor(settings, onDataChanged, logger) { this._items = []; this.logger = logger; this.changeItems = onDataChanged; this.bof = new Reactive(false); this.eof = new Reactive(false); this.cache = new Cache(settings, logger); this.checkCall = new CheckBufferCall(this, logger); this.startIndexUser = settings.startIndex; this.minIndexUser = settings.minIndex; this.maxIndexUser = settings.maxIndex; this.reset(true); } dispose() { this.bof.dispose(); this.eof.dispose(); this._items.forEach(item => item.dispose()); this._items = []; } reset(force, startIndex) { this.items.forEach(item => item.hide()); this.pristine = true; this.items = []; this.cache.reset(force); this.absMinIndex = this.minIndexUser; this.absMaxIndex = this.maxIndexUser; this.setCurrentStartIndex(startIndex); this.bof.set(false); this.eof.set(false); this.pristine = false; } setCurrentStartIndex(newStartIndex) { const min = this.minIndexUser; const max = this.maxIndexUser; const start = this.startIndexUser; let index = Number(newStartIndex); if (Number.isNaN(index)) { this.logger.log(() => `fallback startIndex to settings.startIndex (${start})`); index = start; } if (index < min) { this.logger.log(() => `setting startIndex to settings.minIndex (${min}) because ${index} < ${min}`); index = min; } if (index > max) { this.logger.log(() => `setting startIndex to settings.maxIndex (${max}) because ${index} > ${max}`); index = max; } this.startIndex = index; } set items(items) { this._items = items; this.changeItems(items); if (!this.pristine) { this.checkBOF(); this.checkEOF(); } } get items() { return this._items; } set absMinIndex(value) { if (this._absMinIndex !== value) { this._absMinIndex = Number.isFinite(this._absMaxIndex) && value > this._absMaxIndex ? this._absMaxIndex : value; } if (!this.pristine) { this.checkBOF(); } } get absMinIndex() { return this._absMinIndex; } set absMaxIndex(value) { if (this._absMaxIndex !== value) { this._absMaxIndex = Number.isFinite(this._absMinIndex) && value < this._absMinIndex ? this._absMinIndex : value; } if (!this.pristine) { this.checkEOF(); } } get absMaxIndex() { return this._absMaxIndex; } checkBOF() { // since bof has no setter, need to call checkBOF() on items and absMinIndex change const bof = this.items.length ? this.items[0].$index === this.absMinIndex : isFinite(this.absMinIndex); this.bof.set(bof); } checkEOF() { // since eof has no setter, need to call checkEOF() on items and absMaxIndex change const eof = this.items.length ? this.items[this.items.length - 1].$index === this.absMaxIndex : isFinite(this.absMaxIndex); this.eof.set(eof); } get size() { return this._items.length; } get cacheSize() { return this.cache.size; } get defaultSize() { return this.cache.getDefaultSize(); } get minIndex() { return isFinite(this.cache.minIndex) ? this.cache.minIndex : this.startIndex; } get maxIndex() { return isFinite(this.cache.maxIndex) ? this.cache.maxIndex : this.startIndex; } get firstIndex() { return this.items.length ? this.items[0].$index : NaN; } get lastIndex() { return this.items.length ? this.items[this.items.length - 1].$index : NaN; } get finiteAbsMinIndex() { return isFinite(this.absMinIndex) ? this.absMinIndex : this.minIndex; } get finiteAbsMaxIndex() { return isFinite(this.absMaxIndex) ? this.absMaxIndex : this.maxIndex; } get($index) { return this.items.find(item => item.$index === $index); } setItems(items) { if (!this.items.length) { this.items = [...items]; } else if (this.items[0].$index > items[items.length - 1].$index) { this.items = [...items, ...this.items]; } else if (items[0].$index > this.items[this.items.length - 1].$index) { this.items = [...this.items, ...items]; } else { return false; } return true; } clip() { this.items = this.items.filter(({ toRemove }) => !toRemove); } getIndexToInsert(predicate, before, after) { return this.checkCall.insertInBuffer(predicate, before, after); } shiftExtremum(amount, fixRight) { if (!fixRight) { this.absMaxIndex += amount; } else { this.absMinIndex -= amount; this.startIndex -= amount; } if (this.startIndex > this.absMaxIndex) { this.startIndex = this.absMaxIndex; } else if (this.startIndex < this.absMinIndex) { this.startIndex = this.absMinIndex; } } insertVirtually(items, index, direction, fixRight) { if (!this.checkCall.insertVirtual(items, index, direction)) { return false; } let shift = 0; if (index <= this.firstIndex && !fixRight) { shift = items.length; } else if (index >= this.lastIndex && fixRight) { shift = -items.length; } if (shift) { this.items.forEach(item => item.updateIndex(item.$index + shift)); this.cache.insertItems(items, index, direction, fixRight); this.items = [...this.items]; } this.shiftExtremum(items.length, fixRight); return true; } removeVirtually(indexes, fixRight) { const length = this.items.length; let shifted = false; for (let i = fixRight ? length - 1 : 0; fixRight ? i >= 0 : i < length; fixRight ? i-- : i++) { const item = this.items[i]; const diff = indexes.reduce((acc, index) => acc + (fixRight ? (item.$index < index ? 1 : 0) : item.$index > index ? -1 : 0), 0); shifted = shifted || !!diff; item.updateIndex(item.$index + diff); } this.shiftExtremum(-indexes.length, fixRight); if (shifted) { this.items = [...this.items]; } this.cache.removeItems(indexes, fixRight); } fillEmpty(items, beforeIndex, afterIndex, fixRight, generator) { if (!this.checkCall.fillEmpty(items, beforeIndex, afterIndex)) { return false; } const before = Number.isInteger(beforeIndex); const index = (before ? beforeIndex : afterIndex); const shift = fixRight ? items.length : before ? 1 : 0; this.items = items.map((data, i) => generator(index + i + (!before ? 1 : 0) - shift, data)); this._absMinIndex = this.items[0].$index; this._absMaxIndex = this.items[this.size - 1].$index; if (this.startIndex <= this.absMinIndex) { this.startIndex = this.absMinIndex; } else if (this.startIndex > this.absMaxIndex) { this.startIndex = this.absMaxIndex; } return true; } updateItems(predicate, generator, indexToTrack, fixRight) { if (!this.size || Number.isNaN(this.firstIndex)) { return { trackedIndex: NaN, toRemove: [] }; } let trackedIndex = indexToTrack; let index = fixRight ? this.lastIndex : this.firstIndex; const items = []; const diff = fixRight ? -1 : 1; const limit = this.size - 1; const beforeMap = new Map(); // need to persist original $indexes const updateArray = Array.prototype[fixRight ? 'unshift' : 'push']; for (let i = fixRight ? limit : 0; fixRight ? i >= 0 : i <= limit; i += diff) { const item = this.items[i]; beforeMap.set(item.$index, item); const result = predicate(item); // if predicate result is falsy or empty array -> delete if (!result || (Array.isArray(result) && !result.length)) { item.toRemove = true; trackedIndex += item.$index >= indexToTrack ? (fixRight ? 1 : 0) : fixRight ? 0 : -1; this.shiftExtremum(-1, fixRight); continue; } // if predicate result is truthy but not array -> leave if (!Array.isArray(result)) { item.updateIndex(index); updateArray.call(items, item); index += diff; continue; } // if predicate result is non-empty array -> insert/replace if (item.$index < indexToTrack) { trackedIndex += fixRight ? 0 : result.length - 1; } else if (item.$index > indexToTrack) { trackedIndex += fixRight ? 1 - result.length : 0; } let toRemove = true; const newItems = []; (fixRight ? [...result].reverse() : result).forEach((data, i) => { let newItem; if (item.data === data) { if (indexToTrack === item.$index) { trackedIndex = index + i * diff; } item.updateIndex(index + i * diff); newItem = item; toRemove = false; // insert case } else { newItem = generator(index + i * diff, data); newItem.toInsert = true; } updateArray.call(newItems, newItem); }); item.toRemove = toRemove; updateArray.call(items, ...newItems); index += diff * result.length; if (result.length > 1) { this.shiftExtremum(result.length - 1, fixRight); } } const toRemove = this.items.filter(item => item.toRemove); const itemsBefore = Array.from(beforeMap) .map(([$index, { size, toRemove }]) => ({ $index, size, toRemove })) .sort((a, b) => a.$index - b.$index); this.items = items; this.cache.updateSubset(itemsBefore, items, fixRight); if (this.finiteAbsMinIndex === this.finiteAbsMaxIndex) { trackedIndex = NaN; } else if (trackedIndex > this.finiteAbsMaxIndex) { trackedIndex = this.finiteAbsMaxIndex; } else if (trackedIndex < this.finiteAbsMinIndex) { trackedIndex = this.finiteAbsMinIndex; } return { trackedIndex, toRemove }; } cacheItem(item) { this.cache.add(item); } getFirstVisibleItemIndex() { const length = this.items.length; for (let i = 0; i < length; i++) { if (!this.items[i].invisible) { return i; } } return -1; } getLastVisibleItemIndex() { for (let i = this.items.length - 1; i >= 0; i--) { if (!this.items[i].invisible) { return i; } } return -1; } getFirstVisibleItem() { const index = this.getFirstVisibleItemIndex(); return index >= 0 ? this.items[index] : void 0; } getLastVisibleItem() { const index = this.getLastVisibleItemIndex(); return index >= 0 ? this.items[index] : void 0; } getEdgeVisibleItem(direction, opposite) { return direction === (!opposite ? Direction.forward : Direction.backward) ? this.getLastVisibleItem() : this.getFirstVisibleItem(); } getVisibleItemsCount() { return this.items.reduce((acc, item) => acc + (item.invisible ? 0 : 1), 0); } getSizeByIndex(index) { return this.cache.getSizeByIndex(index); } checkDefaultSize() { return this.cache.recalculateDefaultSize(); } } //# sourceMappingURL=buffer.js.map