vscroll
Version:
Virtual scroll engine
536 lines • 20.8 kB
JavaScript
import { __assign, __read } from "tslib";
import { Reactive } from './reactive';
import { AdapterPropName, AdapterPropType, EMPTY_ITEM, getDefaultAdapterProps, methodPausedResult, methodPreResult, reactiveConfigStorage } from './adapter/props';
import { wantedUtils } from './adapter/wanted';
import { Direction } from '../inputs/index';
import { AdapterProcess, ProcessStatus } from '../processes/index';
var ADAPTER_PROPS_STUB = getDefaultAdapterProps();
var ALLOWED_METHODS_WHEN_PAUSED = ADAPTER_PROPS_STUB.filter(function (v) { return !!v.allowedWhenPaused; }).map(function (v) { return v.name; });
var _has = function (obj, prop) {
return !!obj && typeof obj === 'object' && Object.prototype.hasOwnProperty.call(obj, prop);
};
var convertAppendArgs = function (prepend, options, eof) {
var result = options;
if (!_has(options, 'items')) {
var items = !Array.isArray(options) ? [options] : options;
result = prepend ? { items: items, bof: eof } : { items: items, eof: eof };
}
return result;
};
var convertRemoveArgs = function (options) {
if (!(_has(options, 'predicate') || _has(options, 'indexes'))) {
var predicate = options;
options = { predicate: predicate };
}
return options;
};
var Adapter = /** @class */ (function () {
function Adapter(context, getWorkflow, logger) {
var _this = this;
this.source = {}; // for Reactive props
this.box = {}; // for Scalars over Reactive props
this.demand = {}; // for Scalars on demand
// eslint-disable-next-line @typescript-eslint/no-unused-vars
this.setFirstOrLastVisible = function (_) { };
this.getWorkflow = getWorkflow;
this.logger = logger;
this.relax$ = null;
this.relaxRun = null;
this.reloadCounter = 0;
var contextId = (context === null || context === void 0 ? void 0 : context.id) || -1;
// public context (if exists) should provide access to Reactive props config by id
var reactivePropsStore = context && reactiveConfigStorage.get(context.id) || {};
// the Adapter initialization should not trigger "wanted" props setting;
// after the initialization is completed, "wanted" functionality must be unblocked
wantedUtils.setBlock(true, contextId);
// make array of the original values from public context if present
var adapterProps = context
? ADAPTER_PROPS_STUB.map(function (prop) {
var value = context[prop.name];
// if context is augmented, we need to replace external reactive props with inner ones
if (context.augmented) {
var reactiveProp = reactivePropsStore[prop.name];
if (reactiveProp) {
value = reactiveProp.default; // boolean doesn't matter here
}
}
return (__assign(__assign({}, prop), { value: value }));
})
: getDefaultAdapterProps();
// restore default reactive props if they were configured
Object.entries(reactivePropsStore).forEach(function (_a) {
var _b = __read(_a, 2), key = _b[0], value = _b[1];
var prop = adapterProps.find(function (_a) {
var name = _a.name;
return name === key;
});
if (prop && value) {
prop.value = value.default;
}
});
// Scalar permanent props
adapterProps
.filter(function (_a) {
var type = _a.type, permanent = _a.permanent;
return type === AdapterPropType.Scalar && permanent;
})
.forEach(function (_a) {
var name = _a.name, value = _a.value;
return Object.defineProperty(_this, name, {
configurable: true,
get: function () { return value; }
});
});
// Reactive props: store original values in "source" container, to avoid extra .get() calls on scalar twins set
adapterProps
.filter(function (prop) { return prop.type === AdapterPropType.Reactive; })
.forEach(function (_a) {
var name = _a.name, value = _a.value;
_this.source[name] = value;
Object.defineProperty(_this, name, {
configurable: true,
get: function () { return _this.source[name]; }
});
});
// for "wanted" props that can be explicitly requested for the first time after the Adapter initialization,
// an implicit calculation of the initial value is required;
// so this method should be called when accessing the "wanted" props through one of the following getters
var processWanted = function (prop) {
if (wantedUtils.setBox(prop, contextId)) {
if ([AdapterPropName.firstVisible, AdapterPropName.firstVisible$].some(function (n) { return n === prop.name; })) {
_this.setFirstOrLastVisible({ first: true });
}
else if ([AdapterPropName.lastVisible, AdapterPropName.lastVisible$].some(function (n) { return n === prop.name; })) {
_this.setFirstOrLastVisible({ last: true });
}
}
};
// Scalar props that have Reactive twins
// 1) reactive props (from "source") should be triggered on set
// 2) scalars should use "box" container on get
// 3) "wanted" scalars should also run wanted-related logic on get
adapterProps
.filter(function (prop) { return prop.type === AdapterPropType.Scalar && !!prop.reactive; })
.forEach(function (prop) {
var name = prop.name, value = prop.value, reactive = prop.reactive;
_this.box[name] = value;
Object.defineProperty(_this, name, {
configurable: true,
set: function (newValue) {
if (newValue !== _this.box[name]) {
_this.box[name] = newValue;
_this.source[reactive].set(newValue);
// need to emit new value through the configured reactive prop if present
var reactiveProp = reactivePropsStore[reactive];
if (reactiveProp) {
reactiveProp.emit(reactiveProp.source, newValue);
}
}
},
get: function () {
processWanted(prop);
return _this.box[name];
}
});
});
// Scalar props on-demand
// these scalars should use "demand" container
// setting defaults should be overridden on init()
adapterProps
.filter(function (prop) { return prop.type === AdapterPropType.Scalar && prop.onDemand; })
.forEach(function (_a) {
var name = _a.name, value = _a.value;
_this.demand[name] = value;
Object.defineProperty(_this, name, {
configurable: true,
get: function () { return _this.demand[name]; }
});
});
if (!context) {
return;
}
// Adapter public context augmentation
adapterProps
.forEach(function (prop) {
var name = prop.name, type = prop.type, permanent = prop.permanent;
var value = _this[name];
if (type === AdapterPropType.Function) {
value = value.bind(_this);
}
else if (type === AdapterPropType.WorkflowRunner) {
value = _this.getWorkflowRunnerMethod(value, name);
}
else if (type === AdapterPropType.Reactive && reactivePropsStore[name]) {
value = context[name];
}
else if (name === AdapterPropName.augmented) {
value = true;
}
var nonPermanentScalar = !permanent && type === AdapterPropType.Scalar;
Object.defineProperty(context, name, {
configurable: true,
get: function () {
processWanted(prop); // consider accessing "wanted" Reactive props
if (nonPermanentScalar) {
return _this[name]; // non-permanent Scalars should be taken in runtime
}
return value; // other props (Reactive/Functions/WorkflowRunners) can be defined once
}
});
});
this.externalContext = context;
wantedUtils.setBlock(false, contextId);
}
Object.defineProperty(Adapter.prototype, "workflow", {
get: function () {
return this.getWorkflow();
},
enumerable: false,
configurable: true
});
Object.defineProperty(Adapter.prototype, "reloadCount", {
get: function () {
return this.reloadCounter;
},
enumerable: false,
configurable: true
});
Object.defineProperty(Adapter.prototype, "reloadId", {
get: function () {
return this.id + '.' + this.reloadCounter;
},
enumerable: false,
configurable: true
});
Adapter.prototype.getPromisifiedMethod = function (method, args) {
var _this = this;
return new Promise(function (resolve) {
if (_this.relax$) {
_this.relax$.once(function (value) { return resolve(value); });
}
method.apply(_this, args);
});
};
Adapter.prototype.getWorkflowRunnerMethod = function (method, name) {
var _this = this;
return function () {
var _a, _b, _c, _d;
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
if (!_this.relax$) {
(_b = (_a = _this.logger) === null || _a === void 0 ? void 0 : _a.log) === null || _b === void 0 ? void 0 : _b.call(_a, function () { return 'scroller is not initialized: ' + name + ' method is ignored'; });
return Promise.resolve(methodPreResult);
}
if (_this.paused && !ALLOWED_METHODS_WHEN_PAUSED.includes(name)) {
(_d = (_c = _this.logger) === null || _c === void 0 ? void 0 : _c.log) === null || _d === void 0 ? void 0 : _d.call(_c, function () { return 'scroller is paused: ' + name + ' method is ignored'; });
return Promise.resolve(methodPausedResult);
}
return _this.getPromisifiedMethod(method, args);
};
};
Adapter.prototype.initialize = function (_a) {
var _this = this;
var buffer = _a.buffer, state = _a.state, viewport = _a.viewport, logger = _a.logger, adapterRun$ = _a.adapterRun$, getWorkflow = _a.getWorkflow;
// buffer
Object.defineProperty(this.demand, AdapterPropName.itemsCount, {
get: function () { return buffer.getVisibleItemsCount(); }
});
Object.defineProperty(this.demand, AdapterPropName.bufferInfo, {
get: function () { return ({
firstIndex: buffer.firstIndex,
lastIndex: buffer.lastIndex,
minIndex: buffer.minIndex,
maxIndex: buffer.maxIndex,
absMinIndex: buffer.absMinIndex,
absMaxIndex: buffer.absMaxIndex,
defaultSize: buffer.defaultSize,
}); }
});
this.bof = buffer.bof.get();
buffer.bof.on(function (bof) { return _this.bof = bof; });
this.eof = buffer.eof.get();
buffer.eof.on(function (eof) { return _this.eof = eof; });
// state
Object.defineProperty(this.demand, AdapterPropName.packageInfo, {
get: function () { return state.packageInfo; }
});
this.loopPending = state.cycle.innerLoop.busy.get();
state.cycle.innerLoop.busy.on(function (busy) { return _this.loopPending = busy; });
this.isLoading = state.cycle.busy.get();
state.cycle.busy.on(function (busy) { return _this.isLoading = busy; });
this.paused = state.paused.get();
state.paused.on(function (paused) { return _this.paused = paused; });
//viewport
this.setFirstOrLastVisible = function (_a) {
var _b, _c, _d;
var first = _a.first, last = _a.last, workflow = _a.workflow;
if ((!first && !last) || ((_b = workflow === null || workflow === void 0 ? void 0 : workflow.call) === null || _b === void 0 ? void 0 : _b.interrupted)) {
return;
}
var token = first ? AdapterPropName.firstVisible : AdapterPropName.lastVisible;
if (!((_d = wantedUtils.getBox((_c = _this.externalContext) === null || _c === void 0 ? void 0 : _c.id)) === null || _d === void 0 ? void 0 : _d[token])) {
return;
}
if (buffer.items.some(function (_a) {
var element = _a.element;
return !element;
})) {
logger.log('skipping first/lastVisible set because not all buffered items are rendered at this moment');
return;
}
var direction = first ? Direction.backward : Direction.forward;
var item = viewport.getEdgeVisibleItem(buffer.items, direction).item;
if (!item || item.element !== _this[token].element) {
_this[token] = (item ? item.get() : EMPTY_ITEM);
}
};
// logger
this.logger = logger;
// self-pending subscription; set up only on the very first init
if (adapterRun$) {
if (!this.relax$) {
this.relax$ = new Reactive();
}
var relax$_1 = this.relax$;
adapterRun$.on(function (_a) {
var status = _a.status, payload = _a.payload;
var unSubRelax = function () { };
if (status === ProcessStatus.start) {
unSubRelax = _this.isLoading$.on(function (value) {
if (!value) {
unSubRelax();
relax$_1.set({ success: true, immediate: false, details: null });
}
});
}
else if (status === ProcessStatus.done || status === ProcessStatus.error) {
unSubRelax();
relax$_1.set({
success: status !== ProcessStatus.error,
immediate: true,
details: status === ProcessStatus.error && payload ? String(payload.error) : null
});
}
});
}
// workflow getter
if (getWorkflow) {
this.getWorkflow = getWorkflow;
}
// init
this.init = true;
};
Adapter.prototype.dispose = function () {
var _this = this;
if (this.relax$) {
this.relax$.dispose();
}
if (this.externalContext) {
this.resetContext();
}
Object.getOwnPropertyNames(this).forEach(function (prop) {
delete _this[prop];
});
this.disposed = true;
};
Adapter.prototype.resetContext = function () {
var _this = this;
var _a;
var reactiveStore = reactiveConfigStorage.get((_a = this.externalContext) === null || _a === void 0 ? void 0 : _a.id);
ADAPTER_PROPS_STUB
.forEach(function (_a) {
var type = _a.type, permanent = _a.permanent, name = _a.name, value = _a.value;
// assign initial values to non-reactive non-permanent props
if (type !== AdapterPropType.Reactive && !permanent) {
Object.defineProperty(_this.externalContext, name, {
configurable: true,
get: function () { return value; }
});
}
// reset reactive props
if (type === AdapterPropType.Reactive && reactiveStore) {
var property = reactiveStore[name];
if (property) {
property.default.reset();
property.emit(property.source, property.default.get());
}
}
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Adapter.prototype.reset = function (options) {
this.reloadCounter++;
this.logger.logAdapterMethod('reset', options, " of ".concat(this.reloadId));
this.workflow.call({
process: AdapterProcess.reset,
status: ProcessStatus.start,
payload: { options: options }
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Adapter.prototype.reload = function (options) {
this.reloadCounter++;
this.logger.logAdapterMethod('reload', options, " of ".concat(this.reloadId));
this.workflow.call({
process: AdapterProcess.reload,
status: ProcessStatus.start,
payload: { options: options }
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Adapter.prototype.append = function (_options, eof) {
var options = convertAppendArgs(false, _options, eof); // support old signature
this.logger.logAdapterMethod('append', [options.items, options.eof]);
this.workflow.call({
process: AdapterProcess.append,
status: ProcessStatus.start,
payload: { options: options }
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Adapter.prototype.prepend = function (_options, bof) {
var options = convertAppendArgs(true, _options, bof); // support old signature
this.logger.logAdapterMethod('prepend', [options.items, options.bof]);
this.workflow.call({
process: AdapterProcess.prepend,
status: ProcessStatus.start,
payload: { options: options }
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Adapter.prototype.check = function () {
this.logger.logAdapterMethod('check');
this.workflow.call({
process: AdapterProcess.check,
status: ProcessStatus.start
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Adapter.prototype.remove = function (options) {
options = convertRemoveArgs(options); // support old signature
this.logger.logAdapterMethod('remove', options);
this.workflow.call({
process: AdapterProcess.remove,
status: ProcessStatus.start,
payload: { options: options }
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Adapter.prototype.clip = function (options) {
this.logger.logAdapterMethod('clip', options);
this.workflow.call({
process: AdapterProcess.clip,
status: ProcessStatus.start,
payload: { options: options }
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Adapter.prototype.insert = function (options) {
this.logger.logAdapterMethod('insert', options);
this.workflow.call({
process: AdapterProcess.insert,
status: ProcessStatus.start,
payload: { options: options }
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Adapter.prototype.replace = function (options) {
this.logger.logAdapterMethod('replace', options);
this.workflow.call({
process: AdapterProcess.replace,
status: ProcessStatus.start,
payload: { options: options }
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Adapter.prototype.update = function (options) {
this.logger.logAdapterMethod('update', options);
this.workflow.call({
process: AdapterProcess.update,
status: ProcessStatus.start,
payload: { options: options }
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Adapter.prototype.pause = function () {
this.logger.logAdapterMethod('pause');
this.workflow.call({
process: AdapterProcess.pause,
status: ProcessStatus.start
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Adapter.prototype.resume = function () {
this.logger.logAdapterMethod('resume');
this.workflow.call({
process: AdapterProcess.pause,
status: ProcessStatus.start,
payload: { options: { resume: true } }
});
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Adapter.prototype.fix = function (options) {
this.logger.logAdapterMethod('fix', options);
this.workflow.call({
process: AdapterProcess.fix,
status: ProcessStatus.start,
payload: { options: options }
});
};
Adapter.prototype.relaxUnchained = function (callback, reloadId) {
var _this = this;
var runCallback = function () { return typeof callback === 'function' && reloadId === _this.reloadId && callback(); };
if (!this.isLoading) {
runCallback();
}
return new Promise(function (resolve) {
if (!_this.isLoading) {
resolve(true);
return;
}
_this.isLoading$.once(function () {
runCallback();
resolve(false);
});
}).then(function (immediate) {
var _a, _b;
if (_this.disposed) {
return {
immediate: immediate,
success: false,
details: 'Adapter was disposed'
};
}
var success = reloadId === _this.reloadId;
(_b = (_a = _this.logger) === null || _a === void 0 ? void 0 : _a.log) === null || _b === void 0 ? void 0 : _b.call(_a, function () { return !success ? "relax promise cancelled due to ".concat(reloadId, " != ").concat(_this.reloadId) : void 0; });
return {
immediate: immediate,
success: success,
details: !success ? 'Interrupted by reload or reset' : null
};
});
};
Adapter.prototype.relax = function (callback) {
var _this = this;
var reloadId = this.reloadId;
this.logger.logAdapterMethod('relax', callback, " of ".concat(reloadId));
if (!this.init) {
return Promise.resolve(methodPreResult);
}
return this.relaxRun = this.relaxRun
? this.relaxRun.then(function () { return _this.relaxUnchained(callback, reloadId); })
: this.relaxUnchained(callback, reloadId).then(function (result) {
_this.relaxRun = null;
return result;
});
};
Adapter.prototype.showLog = function () {
this.logger.logAdapterMethod('showLog');
this.logger.logForce();
};
return Adapter;
}());
export { Adapter };
//# sourceMappingURL=adapter.js.map