@uploadcare/blocks
Version:
Building blocks for Uploadcare products integration
217 lines (200 loc) • 4.78 kB
JavaScript
import { Data, UID } from '@symbiotejs/symbiote';
import { TypedData } from './TypedData.js';
export class TypedCollection {
/**
* @param {Object} options
* @param {Object<string, { type: any; value: any }>} options.typedSchema
* @param {String[]} [options.watchList]
* @param {(list: string[], added: Set<any>, removed: Set<any>) => void} [options.handler]
* @param {String} [options.ctxName]
*/
constructor(options) {
/**
* @private
* @type {Object<string, { type: any; value: any }>}
*/
this.__typedSchema = options.typedSchema;
/**
* @private
* @type {String}
*/
this.__ctxId = options.ctxName || UID.generate();
/**
* @private
* @type {Data}
*/
this.__data = Data.registerCtx({}, this.__ctxId);
/**
* @private
* @type {string[]}
*/
this.__watchList = options.watchList || [];
/**
* @private
* @type {(list: string[], added: Set<any>, removed: Set<any>) => void}
*/
this.__handler = options.handler || null;
/**
* @private
* @type {Object<string, any>}
*/
this.__subsMap = Object.create(null);
/**
* @private
* @type {Set}
*/
this.__observers = new Set();
/**
* @private
* @type {Set<string>}
*/
this.__items = new Set();
/**
* @private
* @type {Set<any>}
*/
this.__removed = new Set();
/**
* @private
* @type {Set<any>}
*/
this.__added = new Set();
let changeMap = Object.create(null);
/**
* @private
* @param {String} propName
* @param {String} ctxId
*/
this.__notifyObservers = (propName, ctxId) => {
if (this.__observeTimeout) {
window.clearTimeout(this.__observeTimeout);
}
if (!changeMap[propName]) {
changeMap[propName] = new Set();
}
changeMap[propName].add(ctxId);
/** @private */
this.__observeTimeout = window.setTimeout(() => {
this.__observers.forEach((handler) => {
handler({ ...changeMap });
});
changeMap = Object.create(null);
});
};
}
notify() {
if (this.__notifyTimeout) {
window.clearTimeout(this.__notifyTimeout);
}
/** @private */
this.__notifyTimeout = window.setTimeout(() => {
let added = this.__added;
let removed = this.__removed;
this.__added.clear();
this.__removed.clear();
this.__handler?.([...this.__items], added, removed);
});
}
/**
* @param {Object<string, any>} init
* @returns {any}
*/
add(init) {
let item = new TypedData(this.__typedSchema);
for (let prop in init) {
item.setValue(prop, init[prop]);
}
this.__data.add(item.uid, item);
this.__added.add(item);
this.__watchList.forEach((propName) => {
if (!this.__subsMap[item.uid]) {
this.__subsMap[item.uid] = [];
}
this.__subsMap[item.uid].push(
item.subscribe(propName, () => {
this.__notifyObservers(propName, item.uid);
})
);
});
this.__items.add(item.uid);
this.notify();
return item;
}
/**
* @param {String} id
* @returns {TypedData}
*/
read(id) {
return this.__data.read(id);
}
/**
* @param {String} id
* @param {String} propName
* @returns {any}
*/
readProp(id, propName) {
let item = this.read(id);
return item.getValue(propName);
}
/**
* @template T
* @param {String} id
* @param {String} propName
* @param {T} value
*/
publishProp(id, propName, value) {
let item = this.read(id);
item.setValue(propName, value);
}
/** @param {String} id */
remove(id) {
this.__removed.add(this.__data.read(id));
this.__items.delete(id);
this.notify();
this.__data.pub(id, null);
delete this.__subsMap[id];
}
clearAll() {
this.__items.forEach((id) => {
this.remove(id);
});
}
/** @param {Function} handler */
observe(handler) {
this.__observers.add(handler);
}
/** @param {Function} handler */
unobserve(handler) {
this.__observers.delete(handler);
}
/**
* @param {(item: TypedData) => Boolean} checkFn
* @returns {String[]}
*/
findItems(checkFn) {
let result = [];
this.__items.forEach((id) => {
let item = this.read(id);
if (checkFn(item)) {
result.push(id);
}
});
return result;
}
items() {
return [...this.__items];
}
get size() {
return this.__items.size;
}
destroy() {
Data.deleteCtx(this.__data);
this.__observers = null;
for (let id in this.__subsMap) {
this.__subsMap[id].forEach((sub) => {
sub.remove();
});
delete this.__subsMap[id];
}
}
}