mirror of
https://github.com/ansible/awx.git
synced 2026-04-06 18:49:21 -02:30
Refactor scroll handling into independent service
This commit is contained in:
committed by
Jake McDermott
parent
b16d9a89e3
commit
0c09447f2d
@@ -1,83 +1,41 @@
|
|||||||
import Ansi from 'ansi-to-html';
|
|
||||||
import hasAnsi from 'has-ansi';
|
|
||||||
|
|
||||||
let vm;
|
|
||||||
let ansi;
|
|
||||||
let model;
|
|
||||||
let resource;
|
|
||||||
let page;
|
|
||||||
let container;
|
|
||||||
let $timeout;
|
|
||||||
let $sce;
|
|
||||||
let $compile;
|
|
||||||
let $scope;
|
|
||||||
let $q;
|
|
||||||
|
|
||||||
const record = {};
|
|
||||||
|
|
||||||
let parent = null;
|
|
||||||
|
|
||||||
const SCROLL_THRESHOLD = 0.1;
|
|
||||||
const SCROLL_DELAY = 1000;
|
|
||||||
const EVENT_START_TASK = 'playbook_on_task_start';
|
|
||||||
const EVENT_START_PLAY = 'playbook_on_play_start';
|
|
||||||
const EVENT_STATS_PLAY = 'playbook_on_stats';
|
|
||||||
const ELEMENT_TBODY = '#atStdoutResultTable';
|
|
||||||
const ELEMENT_CONTAINER = '.at-Stdout-container';
|
|
||||||
const JOB_START = 'playbook_on_start';
|
const JOB_START = 'playbook_on_start';
|
||||||
const JOB_END = 'playbook_on_stats';
|
const JOB_END = 'playbook_on_stats';
|
||||||
|
|
||||||
const EVENT_GROUPS = [
|
let vm;
|
||||||
EVENT_START_TASK,
|
let $compile;
|
||||||
EVENT_START_PLAY
|
let $scope;
|
||||||
];
|
let $q;
|
||||||
|
let page;
|
||||||
const TIME_EVENTS = [
|
let render;
|
||||||
EVENT_START_TASK,
|
let scroll;
|
||||||
EVENT_START_PLAY,
|
let resource;
|
||||||
EVENT_STATS_PLAY
|
|
||||||
];
|
|
||||||
|
|
||||||
function JobsIndexController (
|
function JobsIndexController (
|
||||||
_resource_,
|
_resource_,
|
||||||
_page_,
|
_page_,
|
||||||
_$sce_,
|
_scroll_,
|
||||||
_$timeout_,
|
_render_,
|
||||||
_$scope_,
|
_$scope_,
|
||||||
_$compile_,
|
_$compile_,
|
||||||
_$q_
|
_$q_
|
||||||
) {
|
) {
|
||||||
vm = this || {};
|
vm = this || {};
|
||||||
|
|
||||||
$timeout = _$timeout_;
|
|
||||||
$sce = _$sce_;
|
|
||||||
$compile = _$compile_;
|
$compile = _$compile_;
|
||||||
$scope = _$scope_;
|
$scope = _$scope_;
|
||||||
$q = _$q_;
|
$q = _$q_;
|
||||||
resource = _resource_;
|
resource = _resource_;
|
||||||
|
|
||||||
page = _page_;
|
page = _page_;
|
||||||
model = resource.model;
|
scroll = _scroll_;
|
||||||
|
render = _render_;
|
||||||
ansi = new Ansi();
|
|
||||||
|
|
||||||
const events = model.get(`related.${resource.related}.results`);
|
|
||||||
const parsed = parseEvents(events);
|
|
||||||
const html = $sce.trustAsHtml(parsed.html);
|
|
||||||
|
|
||||||
page.init(resource);
|
|
||||||
|
|
||||||
page.add({ number: 1, lines: parsed.lines });
|
|
||||||
|
|
||||||
// Development helper(s)
|
// Development helper(s)
|
||||||
vm.clear = devClear;
|
vm.clear = devClear;
|
||||||
|
|
||||||
// Stdout Navigation
|
// Stdout Navigation
|
||||||
vm.scroll = {
|
vm.scroll = {
|
||||||
isLocked: false,
|
|
||||||
showBackToTop: false,
|
showBackToTop: false,
|
||||||
isActive: false,
|
|
||||||
position: 0,
|
|
||||||
time: 0,
|
|
||||||
home: scrollHome,
|
home: scrollHome,
|
||||||
end: scrollEnd,
|
end: scrollEnd,
|
||||||
down: scrollPageDown,
|
down: scrollPageDown,
|
||||||
@@ -90,87 +48,93 @@ function JobsIndexController (
|
|||||||
vm.isExpanded = true;
|
vm.isExpanded = true;
|
||||||
|
|
||||||
// Real-time (active between JOB_START and JOB_END events only)
|
// Real-time (active between JOB_START and JOB_END events only)
|
||||||
$scope.$on(resource.ws.namespace, processWebSocketEvents);
|
|
||||||
vm.stream = {
|
vm.stream = {
|
||||||
isActive: false,
|
active: false,
|
||||||
isRendering: false,
|
rendering: false,
|
||||||
isPaused: false,
|
paused: false
|
||||||
buffered: 0,
|
|
||||||
count: 0,
|
|
||||||
page: 1
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.requestAnimationFrame(() => {
|
const stream = false; // TODO: Set in route
|
||||||
const table = $(ELEMENT_TBODY);
|
|
||||||
container = $(ELEMENT_CONTAINER);
|
|
||||||
|
|
||||||
table.html($sce.getTrustedHtml(html));
|
render.requestAnimationFrame(() => init());
|
||||||
$compile(table.contents())($scope);
|
|
||||||
|
|
||||||
container.scroll(onScroll);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function processWebSocketEvents (scope, data) {
|
function init (stream) {
|
||||||
let done;
|
page.init(resource);
|
||||||
|
|
||||||
|
render.init({
|
||||||
|
get: () => resource.model.get(`related.${resource.related}.results`),
|
||||||
|
compile: html => $compile(html)($scope)
|
||||||
|
});
|
||||||
|
|
||||||
|
scroll.init({
|
||||||
|
isAtRest: scrollIsAtRest,
|
||||||
|
previous,
|
||||||
|
next
|
||||||
|
});
|
||||||
|
|
||||||
|
if (stream) {
|
||||||
|
$scope.$on(resource.ws.namespace, process);
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function process (scope, data) {
|
||||||
if (data.event === JOB_START) {
|
if (data.event === JOB_START) {
|
||||||
vm.scroll.isActive = true;
|
vm.stream.active = true;
|
||||||
vm.stream.isActive = true;
|
scroll.lock();
|
||||||
vm.scroll.isLocked = true;
|
|
||||||
} else if (data.event === JOB_END) {
|
} else if (data.event === JOB_END) {
|
||||||
vm.stream.isActive = false;
|
vm.stream.active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pageAdded = page.addToBuffer(data);
|
const pageAdded = page.addToBuffer(data);
|
||||||
|
|
||||||
if (pageAdded && !vm.scroll.isLocked) {
|
if (pageAdded && !scroll.isLocked()) {
|
||||||
vm.stream.isPaused = true;
|
vm.stream.paused = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vm.stream.isPaused && vm.scroll.isLocked) {
|
if (vm.stream.paused && scroll.isLocked()) {
|
||||||
vm.stream.isPaused = false;
|
vm.stream.paused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vm.stream.isRendering || vm.stream.isPaused) {
|
if (vm.stream.rendering || vm.stream.paused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const events = page.emptyBuffer();
|
const events = page.emptyBuffer();
|
||||||
|
|
||||||
return render(events);
|
return renderStream(events);
|
||||||
}
|
}
|
||||||
|
|
||||||
function render (events) {
|
function renderStream (events) {
|
||||||
vm.stream.isRendering = true;
|
vm.stream.rendering = true;
|
||||||
|
|
||||||
return shift()
|
return shift()
|
||||||
.then(() => append(events))
|
.then(() => append(events))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (vm.scroll.isLocked) {
|
if (scroll.isLocked()) {
|
||||||
const height = container[0].scrollHeight;
|
scroll.setScrollPosition(scroll.getScrollHeight());
|
||||||
container[0].scrollTop = height;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!vm.stream.isActive) {
|
if (!vm.stream.active) {
|
||||||
const buffer = page.emptyBuffer();
|
const buffer = page.emptyBuffer();
|
||||||
|
|
||||||
if (buffer.length) {
|
if (buffer.length) {
|
||||||
return render(buffer);
|
return renderStream(buffer);
|
||||||
|
} else {
|
||||||
|
vm.stream.rendering = false;
|
||||||
|
scroll.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.stream.isRendering = false;
|
|
||||||
vm.scroll.isLocked = false;
|
|
||||||
vm.scroll.isActive = false;
|
|
||||||
} else {
|
} else {
|
||||||
vm.stream.isRendering = false;
|
vm.stream.rendering = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function devClear () {
|
function devClear () {
|
||||||
page.init(resource);
|
init(true);
|
||||||
clear();
|
render.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
function next () {
|
function next () {
|
||||||
@@ -182,13 +146,12 @@ function next () {
|
|||||||
|
|
||||||
return shift()
|
return shift()
|
||||||
.then(() => append(events));
|
.then(() => append(events));
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function previous () {
|
function previous () {
|
||||||
const container = $(ELEMENT_CONTAINER)[0];
|
let initialPosition = scroll.getScrollPosition();
|
||||||
|
let postPopHeight;
|
||||||
let previousHeight;
|
|
||||||
|
|
||||||
return page.previous()
|
return page.previous()
|
||||||
.then(events => {
|
.then(events => {
|
||||||
@@ -198,296 +161,56 @@ function previous () {
|
|||||||
|
|
||||||
return pop()
|
return pop()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
previousHeight = container.scrollHeight;
|
postPopHeight = scroll.getScrollHeight();
|
||||||
|
|
||||||
return prepend(events);
|
return prepend(events);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const currentHeight = container.scrollHeight;
|
const currentHeight = scroll.getScrollHeight();
|
||||||
container.scrollTop = currentHeight - previousHeight;
|
|
||||||
|
scroll.setScrollPosition(currentHeight - postPopHeight + initialPosition);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function append (events) {
|
function append (events) {
|
||||||
return $q(resolve => {
|
return render.append(events)
|
||||||
window.requestAnimationFrame(() => {
|
.then(count => {
|
||||||
const parsed = parseEvents(events);
|
page.updateLineCount('current', count);
|
||||||
const rows = $($sce.getTrustedHtml($sce.trustAsHtml(parsed.html)));
|
|
||||||
const table = $(ELEMENT_TBODY);
|
|
||||||
|
|
||||||
page.updateLineCount('current', parsed.lines);
|
|
||||||
|
|
||||||
table.append(rows);
|
|
||||||
$compile(rows.contents())($scope);
|
|
||||||
|
|
||||||
return resolve();
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepend (events) {
|
function prepend (events) {
|
||||||
return $q(resolve => {
|
return render.prepend(events)
|
||||||
window.requestAnimationFrame(() => {
|
.then(count => {
|
||||||
const parsed = parseEvents(events);
|
page.updateLineCount('current', count);
|
||||||
const rows = $($sce.getTrustedHtml($sce.trustAsHtml(parsed.html)));
|
|
||||||
const table = $(ELEMENT_TBODY);
|
|
||||||
|
|
||||||
page.updateLineCount('current', parsed.lines);
|
|
||||||
|
|
||||||
table.prepend(rows);
|
|
||||||
$compile(rows.contents())($scope);
|
|
||||||
|
|
||||||
$scope.$apply(() => {
|
|
||||||
return resolve(parsed.lines);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function pop () {
|
function pop () {
|
||||||
return $q(resolve => {
|
if (!page.isOverCapacity()) {
|
||||||
if (!page.isOverCapacity()) {
|
return $q.resolve();
|
||||||
return resolve();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
window.requestAnimationFrame(() => {
|
const lines = page.trim('right');
|
||||||
const lines = page.trim('right');
|
|
||||||
const rows = $(ELEMENT_TBODY).children().slice(-lines);
|
|
||||||
|
|
||||||
rows.empty();
|
return render.pop(lines);
|
||||||
rows.remove();
|
|
||||||
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function shift () {
|
function shift () {
|
||||||
return $q(resolve => {
|
if (!page.isOverCapacity()) {
|
||||||
if (!page.isOverCapacity()) {
|
return $q.resolve();
|
||||||
return resolve();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
window.requestAnimationFrame(() => {
|
const lines = page.trim('left');
|
||||||
const lines = page.trim('left');
|
|
||||||
const rows = $(ELEMENT_TBODY).children().slice(0, lines);
|
|
||||||
|
|
||||||
rows.empty();
|
return render.shift(lines);
|
||||||
rows.remove();
|
|
||||||
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear () {
|
|
||||||
return $q(resolve => {
|
|
||||||
window.requestAnimationFrame(() => {
|
|
||||||
const rows = $(ELEMENT_TBODY).children();
|
|
||||||
|
|
||||||
rows.empty();
|
|
||||||
rows.remove();
|
|
||||||
|
|
||||||
return resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function expand () {
|
function expand () {
|
||||||
vm.toggle(parent, true);
|
vm.toggle(parent, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseEvents (events) {
|
|
||||||
let lines = 0;
|
|
||||||
let html = '';
|
|
||||||
|
|
||||||
events.sort(orderByLineNumber);
|
|
||||||
|
|
||||||
events.forEach(event => {
|
|
||||||
const line = parseLine(event);
|
|
||||||
|
|
||||||
html += line.html;
|
|
||||||
lines += line.count;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
html,
|
|
||||||
lines
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function orderByLineNumber (a, b) {
|
|
||||||
if (a.start_line > b.start_line) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (a.start_line < b.start_line) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseLine (event) {
|
|
||||||
if (!event || !event.stdout) {
|
|
||||||
return { html: '', count: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
const { stdout } = event;
|
|
||||||
const lines = stdout.split('\r\n');
|
|
||||||
|
|
||||||
let count = lines.length;
|
|
||||||
let ln = event.start_line;
|
|
||||||
|
|
||||||
const current = createRecord(ln, lines, event);
|
|
||||||
|
|
||||||
const html = lines.reduce((html, line, i) => {
|
|
||||||
ln++;
|
|
||||||
|
|
||||||
const isLastLine = i === lines.length - 1;
|
|
||||||
let row = createRow(current, ln, line);
|
|
||||||
|
|
||||||
if (current && current.isTruncated && isLastLine) {
|
|
||||||
row += createRow(current);
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${html}${row}`;
|
|
||||||
}, '');
|
|
||||||
|
|
||||||
return { html, count };
|
|
||||||
}
|
|
||||||
|
|
||||||
function createRecord (ln, lines, event) {
|
|
||||||
if (!event.uuid) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const info = {
|
|
||||||
id: event.id,
|
|
||||||
line: ln + 1,
|
|
||||||
uuid: event.uuid,
|
|
||||||
level: event.event_level,
|
|
||||||
start: event.start_line,
|
|
||||||
end: event.end_line,
|
|
||||||
isTruncated: (event.end_line - event.start_line) > lines.length,
|
|
||||||
isHost: typeof event.host === 'number'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (event.parent_uuid) {
|
|
||||||
info.parents = getParentEvents(event.parent_uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.isTruncated) {
|
|
||||||
info.truncatedAt = event.start_line + lines.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EVENT_GROUPS.includes(event.event)) {
|
|
||||||
info.isParent = true;
|
|
||||||
|
|
||||||
if (event.event_level === 1) {
|
|
||||||
parent = event.uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.parent_uuid) {
|
|
||||||
if (record[event.parent_uuid]) {
|
|
||||||
if (record[event.parent_uuid].children &&
|
|
||||||
!record[event.parent_uuid].children.includes(event.uuid)) {
|
|
||||||
record[event.parent_uuid].children.push(event.uuid);
|
|
||||||
} else {
|
|
||||||
record[event.parent_uuid].children = [event.uuid];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TIME_EVENTS.includes(event.event)) {
|
|
||||||
info.time = getTime(event.created);
|
|
||||||
info.line++;
|
|
||||||
}
|
|
||||||
|
|
||||||
record[event.uuid] = info;
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getParentEvents (uuid, list) {
|
|
||||||
list = list || [];
|
|
||||||
|
|
||||||
if (record[uuid]) {
|
|
||||||
list.push(uuid);
|
|
||||||
|
|
||||||
if (record[uuid].parents) {
|
|
||||||
list = list.concat(record[uuid].parents);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createRow (current, ln, content) {
|
|
||||||
let id = '';
|
|
||||||
let timestamp = '';
|
|
||||||
let tdToggle = '';
|
|
||||||
let tdEvent = '';
|
|
||||||
let classList = '';
|
|
||||||
|
|
||||||
content = content || '';
|
|
||||||
|
|
||||||
if (hasAnsi(content)) {
|
|
||||||
content = ansi.toHtml(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current) {
|
|
||||||
if (current.isParent && current.line === ln) {
|
|
||||||
id = current.uuid;
|
|
||||||
tdToggle = `<td class="at-Stdout-toggle" ng-click="vm.toggle('${id}')"><i class="fa fa-angle-down can-toggle"></i></td>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current.isHost) {
|
|
||||||
tdEvent = `<td class="at-Stdout-event--host" ng-click="vm.showHostDetails('${current.id}')">${content}</td>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current.time && current.line === ln) {
|
|
||||||
timestamp = `<span>${current.time}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current.parents) {
|
|
||||||
classList = current.parents.reduce((list, uuid) => `${list} child-of-${uuid}`, '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tdEvent) {
|
|
||||||
tdEvent = `<td class="at-Stdout-event">${content}</td>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tdToggle) {
|
|
||||||
tdToggle = '<td class="at-Stdout-toggle"></td>';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ln) {
|
|
||||||
ln = '...';
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
|
||||||
<tr id="${id}" class="${classList}">
|
|
||||||
${tdToggle}
|
|
||||||
<td class="at-Stdout-line">${ln}</td>
|
|
||||||
${tdEvent}
|
|
||||||
<td class="at-Stdout-time">${timestamp}</td>
|
|
||||||
</tr>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTime (created) {
|
|
||||||
const date = new Date(created);
|
|
||||||
const hour = date.getHours() < 10 ? `0${date.getHours()}` : date.getHours();
|
|
||||||
const minute = date.getMinutes() < 10 ? `0${date.getMinutes()}` : date.getMinutes();
|
|
||||||
const second = date.getSeconds() < 10 ? `0${date.getSeconds()}` : date.getSeconds();
|
|
||||||
|
|
||||||
return `${hour}:${minute}:${second}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showHostDetails (id) {
|
function showHostDetails (id) {
|
||||||
jobEvent.request('get', id)
|
jobEvent.request('get', id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -527,100 +250,38 @@ function toggle (uuid, menu) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onScroll () {
|
|
||||||
if (vm.scroll.isActive) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vm.scroll.register) {
|
|
||||||
$timeout.cancel(vm.scroll.register);
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.scroll.register = $timeout(registerScrollEvent, SCROLL_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
function registerScrollEvent () {
|
|
||||||
vm.scroll.isActive = true;
|
|
||||||
|
|
||||||
const position = container[0].scrollTop;
|
|
||||||
const height = container[0].offsetHeight;
|
|
||||||
const downward = position > vm.scroll.position;
|
|
||||||
|
|
||||||
let promise;
|
|
||||||
|
|
||||||
if (position !== 0 ) {
|
|
||||||
vm.scroll.showBackToTop = true;
|
|
||||||
} else {
|
|
||||||
vm.scroll.showBackToTop = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
console.log('downward', downward);
|
|
||||||
if (downward) {
|
|
||||||
if (((height - position) / height) < SCROLL_THRESHOLD) {
|
|
||||||
promise = next;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ((position / height) < SCROLL_THRESHOLD) {
|
|
||||||
console.log('previous');
|
|
||||||
promise = previous;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.scroll.position = position;
|
|
||||||
|
|
||||||
if (!promise) {
|
|
||||||
vm.scroll.isActive = false;
|
|
||||||
|
|
||||||
return $q.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return promise()
|
|
||||||
.then(() => {
|
|
||||||
console.log('done');
|
|
||||||
vm.scroll.isActive = false;
|
|
||||||
/*
|
|
||||||
*$timeout(() => {
|
|
||||||
* vm.scroll.isActive = false;
|
|
||||||
*}, SCROLL_DELAY);
|
|
||||||
*/
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollHome () {
|
function scrollHome () {
|
||||||
|
scroll.pause();
|
||||||
|
|
||||||
return page.first()
|
return page.first()
|
||||||
.then(events => {
|
.then(events => {
|
||||||
if (!events) {
|
if (!events) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return clear()
|
return render.clear()
|
||||||
.then(() => prepend(events))
|
.then(() => render.prepend(events))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
vm.scroll.isActive = false;
|
scroll.setScrollPosition(0);
|
||||||
|
scroll.resume();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollEnd () {
|
function scrollEnd () {
|
||||||
if (vm.scroll.isLocked) {
|
if (scroll.isLocked()) {
|
||||||
page.bookmark();
|
page.bookmark();
|
||||||
|
scroll.unlock();
|
||||||
vm.scroll.isLocked = false;
|
|
||||||
vm.scroll.isActive = false;
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else if (!vm.scroll.isLocked && vm.stream.isActive) {
|
} else if (!scroll.isLocked() && vm.stream.active) {
|
||||||
page.bookmark();
|
page.bookmark();
|
||||||
|
scroll.lock();
|
||||||
vm.scroll.isActive = true;
|
|
||||||
vm.scroll.isLocked = true;
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.scroll.isActive = true;
|
scroll.pause();
|
||||||
|
|
||||||
return page.last()
|
return page.last()
|
||||||
.then(events => {
|
.then(events => {
|
||||||
@@ -628,36 +289,32 @@ function scrollEnd () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return clear()
|
return render.clear()
|
||||||
.then(() => append(events))
|
.then(() => render.append(events))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const container = $(ELEMENT_CONTAINER)[0];
|
scroll.setScrollPosition(scroll.getScrollHeight());
|
||||||
|
scroll.resume();
|
||||||
container.scrollTop = container.scrollHeight;
|
|
||||||
vm.scroll.isActive = false;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollPageUp () {
|
function scrollPageUp () {
|
||||||
const container = $(ELEMENT_CONTAINER)[0];
|
scroll.pageUp();
|
||||||
const jump = container.scrollTop - container.offsetHeight;
|
|
||||||
|
|
||||||
container.scrollTop = jump;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollPageDown () {
|
function scrollPageDown () {
|
||||||
const container = $(ELEMENT_CONTAINER)[0];
|
scroll.pageDown();
|
||||||
const jump = container.scrollTop + container.offsetHeight;
|
}
|
||||||
|
|
||||||
container.scrollTop = jump;
|
function scrollIsAtRest (isAtRest) {
|
||||||
|
vm.scroll.showBackToTop = !isAtRest;
|
||||||
}
|
}
|
||||||
|
|
||||||
JobsIndexController.$inject = [
|
JobsIndexController.$inject = [
|
||||||
'resource',
|
'resource',
|
||||||
'JobPageService',
|
'JobPageService',
|
||||||
'$sce',
|
'JobScrollService',
|
||||||
'$timeout',
|
'JobRenderService',
|
||||||
'$scope',
|
'$scope',
|
||||||
'$compile',
|
'$compile',
|
||||||
'$q'
|
'$q'
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import JobsStrings from '~features/output/jobs.strings';
|
|
||||||
import IndexController from '~features/output/index.controller';
|
|
||||||
import atLibModels from '~models';
|
import atLibModels from '~models';
|
||||||
import atLibComponents from '~components';
|
import atLibComponents from '~components';
|
||||||
|
|
||||||
import Strings from '~features/output/jobs.strings';
|
import Strings from '~features/output/jobs.strings';
|
||||||
import Controller from '~features/output/index.controller';
|
import Controller from '~features/output/index.controller';
|
||||||
import PageService from '~features/output/page.service';
|
import PageService from '~features/output/page.service';
|
||||||
|
import ScrollService from '~features/output/scroll.service';
|
||||||
|
|
||||||
const Template = require('~features/output/index.view.html');
|
const Template = require('~features/output/index.view.html');
|
||||||
|
|
||||||
@@ -178,6 +177,7 @@ angular
|
|||||||
])
|
])
|
||||||
.service('JobStrings', Strings)
|
.service('JobStrings', Strings)
|
||||||
.service('JobPageService', PageService)
|
.service('JobPageService', PageService)
|
||||||
|
.service('JobScrollService', ScrollService)
|
||||||
.run(JobsRun);
|
.run(JobsRun);
|
||||||
|
|
||||||
export default MODULE_NAME;
|
export default MODULE_NAME;
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
function JobPageService ($q) {
|
function JobPageService ($q) {
|
||||||
this.page = null;
|
|
||||||
this.resource = null;
|
|
||||||
this.result = null;
|
|
||||||
this.buffer = null;
|
|
||||||
this.cache = null;
|
|
||||||
|
|
||||||
this.init = resource => {
|
this.init = resource => {
|
||||||
this.resource = resource;
|
this.resource = resource;
|
||||||
|
|
||||||
this.page = {
|
this.page = {
|
||||||
limit: resource.page.pageLimit,
|
limit: this.resource.page.pageLimit,
|
||||||
size: resource.page.size,
|
size: this.resource.page.size,
|
||||||
current: 0,
|
current: 0,
|
||||||
index: -1,
|
index: -1,
|
||||||
count: 0,
|
count: 0,
|
||||||
@@ -153,13 +147,10 @@ function JobPageService ($q) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.bookmark = () => {
|
this.bookmark = () => {
|
||||||
console.log('b,current', this.page.current);
|
|
||||||
if (!this.page.bookmark.active) {
|
if (!this.page.bookmark.active) {
|
||||||
this.page.bookmark.first = this.page.first;
|
this.page.bookmark.first = this.page.first;
|
||||||
this.page.bookmark.last = this.page.last;
|
this.page.bookmark.last = this.page.last;
|
||||||
this.page.bookmark.current = this.page.current;
|
this.page.bookmark.current = this.page.current;
|
||||||
|
|
||||||
console.log('b,bookmark', this.page.bookmark.current);
|
|
||||||
this.page.bookmark.active = true;
|
this.page.bookmark.active = true;
|
||||||
} else {
|
} else {
|
||||||
this.page.bookmark.active = false;
|
this.page.bookmark.active = false;
|
||||||
|
|||||||
@@ -0,0 +1,297 @@
|
|||||||
|
import Ansi from 'ansi-to-html';
|
||||||
|
import hasAnsi from 'has-ansi';
|
||||||
|
|
||||||
|
const ELEMENT_TBODY = '#atStdoutResultTable';
|
||||||
|
const EVENT_START_TASK = 'playbook_on_task_start';
|
||||||
|
const EVENT_START_PLAY = 'playbook_on_play_start';
|
||||||
|
const EVENT_STATS_PLAY = 'playbook_on_stats';
|
||||||
|
const JOB_START = 'playbook_on_start';
|
||||||
|
const JOB_END = 'playbook_on_stats';
|
||||||
|
|
||||||
|
const EVENT_GROUPS = [
|
||||||
|
EVENT_START_TASK,
|
||||||
|
EVENT_START_PLAY
|
||||||
|
];
|
||||||
|
|
||||||
|
const TIME_EVENTS = [
|
||||||
|
EVENT_START_TASK,
|
||||||
|
EVENT_START_PLAY,
|
||||||
|
EVENT_STATS_PLAY
|
||||||
|
];
|
||||||
|
|
||||||
|
const ansi = new Ansi();
|
||||||
|
|
||||||
|
function JobRenderService ($q, $sce, $window) {
|
||||||
|
this.init = ({ compile, apply, get }) => {
|
||||||
|
this.parent = null;
|
||||||
|
this.record = {};
|
||||||
|
this.el = $(ELEMENT_TBODY);
|
||||||
|
this.hooks = { get, compile, apply };
|
||||||
|
};
|
||||||
|
|
||||||
|
this.sortByLineNumber = (a, b) => {
|
||||||
|
if (a.start_line > b.start_line) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.start_line < b.start_line) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.transformEventGroup = events => {
|
||||||
|
let lines = 0;
|
||||||
|
let html = '';
|
||||||
|
|
||||||
|
events.sort(this.sortByLineNumber);
|
||||||
|
|
||||||
|
events.forEach(event => {
|
||||||
|
const line = this.transformEvent(event);
|
||||||
|
|
||||||
|
html += line.html;
|
||||||
|
lines += line.count;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { html, lines };
|
||||||
|
};
|
||||||
|
|
||||||
|
this.transformEvent = event => {
|
||||||
|
if (!event || !event.stdout) {
|
||||||
|
return { html: '', count: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { stdout } = event;
|
||||||
|
const lines = stdout.split('\r\n');
|
||||||
|
|
||||||
|
let count = lines.length;
|
||||||
|
let ln = event.start_line;
|
||||||
|
|
||||||
|
const current = this.createRecord(ln, lines, event);
|
||||||
|
|
||||||
|
const html = lines.reduce((html, line, i) => {
|
||||||
|
ln++;
|
||||||
|
|
||||||
|
const isLastLine = i === lines.length - 1;
|
||||||
|
let row = this.createRow(current, ln, line);
|
||||||
|
|
||||||
|
if (current && current.isTruncated && isLastLine) {
|
||||||
|
row += this.createRow(current);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${html}${row}`;
|
||||||
|
}, '');
|
||||||
|
|
||||||
|
return { html, count };
|
||||||
|
};
|
||||||
|
|
||||||
|
this.createRecord = event => {
|
||||||
|
if (!event.uuid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = {
|
||||||
|
id: event.id,
|
||||||
|
line: ln + 1,
|
||||||
|
uuid: event.uuid,
|
||||||
|
level: event.event_level,
|
||||||
|
start: event.start_line,
|
||||||
|
end: event.end_line,
|
||||||
|
isTruncated: (event.end_line - event.start_line) > lines.length,
|
||||||
|
isHost: typeof event.host === 'number'
|
||||||
|
};
|
||||||
|
|
||||||
|
if (event.parent_uuid) {
|
||||||
|
info.parents = getParentEvents(event.parent_uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.isTruncated) {
|
||||||
|
info.truncatedAt = event.start_line + lines.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EVENT_GROUPS.includes(event.event)) {
|
||||||
|
info.isParent = true;
|
||||||
|
|
||||||
|
if (event.event_level === 1) {
|
||||||
|
this.parent = event.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.parent_uuid) {
|
||||||
|
if (this.record[event.parent_uuid]) {
|
||||||
|
if (this.record[event.parent_uuid].children &&
|
||||||
|
!this.record[event.parent_uuid].children.includes(event.uuid)) {
|
||||||
|
this.record[event.parent_uuid].children.push(event.uuid);
|
||||||
|
} else {
|
||||||
|
this.record[event.parent_uuid].children = [event.uuid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TIME_EVENTS.includes(event.event)) {
|
||||||
|
info.time = this.getTimestamp(event.created);
|
||||||
|
info.line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.record[event.uuid] = info;
|
||||||
|
|
||||||
|
return info;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.createRow = (current, ln, content) => {
|
||||||
|
let id = '';
|
||||||
|
let timestamp = '';
|
||||||
|
let tdToggle = '';
|
||||||
|
let tdEvent = '';
|
||||||
|
let classList = '';
|
||||||
|
|
||||||
|
content = content || '';
|
||||||
|
|
||||||
|
if (hasAnsi(content)) {
|
||||||
|
content = ansi.toHtml(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current) {
|
||||||
|
if (current.isParent && current.line === ln) {
|
||||||
|
id = current.uuid;
|
||||||
|
tdToggle = `<td class="at-Stdout-toggle" ng-click="vm.toggle('${id}')"><i class="fa fa-angle-down can-toggle"></i></td>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.isHost) {
|
||||||
|
tdEvent = `<td class="at-Stdout-event--host" ng-click="vm.showHostDetails('${current.id}')">${content}</td>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.time && current.line === ln) {
|
||||||
|
timestamp = `<span>${current.time}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.parents) {
|
||||||
|
classList = current.parents.reduce((list, uuid) => `${list} child-of-${uuid}`, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tdEvent) {
|
||||||
|
tdEvent = `<td class="at-Stdout-event">${content}</td>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tdToggle) {
|
||||||
|
tdToggle = '<td class="at-Stdout-toggle"></td>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ln) {
|
||||||
|
ln = '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<tr id="${id}" class="${classList}">
|
||||||
|
${tdToggle}
|
||||||
|
<td class="at-Stdout-line">${ln}</td>
|
||||||
|
${tdEvent}
|
||||||
|
<td class="at-Stdout-time">${timestamp}</td>
|
||||||
|
</tr>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getTimestamp = (created) => {
|
||||||
|
const date = new Date(created);
|
||||||
|
const hour = date.getHours() < 10 ? `0${date.getHours()}` : date.getHours();
|
||||||
|
const minute = date.getMinutes() < 10 ? `0${date.getMinutes()}` : date.getMinutes();
|
||||||
|
const second = date.getSeconds() < 10 ? `0${date.getSeconds()}` : date.getSeconds();
|
||||||
|
|
||||||
|
return `${hour}:${minute}:${second}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getParentEvents = (uuid, list) => {
|
||||||
|
list = list || [];
|
||||||
|
|
||||||
|
if (this.record[uuid]) {
|
||||||
|
list.push(uuid);
|
||||||
|
|
||||||
|
if (this.record[uuid].parents) {
|
||||||
|
list = list.concat(record[uuid].parents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getEvents = () => {
|
||||||
|
return this.hooks.get();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.insert = (events, insert) => {
|
||||||
|
const result = this.transformEventGroup(events);
|
||||||
|
const html = this.sanitize(result.html);
|
||||||
|
|
||||||
|
return this.requestAnimationFrame(() => insert(html))
|
||||||
|
.then(() => this.compile(html))
|
||||||
|
.then(() => result.lines);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.remove = elements => {
|
||||||
|
return this.requestAnimationFrame(() => {
|
||||||
|
elements.empty();
|
||||||
|
elements.remove();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.requestAnimationFrame = fn => {
|
||||||
|
return $q(resolve => {
|
||||||
|
$window.requestAnimationFrame(() => {
|
||||||
|
if (fn) {
|
||||||
|
fn();
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.compile = html => {
|
||||||
|
this.hooks.compile(html);
|
||||||
|
|
||||||
|
return this.requestAnimationFrame();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.build = () => {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
this.clear = () => {
|
||||||
|
const elements = this.el.children();
|
||||||
|
|
||||||
|
return this.remove(elements);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.shift = lines => {
|
||||||
|
const elements = this.el.children().slice(0, lines);
|
||||||
|
|
||||||
|
return this.remove(elements);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pop = lines => {
|
||||||
|
const elements = this.el.children().slice(-lines);
|
||||||
|
|
||||||
|
return this.remove(elements);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.prepend = events => {
|
||||||
|
return this.insert(events, html => this.el.prepend(html))
|
||||||
|
};
|
||||||
|
|
||||||
|
this.append = events => {
|
||||||
|
return this.insert(events, html => this.el.append(html))
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: stdout from the API should not be trusted.
|
||||||
|
this.sanitize = html => {
|
||||||
|
html = $sce.trustAsHtml(html);
|
||||||
|
|
||||||
|
return $sce.getTrustedHtml(html);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
JobRenderService.$inject = ['$q', '$sce', '$window'];
|
||||||
|
|
||||||
|
export default JobRenderService;
|
||||||
|
|||||||
172
awx/ui/client/features/output/scroll.service.js
Normal file
172
awx/ui/client/features/output/scroll.service.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
const ELEMENT_CONTAINER = '.at-Stdout-container';
|
||||||
|
const DELAY = 100;
|
||||||
|
const THRESHOLD = 0.1;
|
||||||
|
|
||||||
|
function JobScrollService ($q, $timeout) {
|
||||||
|
this.init = (hooks) => {
|
||||||
|
this.el = $(ELEMENT_CONTAINER);
|
||||||
|
this.timer = null;
|
||||||
|
|
||||||
|
this.position = {
|
||||||
|
previous: 0,
|
||||||
|
current: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
this.hooks = {
|
||||||
|
isAtRest: hooks.isAtRest,
|
||||||
|
next: hooks.next,
|
||||||
|
previous: hooks.previous
|
||||||
|
};
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
locked: false,
|
||||||
|
paused: false,
|
||||||
|
top: true
|
||||||
|
};
|
||||||
|
|
||||||
|
this.el.scroll(this.listen);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.listen = () => {
|
||||||
|
if (this.isPaused()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.timer) {
|
||||||
|
$timeout.cancel(this.timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timer = $timeout(this.register, DELAY);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.register = () => {
|
||||||
|
this.pause();
|
||||||
|
|
||||||
|
const height = this.getScrollHeight();
|
||||||
|
const current = this.getScrollPosition();
|
||||||
|
const downward = current > this.position.previous;
|
||||||
|
|
||||||
|
let promise;
|
||||||
|
|
||||||
|
if (downward && this.isBeyondThreshold(downward, current)) {
|
||||||
|
promise = this.hooks.next;
|
||||||
|
} else if (!downward && this.isBeyondThreshold(downward, current)) {
|
||||||
|
promise = this.hooks.previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!promise) {
|
||||||
|
this.setScrollPosition(current);
|
||||||
|
this.isAtRest();
|
||||||
|
this.resume();
|
||||||
|
|
||||||
|
return $q.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise()
|
||||||
|
.then(() => {
|
||||||
|
this.setScrollPosition(this.getScrollPosition());
|
||||||
|
this.isAtRest();
|
||||||
|
this.resume();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isBeyondThreshold = (downward, current) => {
|
||||||
|
const previous = this.position.previous;
|
||||||
|
const height = this.getScrollHeight();
|
||||||
|
|
||||||
|
if (downward) {
|
||||||
|
current += this.getViewableHeight();
|
||||||
|
|
||||||
|
if (current >= height || ((height - current) / height) < THRESHOLD) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (current <= 0 || (current / height) < THRESHOLD) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pageUp = () => {
|
||||||
|
if (this.isPaused()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const top = this.getScrollPosition();
|
||||||
|
const height = this.getViewableHeight();
|
||||||
|
|
||||||
|
this.setScrollPosition(top - height);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pageDown = () => {
|
||||||
|
if (this.isPaused()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const top = this.getScrollPosition();
|
||||||
|
const height = this.getViewableHeight();
|
||||||
|
|
||||||
|
this.setScrollPosition(top + height);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getScrollHeight = () => {
|
||||||
|
return this.el[0].scrollHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getViewableHeight = () => {
|
||||||
|
return this.el[0].offsetHeight;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getScrollPosition = () => {
|
||||||
|
return this.el[0].scrollTop;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setScrollPosition = position => {
|
||||||
|
this.position.previous = this.position.current;
|
||||||
|
this.position.current = position;
|
||||||
|
this.el[0].scrollTop = position;
|
||||||
|
this.isAtRest();
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isAtRest = () => {
|
||||||
|
if (this.position.current === 0 && !this.state.top) {
|
||||||
|
this.state.top = true;
|
||||||
|
this.hooks.isAtRest(true);
|
||||||
|
} else if (this.position.current > 0 && this.state.top) {
|
||||||
|
this.state.top = false;
|
||||||
|
this.hooks.isAtRest(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.resume = () => {
|
||||||
|
this.state.paused = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pause = () => {
|
||||||
|
this.state.paused = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isPaused = () => {
|
||||||
|
return this.state.paused;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.lock = () => {
|
||||||
|
this.state.locked = true;
|
||||||
|
this.state.paused = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.unlock = () => {
|
||||||
|
this.state.locked = false;
|
||||||
|
this.state.paused = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isLocked = () => {
|
||||||
|
return this.state.locked;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
JobScrollService.$inject = ['$q', '$timeout'];
|
||||||
|
|
||||||
|
export default JobScrollService;
|
||||||
Reference in New Issue
Block a user