redoc/lib/shared/components/LazyFor/lazy-for.ts

173 lines
4.5 KiB
TypeScript
Raw Permalink Normal View History

2016-11-23 02:23:32 +03:00
'use strict';
import {
Directive,
Input,
TemplateRef,
ChangeDetectorRef,
ViewContainerRef,
2016-12-02 12:59:29 +03:00
Injectable
2016-11-23 02:23:32 +03:00
} from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { ScrollService } from '../../../services/scroll.service';
import { OptionsService } from '../../../services/options.service';
import { isSafari } from '../../../utils/helpers';
export class LazyForRow {
2016-12-25 15:24:58 +03:00
constructor(public $implicit: any, public index: number, public ready: boolean) {}
2016-11-23 02:23:32 +03:00
get first(): boolean { return this.index === 0; }
get even(): boolean { return this.index % 2 === 0; }
get odd(): boolean { return !this.even; }
}
@Injectable()
export class LazyTasksService {
private _tasks = [];
private _current: number = 0;
private _syncCount: number = 0;
private _emptyProcessed = false;
private menuService;
2016-11-23 02:23:32 +03:00
public loadProgress = new BehaviorSubject<number>(0);
public allSync = false;
2016-12-02 12:59:29 +03:00
constructor(public optionsService: OptionsService) {
2016-11-23 02:23:32 +03:00
}
get processed() {
let res = this._tasks.length && (this._current >= this._tasks.length) || this._emptyProcessed;
if (!this._tasks.length) this._emptyProcessed = true;
return res;
2016-11-23 02:23:32 +03:00
}
2016-11-24 16:29:29 +03:00
2016-11-23 02:23:32 +03:00
set syncCount(n: number) {
this._syncCount = n;
}
2016-11-24 16:29:29 +03:00
set lazy(sync:boolean) {
this.allSync = sync;
}
2016-11-23 02:23:32 +03:00
addTasks(tasks:any[], callback:Function) {
tasks.forEach((task, idx) => {
let taskCopy = Object.assign({_callback: callback, idx: idx}, task);
2016-11-23 02:23:32 +03:00
this._tasks.push(taskCopy);
});
}
nextTaskSync() {
2016-11-24 16:29:29 +03:00
let task = this._tasks[this._current];
if (!task) return;
task._callback(task.idx, true);
this._current++;
this.menuService.enableItem(task.flatIdx);
2016-11-24 16:29:29 +03:00
this.loadProgress.next(this._current / this._tasks.length * 100);
2016-11-23 02:23:32 +03:00
}
nextTask() {
requestAnimationFrame(() => {
let task = this._tasks[this._current];
if (!task) return;
task._callback(task.idx, false).then(() => {
this._current++;
this.menuService.enableItem(task.flatIdx);
2016-11-23 02:23:32 +03:00
setTimeout(()=> this.nextTask());
this.loadProgress.next(this._current / this._tasks.length * 100);
2016-11-24 16:29:29 +03:00
}).catch(err => console.error(err));
2016-11-23 02:23:32 +03:00
});
}
sortTasks(center) {
2016-11-23 02:23:32 +03:00
let idxMap = {};
this._tasks.sort((a, b) => {
return Math.abs(a.flatIdx - center) - Math.abs(b.flatIdx - center);
2016-11-23 02:23:32 +03:00
})
}
start(idx, menuService) {
this.menuService = menuService;
2016-11-23 02:23:32 +03:00
let syncCount = 5;
// I know this is a bad practice to detect browsers but there is an issue in Safari only
2016-11-23 02:23:32 +03:00
// http://stackoverflow.com/questions/40692365/maintaining-scroll-position-while-inserting-elements-above-glitching-only-in-sa
if (isSafari && this.optionsService.options.$scrollParent === window) {
2016-12-25 18:15:01 +03:00
syncCount = this._tasks.findIndex(task => task.flatIdx === idx);
2016-11-23 02:23:32 +03:00
syncCount += 1;
} else {
this.sortTasks(idx);
2016-11-23 02:23:32 +03:00
}
syncCount = Math.min(syncCount, this._tasks.length);
2016-11-23 02:23:32 +03:00
if (this.allSync) syncCount = this._tasks.length;
2016-11-24 16:29:29 +03:00
for (var i = this._current; i < syncCount; i++) {
2016-11-23 02:23:32 +03:00
this.nextTaskSync();
}
if (!this._tasks.length) {
this.loadProgress.next(100);
return;
}
2016-11-23 02:23:32 +03:00
this.nextTask();
}
}
2016-11-24 16:29:29 +03:00
@Injectable()
export class LazyTasksServiceSync extends LazyTasksService {
2016-12-02 12:59:29 +03:00
constructor(optionsService: OptionsService) {
super(optionsService);
2016-11-24 16:29:29 +03:00
this.allSync = true;
}
}
2016-11-23 02:23:32 +03:00
@Directive({
selector: '[lazyFor][lazyForOf]'
})
export class LazyFor {
@Input() lazyForOf: any;
prevIdx = null;
constructor(
public _template: TemplateRef<LazyForRow>,
public cdr: ChangeDetectorRef,
public _viewContainer: ViewContainerRef,
public lazyTasks: LazyTasksService,
public scroll: ScrollService
){
}
nextIteration(idx: number, sync: boolean):Promise<void> {
const view = this._viewContainer.createEmbeddedView(this._template,
new LazyForRow(this.lazyForOf[idx], idx, sync), idx < this.prevIdx ? 0 : undefined);
2016-11-23 02:23:32 +03:00
this.prevIdx = idx;
view.context.index = idx;
(<any>view as ChangeDetectorRef).markForCheck();
(<any>view as ChangeDetectorRef).detectChanges();
if (sync) {
return Promise.resolve();
}
2016-12-02 12:59:29 +03:00
return new Promise<void>(resolve => {
2016-11-23 02:23:32 +03:00
requestAnimationFrame(() => {
this.scroll.saveScroll();
2016-12-25 15:24:58 +03:00
view.context.ready = true;
2016-11-23 02:23:32 +03:00
(<any>view as ChangeDetectorRef).markForCheck();
(<any>view as ChangeDetectorRef).detectChanges();
this.scroll.restoreScroll();
resolve();
});
});
}
ngOnInit() {
if (!this.lazyForOf) return;
2016-11-23 02:23:32 +03:00
this.lazyTasks.addTasks(this.lazyForOf, this.nextIteration.bind(this))
}
}