in kahuna/public/js/components/gu-lazy-table/gu-lazy-table.js [58:193]
ctrl.init = function({items$, preloadedRows$, cellHeight$, cellMinWidth$,
containerWidth$, viewportHeight$, viewportTop$}) {
const itemsCount$ = items$.map(items => items.length).distinctUntilChanged();
const columns$ = max$(floor$(div$(containerWidth$, cellMinWidth$)), 1);
const rows$ = ceil$(div$(itemsCount$, columns$));
const cellWidth$ = floor$(div$(containerWidth$, columns$));
const viewportBottom$ = add$(viewportTop$, viewportHeight$);
const currentRowTop$ = round$(div$(viewportTop$, cellHeight$));
const currentRowBottom$ = round$(div$(viewportBottom$, cellHeight$));
const loadedRowTop$ = max$(sub$(currentRowTop$, preloadedRows$), 0).
distinctUntilChanged();
const loadedRowBottom$ = combine$(currentRowBottom$, preloadedRows$, rows$,
(currentRowBottom, preloadedRows, rows) => {
return Math.min(currentRowBottom + preloadedRows, rows - 1);
}).distinctUntilChanged();
const loadedRangeStart$ = mult$(loadedRowTop$, columns$);
const loadedRangeEnd$ = combine$(loadedRowBottom$, columns$, itemsCount$,
(loadedRowBottom, columns, itemsCount) => {
const endRowItemIndex = ((loadedRowBottom + 1) * columns) - 1;
return Math.min(Math.max(endRowItemIndex, 0), itemsCount);
});
const rangeToLoad$ = combine$(
items$, loadedRangeStart$, loadedRangeEnd$,
(items, loadedRangeStart, loadedRangeEnd) => {
const $start = findIndexFrom(items, undefined, loadedRangeStart);
const $end = findLastIndexFrom(items, undefined, loadedRangeEnd);
return {$start, $end};
}
).
// Debounce range loading, which also helps discard
// erroneous large ranges while combining
// loadedRangeStart$ and loadedRangeEnd$ changes (one after the other)
debounce(10).
// Ignore if either end isn't set (whole range already loaded)
filter(({$start, $end}) => $start !== -1 && $end !== -1).
// Ignore if $start after $end (incomplete combine$ state)
filter(({$start, $end}) => $start <= $end).
// Max query size
map(({$start, $end})=> {
if (($end - $start) < maxSize) {
return {$start, $end};
}
return {$start, $end: $start + maxSize - 1};
}).
distinctUntilChanged(({$start, $end}) => `${$start}-${$end}`);
// Placeholders
const placeholderExtraCount$ = mult$(columns$, preloadedRows$);
const placeholderRangeStart$ = max$(sub$(loadedRangeStart$, placeholderExtraCount$), 0);
const placeholderRangeEnd$ = min$(add$(loadedRangeEnd$, placeholderExtraCount$),
sub$(itemsCount$, 1));
const placeholderIndexes$ = combine$(
placeholderRangeStart$, placeholderRangeEnd$,
(placeholderRangeStart, placeholderRangeEnd) => {
let indexes = range(placeholderRangeStart, placeholderRangeEnd);
return Array.from(indexes);
}
);
const viewHeight$ = mult$(rows$, cellHeight$);
// Mutations needed here to access streams in this closure ;_;
// Share subscriptions to these streams between all cells and
// placeholders that register to their position
const itemsShared$ = items$.shareReplay(1);
const cellWidthShared$ = cellWidth$.shareReplay(1);
const cellHeightShared$ = cellHeight$.shareReplay(1);
const columnsShared$ = columns$.shareReplay(1);
const preloadedRowsShared$ = preloadedRows$.shareReplay(1);
const viewportTopShared$ = viewportTop$.shareReplay(1);
const viewportBottomShared$ = viewportBottom$.shareReplay(1);
ctrl.getItemPosition$ = createGetItemPosition$({
items$: itemsShared$,
cellWidth$: cellWidthShared$,
cellHeight$: cellHeightShared$,
columns$: columnsShared$,
preloadedRows$: preloadedRowsShared$,
viewportTop$: viewportTopShared$,
viewportBottom$: viewportBottomShared$
});
ctrl.getCellPosition$ = createGetCellPosition$({
cellWidth$: cellWidthShared$,
cellHeight$: cellHeightShared$,
columns$: columnsShared$,
preloadedRows$: preloadedRowsShared$,
viewportTop$: viewportTopShared$,
viewportBottom$: viewportBottomShared$
});
const rowsInViewport$ = max$(floor$(div$(viewportHeight$, cellHeight$)), 1);
// Marginally satisfying pattern to convert from imperative to
// reactive land
const scrollCommands$ = Rx.Observable.create(observer => {
ctrl.scrollPrevRow = () => observer.onNext('prevRow');
ctrl.scrollNextRow = () => observer.onNext('nextRow');
ctrl.scrollPrevPage = () => observer.onNext('prevPage');
ctrl.scrollNextPage = () => observer.onNext('nextPage');
ctrl.scrollStart = () => observer.onNext('start');
ctrl.scrollEnd = () => observer.onNext('end');
});
const rowOffset$ = scrollCommands$.withLatestFrom(
rowsInViewport$, currentRowTop$, rows$,
(command, rowsInViewport, currentRowTop, rows) => {
return {
prevRow: - 1,
nextRow: + 1,
prevPage: - rowsInViewport,
nextPage: + rowsInViewport,
start: - currentRowTop,
end: rows - currentRowTop
}[command] || 0;
});
const newScrollTop$ = rowOffset$.withLatestFrom(currentRowTop$, cellHeight$,
(rowOffset, currentRowTop, cellHeight) => {
return (currentRowTop + rowOffset) * cellHeight;
});
return {
viewHeight$, rangeToLoad$, placeholderIndexes$, newScrollTop$
};
};