moving render/record keeping and scroll functionality out of pagers

This commit is contained in:
Jake McDermott
2018-08-19 21:57:13 -04:00
parent ee348b7169
commit 138f8a45ae
3 changed files with 394 additions and 513 deletions

View File

@@ -17,12 +17,12 @@ let scroll;
let status; let status;
let slide; let slide;
let stream; let stream;
let page;
let vm; let vm;
const listeners = []; const listeners = [];
let lockFrames = false;
let lockFrames;
function onFrames (events) { function onFrames (events) {
events = slide.pushFrames(events); events = slide.pushFrames(events);
@@ -30,7 +30,7 @@ function onFrames (events) {
return $q.resolve(); return $q.resolve();
} }
const popCount = events.length - slide.getCapacity(); const popCount = events.length - render.getCapacity();
const isAttached = events.length > 0; const isAttached = events.length > 0;
if (!isAttached) { if (!isAttached) {
@@ -52,13 +52,13 @@ function onFrames (events) {
scroll.scrollToBottom(); scroll.scrollToBottom();
} }
return slide.popBack(popCount) return render.popBack(popCount)
.then(() => { .then(() => {
if (vm.isFollowing) { if (vm.isFollowing) {
scroll.scrollToBottom(); scroll.scrollToBottom();
} }
return slide.pushFront(events); return render.pushFrames(events);
}) })
.then(() => { .then(() => {
if (vm.isFollowing) { if (vm.isFollowing) {
@@ -71,7 +71,11 @@ function onFrames (events) {
}); });
} }
function first () { //
// Menu Controls (Running)
//
function firstRange () {
if (scroll.isPaused()) { if (scroll.isPaused()) {
return $q.resolve(); return $q.resolve();
} }
@@ -81,9 +85,15 @@ function first () {
stopFollowing(); stopFollowing();
return slide.getFirst() return render.clear()
.then(() => { .then(() => slide.getFirst())
scroll.resetScrollPosition(); .then(results => render.pushFront(results))
.then(() => slide.getNext())
.then(results => {
const popCount = results.length - render.getCapacity();
return render.popBack(popCount)
.then(() => render.pushFront(results));
}) })
.finally(() => { .finally(() => {
scroll.resume(); scroll.resume();
@@ -91,7 +101,7 @@ function first () {
}); });
} }
function next () { function nextRange () {
if (vm.isFollowing) { if (vm.isFollowing) {
scroll.scrollToBottom(); scroll.scrollToBottom();
@@ -110,26 +120,43 @@ function next () {
lockFrames = true; lockFrames = true;
return slide.getNext() return slide.getNext()
.then(results => {
const popCount = results.length - render.getCapacity();
return render.popBack(popCount)
.then(() => render.pushFront(results));
})
.finally(() => { .finally(() => {
scroll.resume(); scroll.resume();
lockFrames = false; lockFrames = false;
}); });
} }
function previous () { function previousRange () {
if (scroll.isPaused()) { if (scroll.isPaused()) {
return $q.resolve(); return $q.resolve();
} }
scroll.pause(); scroll.pause();
lockFrames = true; lockFrames = true;
stopFollowing(); stopFollowing();
const initialPosition = scroll.getScrollPosition(); let initialPosition;
let popHeight;
return slide.getPrevious() return slide.getPrevious()
.then(popHeight => { .then(results => {
const popCount = results.length - render.getCapacity();
initialPosition = scroll.getScrollPosition();
return render.popFront(popCount)
.then(() => {
popHeight = scroll.getScrollHeight();
return render.pushBack(results);
});
})
.then(() => {
const currentHeight = scroll.getScrollHeight(); const currentHeight = scroll.getScrollHeight();
scroll.setScrollPosition(currentHeight - popHeight + initialPosition); scroll.setScrollPosition(currentHeight - popHeight + initialPosition);
@@ -138,10 +165,12 @@ function previous () {
.finally(() => { .finally(() => {
scroll.resume(); scroll.resume();
lockFrames = false; lockFrames = false;
return $q.resolve();
}); });
} }
function last () { function lastRange () {
if (scroll.isPaused()) { if (scroll.isPaused()) {
return $q.resolve(); return $q.resolve();
} }
@@ -149,9 +178,10 @@ function last () {
scroll.pause(); scroll.pause();
lockFrames = true; lockFrames = true;
return slide.getLast() return render.clear()
.then(() => slide.getLast())
.then(results => render.pushFront(results))
.then(() => { .then(() => {
stream.setMissingCounterThreshold(slide.getTailCounter() + 1);
scroll.scrollToBottom(); scroll.scrollToBottom();
return $q.resolve(); return $q.resolve();
@@ -159,9 +189,30 @@ function last () {
.finally(() => { .finally(() => {
scroll.resume(); scroll.resume();
lockFrames = false; lockFrames = false;
return $q.resolve();
}); });
} }
function menuLastRange () {
if (vm.isFollowing) {
lockFollow = true;
stopFollowing();
return $q.resolve();
}
lockFollow = false;
if (slide.isOnLastPage()) {
scroll.scrollToBottom();
return $q.resolve();
}
return last();
}
let followOnce; let followOnce;
let lockFollow; let lockFollow;
function canStartFollowing () { function canStartFollowing () {
@@ -207,23 +258,159 @@ function stopFollowing () {
vm.followTooltip = vm.strings.get('tooltips.MENU_LAST'); vm.followTooltip = vm.strings.get('tooltips.MENU_LAST');
} }
//
// Menu Controls (Page Mode)
//
function firstPage () {
if (scroll.isPaused()) {
return $q.resolve();
}
scroll.pause();
return render.clear()
.then(() => page.getFirst())
.then(results => render.pushFront(results))
.then(() => page.getNext())
.then(results => {
const popCount = page.trimHead();
return render.popBack(popCount)
.then(() => render.pushFront(results));
})
.finally(() => {
scroll.resume();
return $q.resolve();
});
}
function lastPage () {
if (scroll.isPaused()) {
return $q.resolve();
}
scroll.pause();
return render.clear()
.then(() => page.getLast())
.then(results => render.pushBack(results))
.then(() => page.getPrevious())
.then(results => {
const popCount = page.trimTail();
return render.popFront(popCount)
.then(() => render.pushBack(results));
})
.then(() => {
scroll.scrollToBottom();
return $q.resolve();
})
.finally(() => {
scroll.resume();
return $q.resolve();
});
}
function nextPage () {
if (scroll.isPaused()) {
return $q.resolve();
}
scroll.pause();
return page.getNext()
.then(results => {
const popCount = page.trimHead();
return render.popBack(popCount)
.then(() => render.pushFront(results));
})
.finally(() => {
scroll.resume();
});
}
function previousPage () {
if (scroll.isPaused()) {
return $q.resolve();
}
scroll.pause();
let initialPosition;
let popHeight;
return page.getPrevious()
.then(results => {
const popCount = page.trimTail();
initialPosition = scroll.getScrollPosition();
return render.popFront(popCount)
.then(() => {
popHeight = scroll.getScrollHeight();
return render.pushBack(results);
});
})
.then(() => {
const currentHeight = scroll.getScrollHeight();
scroll.setScrollPosition(currentHeight - popHeight + initialPosition);
return $q.resolve();
})
.finally(() => {
scroll.resume();
return $q.resolve();
});
}
//
// Menu Controls
//
function first () {
if (vm.isProcessingFinished) {
return firstPage();
}
return firstRange();
}
function last () {
if (vm.isProcessingFinished) {
return lastPage();
}
return lastRange();
}
function next () {
if (vm.isProcessingFinished) {
return nextPage();
}
return nextRange();
}
function previous () {
if (vm.isProcessingFinished) {
return previousPage();
}
return previousRange();
}
function menuLast () { function menuLast () {
if (vm.isFollowing) { if (vm.isProcessingFinished) {
lockFollow = true; return lastPage();
stopFollowing();
return $q.resolve();
} }
lockFollow = false; return menuLastRange();
if (slide.isOnLastPage()) {
scroll.scrollToBottom();
return $q.resolve();
}
return last();
} }
function down () { function down () {
@@ -238,6 +425,10 @@ function togglePanelExpand () {
vm.isPanelExpanded = !vm.isPanelExpanded; vm.isPanelExpanded = !vm.isPanelExpanded;
} }
//
// Line Interaction
//
const iconCollapsed = 'fa-angle-right'; const iconCollapsed = 'fa-angle-right';
const iconExpanded = 'fa-angle-down'; const iconExpanded = 'fa-angle-down';
const iconSelector = '.at-Stdout-toggle > i'; const iconSelector = '.at-Stdout-toggle > i';
@@ -246,7 +437,7 @@ const lineCollapsed = 'hidden';
function toggleCollapseAll () { function toggleCollapseAll () {
if (scroll.isPaused()) return; if (scroll.isPaused()) return;
const records = Object.keys(render.record).map(key => render.record[key]); const records = Object.keys(render.records).map(key => render.records[key]);
const plays = records.filter(({ name }) => name === EVENT_START_PLAY); const plays = records.filter(({ name }) => name === EVENT_START_PLAY);
const tasks = records.filter(({ name }) => name === EVENT_START_TASK); const tasks = records.filter(({ name }) => name === EVENT_START_TASK);
@@ -286,7 +477,7 @@ function toggleCollapseAll () {
function toggleCollapse (uuid) { function toggleCollapse (uuid) {
if (scroll.isPaused()) return; if (scroll.isPaused()) return;
const record = render.record[uuid]; const record = render.records[uuid];
if (record.name === EVENT_START_PLAY) { if (record.name === EVENT_START_PLAY) {
togglePlayCollapse(uuid); togglePlayCollapse(uuid);
@@ -298,7 +489,7 @@ function toggleCollapse (uuid) {
} }
function togglePlayCollapse (uuid) { function togglePlayCollapse (uuid) {
const record = render.record[uuid]; const record = render.records[uuid];
const descendants = record.children || []; const descendants = record.children || [];
const icon = $(`#${uuid} ${iconSelector}`); const icon = $(`#${uuid} ${iconSelector}`);
@@ -329,11 +520,11 @@ function togglePlayCollapse (uuid) {
} }
descendants descendants
.map(item => render.record[item]) .map(item => render.records[item])
.filter(({ name }) => name === EVENT_START_TASK) .filter(({ name }) => name === EVENT_START_TASK)
.forEach(rec => { render.record[rec.uuid].isCollapsed = true; }); .forEach(rec => { render.records[rec.uuid].isCollapsed = true; });
render.record[uuid].isCollapsed = !isCollapsed; render.records[uuid].isCollapsed = !isCollapsed;
} }
function toggleTaskCollapse (uuid) { function toggleTaskCollapse (uuid) {
@@ -352,7 +543,7 @@ function toggleTaskCollapse (uuid) {
lines.addClass(lineCollapsed); lines.addClass(lineCollapsed);
} }
render.record[uuid].isCollapsed = !isCollapsed; render.records[uuid].isCollapsed = !isCollapsed;
} }
function compile (html) { function compile (html) {
@@ -363,6 +554,14 @@ function showHostDetails (id, uuid) {
$state.go('output.host-event.json', { eventId: id, taskUuid: uuid }); $state.go('output.host-event.json', { eventId: id, taskUuid: uuid });
} }
function showMissingEvents (uuid) {
console.log(`expandMissingEvents: ${uuid}`);
}
//
// Event Handling
//
let streaming; let streaming;
function stopListening () { function stopListening () {
streaming = null; streaming = null;
@@ -433,12 +632,20 @@ function handleSummaryEvent (data) {
stream.setFinalCounter(data.final_counter); stream.setFinalCounter(data.final_counter);
} }
//
// Search
//
function reloadState (params) { function reloadState (params) {
params.isPanelExpanded = vm.isPanelExpanded; params.isPanelExpanded = vm.isPanelExpanded;
return $state.transitionTo($state.current, params, { inherit: false, location: 'replace' }); return $state.transitionTo($state.current, params, { inherit: false, location: 'replace' });
} }
//
// Debug Mode
//
function clear () { function clear () {
stopListening(); stopListening();
render.clear(); render.clear();
@@ -449,7 +656,7 @@ function clear () {
stream.bufferInit(); stream.bufferInit();
status.init(resource); status.init(resource);
slide.init(render, resource.events, scroll); slide.init(resource.events, render);
status.subscribe(data => { vm.status = data.status; }); status.subscribe(data => { vm.status = data.status; });
startListening(); startListening();
@@ -484,7 +691,8 @@ function OutputIndexController (
render = _render_; render = _render_;
status = _status_; status = _status_;
stream = _stream_; stream = _stream_;
slide = isProcessingFinished ? _page_ : _slide_; slide = _slide_;
page = _page_;
vm = this || {}; vm = this || {};
@@ -495,6 +703,7 @@ function OutputIndexController (
vm.resource = resource; vm.resource = resource;
vm.reloadState = reloadState; vm.reloadState = reloadState;
vm.isPanelExpanded = isPanelExpanded; vm.isPanelExpanded = isPanelExpanded;
vm.isProcessingFinished = isProcessingFinished;
vm.togglePanelExpand = togglePanelExpand; vm.togglePanelExpand = togglePanelExpand;
// Stdout Navigation // Stdout Navigation
@@ -504,15 +713,18 @@ function OutputIndexController (
vm.toggleCollapseAll = toggleCollapseAll; vm.toggleCollapseAll = toggleCollapseAll;
vm.toggleCollapse = toggleCollapse; vm.toggleCollapse = toggleCollapse;
vm.showHostDetails = showHostDetails; vm.showHostDetails = showHostDetails;
vm.showMissingEvents = showMissingEvents;
vm.toggleLineEnabled = resource.model.get('type') === 'job'; vm.toggleLineEnabled = resource.model.get('type') === 'job';
vm.followTooltip = vm.strings.get('tooltips.MENU_LAST'); vm.followTooltip = vm.strings.get('tooltips.MENU_LAST');
vm.debug = _debug; vm.debug = _debug;
render.requestAnimationFrame(() => { render.requestAnimationFrame(() => {
status.init(resource);
slide.init(render, resource.events, scroll);
render.init({ compile, toggles: vm.toggleLineEnabled }); render.init({ compile, toggles: vm.toggleLineEnabled });
status.init(resource);
page.init(resource.events);
slide.init(resource.events, render);
scroll.init({ scroll.init({
next, next,
previous, previous,
@@ -600,4 +812,3 @@ OutputIndexController.$inject = [
]; ];
module.exports = OutputIndexController; module.exports = OutputIndexController;

View File

@@ -2,244 +2,153 @@
import { OUTPUT_PAGE_LIMIT } from './constants'; import { OUTPUT_PAGE_LIMIT } from './constants';
function PageService ($q) { function PageService ($q) {
this.init = (storage, api, { getScrollHeight }) => { this.init = ({ getPage, getFirst, getLast, getLastPageNumber }) => {
const { prepend, append, shift, pop, deleteRecord } = storage;
const { getPage, getFirst, getLast, getLastPageNumber, getMaxCounter } = api;
this.api = { this.api = {
getPage, getPage,
getFirst, getFirst,
getLast, getLast,
getLastPageNumber, getLastPageNumber,
getMaxCounter,
}; };
this.pages = {};
this.storage = { this.state = { head: 0, tail: 0 };
prepend,
append,
shift,
pop,
deleteRecord,
};
this.hooks = {
getScrollHeight,
};
this.records = {};
this.uuids = {};
this.state = {
head: 0,
tail: 0,
};
this.chain = $q.resolve();
};
this.pushFront = (results, key) => {
if (!results) {
return $q.resolve();
}
return this.storage.append(results)
.then(() => {
const tail = key || ++this.state.tail;
this.records[tail] = {};
results.forEach(({ counter, start_line, end_line, uuid }) => {
this.records[tail][counter] = { start_line, end_line };
this.uuids[counter] = uuid;
});
return $q.resolve();
});
};
this.pushBack = (results, key) => {
if (!results) {
return $q.resolve();
}
return this.storage.prepend(results)
.then(() => {
const head = key || --this.state.head;
this.records[head] = {};
results.forEach(({ counter, start_line, end_line, uuid }) => {
this.records[head][counter] = { start_line, end_line };
this.uuids[counter] = uuid;
});
return $q.resolve();
});
};
this.popBack = () => {
if (this.getRecordCount() === 0) {
return $q.resolve();
}
const pageRecord = this.records[this.state.head] || {};
let lines = 0;
const counters = [];
Object.keys(pageRecord)
.forEach(counter => {
lines += pageRecord[counter].end_line - pageRecord[counter].start_line;
counters.push(counter);
});
return this.storage.shift(lines)
.then(() => {
counters.forEach(counter => {
this.storage.deleteRecord(this.uuids[counter]);
delete this.uuids[counter];
});
delete this.records[this.state.head++];
return $q.resolve();
});
};
this.popFront = () => {
if (this.getRecordCount() === 0) {
return $q.resolve();
}
const pageRecord = this.records[this.state.tail] || {};
let lines = 0;
const counters = [];
Object.keys(pageRecord)
.forEach(counter => {
lines += pageRecord[counter].end_line - pageRecord[counter].start_line;
counters.push(counter);
});
return this.storage.pop(lines)
.then(() => {
counters.forEach(counter => {
this.storage.deleteRecord(this.uuids[counter]);
delete this.uuids[counter];
});
delete this.records[this.state.tail--];
return $q.resolve();
});
}; };
this.getNext = () => { this.getNext = () => {
const lastPageNumber = this.api.getLastPageNumber(); const lastPageNumber = this.api.getLastPageNumber();
const number = Math.min(this.state.tail + 1, lastPageNumber); const number = Math.min(this.state.tail + 1, lastPageNumber);
const isLoaded = (number >= this.state.head && number <= this.state.tail); if (number < 1) {
const isValid = (number >= 1 && number <= lastPageNumber); return $q.resolve([]);
let popHeight = this.hooks.getScrollHeight();
if (!isValid || isLoaded) {
this.chain = this.chain
.then(() => $q.resolve(popHeight));
return this.chain;
} }
const pageCount = this.state.head - this.state.tail; if (number > lastPageNumber) {
return $q.resolve([]);
if (pageCount >= OUTPUT_PAGE_LIMIT) {
this.chain = this.chain
.then(() => this.popBack())
.then(() => {
popHeight = this.hooks.getScrollHeight();
return $q.resolve();
});
} }
this.chain = this.chain let promise;
.then(() => this.api.getPage(number))
.then(events => this.pushFront(events))
.then(() => $q.resolve(popHeight));
return this.chain; if (this.pages[number]) {
promise = $q.resolve(this.pages[number]);
} else {
promise = this.api.getPage(number);
}
return promise
.then(results => {
if (results.length <= 0) {
return $q.resolve([]);
}
this.state.tail = number;
this.pages[number] = results;
return $q.resolve(results);
});
}; };
this.getPrevious = () => { this.getPrevious = () => {
const number = Math.max(this.state.head - 1, 1); const number = Math.max(this.state.head - 1, 1);
const isLoaded = (number >= this.state.head && number <= this.state.tail); if (number < 1) {
const isValid = (number >= 1 && number <= this.api.getLastPageNumber()); return $q.resolve([]);
let popHeight = this.hooks.getScrollHeight();
if (!isValid || isLoaded) {
this.chain = this.chain
.then(() => $q.resolve(popHeight));
return this.chain;
} }
const pageCount = this.state.head - this.state.tail; if (number > this.api.getLastPageNumber()) {
return $q.resolve([]);
if (pageCount >= OUTPUT_PAGE_LIMIT) {
this.chain = this.chain
.then(() => this.popFront())
.then(() => {
popHeight = this.hooks.getScrollHeight();
return $q.resolve();
});
} }
this.chain = this.chain let promise;
.then(() => this.api.getPage(number))
.then(events => this.pushBack(events))
.then(() => $q.resolve(popHeight));
return this.chain; if (this.pages[number]) {
promise = $q.resolve(this.pages[number]);
} else {
promise = this.api.getPage(number);
}
return promise
.then(results => {
if (results.length <= 0) {
return $q.resolve([]);
}
this.state.head = number;
this.pages[number] = results;
return $q.resolve(results);
});
}; };
this.clear = () => { this.getLast = () => this.api.getLast()
const count = this.getRecordCount(); .then(results => {
if (results.length <= 0) {
return $q.resolve([]);
}
for (let i = 0; i <= count; ++i) { const number = this.api.getLastPageNumber();
this.chain = this.chain.then(() => this.popBack());
}
return this.chain; this.state.head = number;
}; this.state.tail = number;
this.pages[number] = results;
this.getLast = () => this.clear() return $q.resolve(results);
.then(() => this.api.getLast()) });
.then(events => {
const lastPage = this.api.getLastPageNumber();
this.state.head = lastPage; this.getFirst = () => this.api.getFirst()
this.state.tail = lastPage; .then(results => {
if (results.length <= 0) {
return $q.resolve([]);
}
return this.pushBack(events, lastPage);
})
.then(() => this.getPrevious());
this.getFirst = () => this.clear()
.then(() => this.api.getFirst())
.then(events => {
this.state.head = 1; this.state.head = 1;
this.state.tail = 1; this.state.tail = 1;
this.pages[1] = results;
return this.pushBack(events, 1); return $q.resolve(results);
}) });
.then(() => this.getNext());
this.isOnLastPage = () => this.api.getLastPageNumber() === this.state.tail; this.trimTail = () => {
this.getRecordCount = () => Object.keys(this.records).length; const { tail, head } = this.state;
this.getTailCounter = () => this.state.tail; let popCount = 0;
this.getMaxCounter = () => this.api.getMaxCounter();
for (let i = tail; i > head; i--) {
if (!this.isOverCapacity()) {
break;
}
if (this.pages[i]) {
popCount += this.pages[i].length;
}
delete this.pages[i];
this.state.tail--;
}
return popCount;
};
this.trimHead = () => {
const { head, tail } = this.state;
let popCount = 0;
for (let i = head; i < tail; i++) {
if (!this.isOverCapacity()) {
break;
}
if (this.pages[i]) {
popCount += this.pages[i].length;
}
delete this.pages[i];
this.state.head++;
}
return popCount;
};
this.isOverCapacity = () => this.state.tail - this.state.head > OUTPUT_PAGE_LIMIT;
} }
PageService.$inject = ['$q']; PageService.$inject = ['$q'];

View File

@@ -1,6 +1,5 @@
/* eslint camelcase: 0 */ /* eslint camelcase: 0 */
import { import {
API_MAX_PAGE_SIZE,
OUTPUT_EVENT_LIMIT, OUTPUT_EVENT_LIMIT,
OUTPUT_PAGE_SIZE, OUTPUT_PAGE_SIZE,
} from './constants'; } from './constants';
@@ -34,9 +33,8 @@ function getContinuous (events, reverse = false) {
} }
function SlidingWindowService ($q) { function SlidingWindowService ($q) {
this.init = (storage, api, { getScrollHeight }) => { this.init = ({ getRange, getFirst, getLast, getMaxCounter }, storage) => {
const { prepend, append, shift, pop, getRecord, deleteRecord, clear } = storage; const { getHeadCounter, getTailCounter } = storage;
const { getRange, getFirst, getLast, getMaxCounter } = api;
this.api = { this.api = {
getRange, getRange,
@@ -46,36 +44,24 @@ function SlidingWindowService ($q) {
}; };
this.storage = { this.storage = {
clear, getHeadCounter,
prepend, getTailCounter,
append,
shift,
pop,
getRecord,
deleteRecord,
}; };
this.hooks = {
getScrollHeight,
};
this.lines = {};
this.uuids = {};
this.chain = $q.resolve();
this.state = { head: null, tail: null };
this.cache = { first: null };
this.buffer = { this.buffer = {
events: [], events: [],
min: 0, min: 0,
max: 0, max: 0,
count: 0, count: 0,
}; };
this.cache = {
first: null
};
}; };
this.getBoundedRange = range => { this.getBoundedRange = range => {
const bounds = [0, this.getMaxCounter()]; const bounds = [1, this.getMaxCounter()];
return [Math.max(range[0], bounds[0]), Math.min(range[1], bounds[1])]; return [Math.max(range[0], bounds[0]), Math.min(range[1], bounds[1])];
}; };
@@ -92,273 +78,48 @@ function SlidingWindowService ($q) {
return this.getBoundedRange([head - 1 - displacement, head - 1]); return this.getBoundedRange([head - 1 - displacement, head - 1]);
}; };
this.createRecord = ({ counter, uuid, start_line, end_line }) => {
this.lines[counter] = end_line - start_line;
this.uuids[counter] = uuid;
if (this.state.tail === null) {
this.state.tail = counter;
}
if (counter > this.state.tail) {
this.state.tail = counter;
}
if (this.state.head === null) {
this.state.head = counter;
}
if (counter < this.state.head) {
this.state.head = counter;
}
};
this.deleteRecord = counter => {
this.storage.deleteRecord(this.uuids[counter]);
delete this.uuids[counter];
delete this.lines[counter];
};
this.getLineCount = counter => {
const record = this.storage.getRecord(counter);
if (record && record.lineCount) {
return record.lineCount;
}
if (this.lines[counter]) {
return this.lines[counter];
}
return 0;
};
this.pushFront = events => {
const tail = this.getTailCounter();
const newEvents = events.filter(({ counter }) => counter > tail);
return this.storage.append(newEvents)
.then(() => {
newEvents.forEach(event => this.createRecord(event));
return $q.resolve();
});
};
this.pushBack = events => {
const [head, tail] = this.getRange();
const newEvents = events
.filter(({ counter }) => counter < head || counter > tail);
return this.storage.prepend(newEvents)
.then(() => {
newEvents.forEach(event => this.createRecord(event));
return $q.resolve();
});
};
this.popFront = count => {
if (!count || count <= 0) {
return $q.resolve();
}
const max = this.getTailCounter();
const min = max - count;
let lines = 0;
for (let i = max; i >= min; --i) {
lines += this.getLineCount(i);
}
return this.storage.pop(lines)
.then(() => {
for (let i = max; i >= min; --i) {
this.deleteRecord(i);
this.state.tail--;
}
return $q.resolve();
});
};
this.popBack = count => {
if (!count || count <= 0) {
return $q.resolve();
}
const min = this.getHeadCounter();
const max = min + count;
let lines = 0;
for (let i = min; i <= max; ++i) {
lines += this.getLineCount(i);
}
return this.storage.shift(lines)
.then(() => {
for (let i = min; i <= max; ++i) {
this.deleteRecord(i);
this.state.head++;
}
return $q.resolve();
});
};
this.clear = () => this.storage.clear()
.then(() => {
const [head, tail] = this.getRange();
for (let i = head; i <= tail; ++i) {
this.deleteRecord(i);
}
this.state.head = null;
this.state.tail = null;
return $q.resolve();
});
this.getNext = (displacement = OUTPUT_PAGE_SIZE) => { this.getNext = (displacement = OUTPUT_PAGE_SIZE) => {
const next = this.getNextRange(displacement); const next = this.getNextRange(displacement);
const [head, tail] = this.getRange();
this.chain = this.chain return this.api.getRange(next)
.then(() => this.api.getRange(next)) .then(results => getContinuous(results));
.then(events => {
const results = getContinuous(events);
const min = Math.min(...results.map(({ counter }) => counter));
if (min > tail + 1) {
return $q.resolve([]);
}
return $q.resolve(results);
})
.then(results => {
const count = (tail - head + results.length);
const excess = count - OUTPUT_EVENT_LIMIT;
return this.popBack(excess)
.then(() => {
const popHeight = this.hooks.getScrollHeight();
return this.pushFront(results).then(() => $q.resolve(popHeight));
});
});
return this.chain;
}; };
this.getPrevious = (displacement = OUTPUT_PAGE_SIZE) => { this.getPrevious = (displacement = OUTPUT_PAGE_SIZE) => {
const previous = this.getPreviousRange(displacement); const previous = this.getPreviousRange(displacement);
const [head, tail] = this.getRange();
this.chain = this.chain return this.api.getRange(previous)
.then(() => this.api.getRange(previous)) .then(results => getContinuous(results, true));
.then(events => {
const results = getContinuous(events, true);
const max = Math.max(...results.map(({ counter }) => counter));
if (head > max + 1) {
return $q.resolve([]);
}
return $q.resolve(results);
})
.then(results => {
const count = (tail - head + results.length);
const excess = count - OUTPUT_EVENT_LIMIT;
return this.popFront(excess)
.then(() => {
const popHeight = this.hooks.getScrollHeight();
return this.pushBack(results).then(() => $q.resolve(popHeight));
});
});
return this.chain;
}; };
this.getFirst = () => { this.getFirst = () => {
this.chain = this.chain if (this.cache.first) {
.then(() => this.clear()) return $q.resolve(this.cache.first);
.then(() => { }
if (this.cache.first) {
return $q.resolve(this.cache.first);
}
return this.api.getFirst(); return this.api.getFirst()
})
.then(events => { .then(events => {
if (events.length === OUTPUT_PAGE_SIZE) { if (events.length === OUTPUT_PAGE_SIZE) {
this.cache.first = events; this.cache.first = events;
} }
return this.pushFront(events); return $q.resolve(events);
}); });
return this.chain
.then(() => this.getNext());
}; };
this.getLast = () => { this.getLast = () => this.getFrames()
this.chain = this.chain .then(frames => {
.then(() => this.getFrames()) if (frames.length > 0) {
.then(frames => { return $q.resolve(frames);
if (frames.length > 0) { }
return $q.resolve(frames);
}
return this.api.getLast(); return this.api.getLast();
}) });
.then(events => {
const min = Math.min(...events.map(({ counter }) => counter));
if (min <= this.getTailCounter() + 1) {
return this.pushFront(events);
}
return this.clear()
.then(() => this.pushBack(events));
});
return this.chain
.then(() => this.getPrevious());
};
this.getTailCounter = () => {
if (this.state.tail === null) {
return 0;
}
if (this.state.tail < 0) {
return 0;
}
return this.state.tail;
};
this.getHeadCounter = () => {
if (this.state.head === null) {
return 0;
}
if (this.state.head < 0) {
return 0;
}
return this.state.head;
};
this.pushFrames = events => { this.pushFrames = events => {
const head = this.getHeadCounter();
const tail = this.getTailCounter();
const frames = this.buffer.events.concat(events); const frames = this.buffer.events.concat(events);
const [head, tail] = this.getRange();
let min; let min;
let max; let max;
@@ -367,7 +128,7 @@ function SlidingWindowService ($q) {
for (let i = frames.length - 1; i >= 0; i--) { for (let i = frames.length - 1; i >= 0; i--) {
count++; count++;
if (count > API_MAX_PAGE_SIZE) { if (count > OUTPUT_EVENT_LIMIT) {
frames.splice(i, 1); frames.splice(i, 1);
count--; count--;
@@ -417,9 +178,9 @@ function SlidingWindowService ($q) {
return this.getTailCounter() >= (this.getMaxCounter() - OUTPUT_PAGE_SIZE); return this.getTailCounter() >= (this.getMaxCounter() - OUTPUT_PAGE_SIZE);
}; };
this.getRange = () => [this.getHeadCounter(), this.getTailCounter()]; this.isOnFirstPage = () => this.getHeadCounter() === 1;
this.getRecordCount = () => Object.keys(this.lines).length; this.getTailCounter = () => this.storage.getTailCounter();
this.getCapacity = () => OUTPUT_EVENT_LIMIT - this.getRecordCount(); this.getHeadCounter = () => this.storage.getHeadCounter();
} }
SlidingWindowService.$inject = ['$q']; SlidingWindowService.$inject = ['$q'];