mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) move client code to core
Summary: This moves all client code to core, and makes minimal fix-ups to get grist and grist-core to compile correctly. The client works in core, but I'm leaving clean-up around the build and bundles to follow-up. Test Plan: existing tests pass; server-dev bundle looks sane Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2627
This commit is contained in:
155
app/client/lib/ObservableMap.js
Normal file
155
app/client/lib/ObservableMap.js
Normal file
@@ -0,0 +1,155 @@
|
||||
var ko = require('knockout');
|
||||
|
||||
var dispose = require('./dispose');
|
||||
|
||||
/**
|
||||
* ObservableMap provides a structure to keep track of values that need to recalculate in
|
||||
* response to a key change or a mapping function change.
|
||||
*
|
||||
* @example
|
||||
* let factor = ko.observable(2);
|
||||
* let myFunc = ko.computed(() => {
|
||||
* let f = factor();
|
||||
* return (keyId) => key * f;
|
||||
* });
|
||||
*
|
||||
* let myMap = ObservableMap.create(myFunc);
|
||||
* let inObs1 = ko.observable(2);
|
||||
* let inObs2 = ko.observable(3);
|
||||
*
|
||||
* let outObs1 = myMap.add(inObs1);
|
||||
* let outObs2 = myMap.add(inObs2);
|
||||
* outObs1(); // 4
|
||||
* outObs2(); // 6
|
||||
*
|
||||
* inObs1(5);
|
||||
* outObs1(); // 10
|
||||
*
|
||||
* factor(3);
|
||||
* outObs1(); // 15
|
||||
* outObs2(); // 9
|
||||
*
|
||||
*
|
||||
* @param {Function} mapFunc - Computed that returns a mapping function that takes in a key and
|
||||
* returns a value. Whenever `mapFunc` is updated, all the current values in the map will be
|
||||
* recalculated using the new function.
|
||||
*/
|
||||
function ObservableMap(mapFunc) {
|
||||
this.store = new Map();
|
||||
this.mapFunc = mapFunc;
|
||||
|
||||
// Recalculate all values on changes to mapFunc
|
||||
let mapFuncSub = mapFunc.subscribe(() => {
|
||||
this.updateAll();
|
||||
});
|
||||
|
||||
// Disposes all stored observable and clears the map.
|
||||
this.autoDisposeCallback(() => {
|
||||
// Unsbuscribe from mapping function
|
||||
mapFuncSub.dispose();
|
||||
// Clear the store
|
||||
this.store.forEach((val, key) => val.forEach(obj => obj.dispose()));
|
||||
this.store.clear();
|
||||
});
|
||||
}
|
||||
dispose.makeDisposable(ObservableMap);
|
||||
|
||||
/**
|
||||
* Takes an observable for the key value and returns an observable for the output.
|
||||
* Subscribes to the given observable so that whenever it changes the output observable is
|
||||
* updated to the value returned by `mapFunc` when provided the new key as input.
|
||||
* If user disposes of the returned observable, it will be removed from the map.
|
||||
*
|
||||
* @param {ko.observable} obsKey
|
||||
* @return {ko.observble} Observable value equal to `mapFunc(obsKey())` that will be updated on
|
||||
* updates to `obsKey` and `mapFunc`.
|
||||
*/
|
||||
ObservableMap.prototype.add = function (obsKey) {
|
||||
let currKey = obsKey();
|
||||
let ret = ko.observable(this.mapFunc()(currKey));
|
||||
|
||||
// Add to map
|
||||
this._addKeyValue(currKey, ret);
|
||||
|
||||
// Subscribe to changes to key
|
||||
let subs = obsKey.subscribe(newKey => {
|
||||
ret(this.mapFunc()(newKey));
|
||||
|
||||
if (currKey !== newKey) {
|
||||
// If the key changed, add it to the new bucket and delete from the old one
|
||||
this._addKeyValue(newKey, ret);
|
||||
this._delete(currKey, ret);
|
||||
// And update the key
|
||||
currKey = newKey;
|
||||
}
|
||||
});
|
||||
ret.dispose = () => {
|
||||
// On dispose, delete from map unless the whole map is being disposed
|
||||
if (!this.isDisposed()) {
|
||||
this._delete(currKey, ret);
|
||||
}
|
||||
subs.dispose();
|
||||
};
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the Set of observable values for the given key.
|
||||
*/
|
||||
ObservableMap.prototype.get = function (key) {
|
||||
return this.store.get(key);
|
||||
};
|
||||
|
||||
ObservableMap.prototype._addKeyValue = function (key, value) {
|
||||
if (!this.store.has(key)) {
|
||||
this.store.set(key, new Set([value]));
|
||||
} else {
|
||||
this.store.get(key).add(value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers an update for all keys.
|
||||
*/
|
||||
ObservableMap.prototype.updateAll = function () {
|
||||
this.store.forEach((val, key) => this.updateKey(key));
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers an update for all observables for given keys in the map.
|
||||
* @param {Array} keys
|
||||
*/
|
||||
ObservableMap.prototype.updateKeys = function (keys) {
|
||||
keys.forEach(key => this.updateKey(key));
|
||||
};
|
||||
|
||||
/**
|
||||
* Triggers an update for all observables for the given key in the map.
|
||||
* @param {Any} key
|
||||
*/
|
||||
ObservableMap.prototype.updateKey = function (key) {
|
||||
if (this.store.has(key) && this.store.get(key).size > 0) {
|
||||
this.store.get(key).forEach(obj => {
|
||||
obj(this.mapFunc()(key));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a key and an observable, deletes the observable from that key's bucket.
|
||||
*
|
||||
* @param {Any} key - Current value of the key.
|
||||
* @param {Any} obsValue - An observable previously returned by `add`.
|
||||
*/
|
||||
ObservableMap.prototype._delete = function (key, obsValue) {
|
||||
if (this.store.has(key) && this.store.get(key).size > 0) {
|
||||
this.store.get(key).delete(obsValue);
|
||||
// Clean up empty buckets
|
||||
if (this.store.get(key).size === 0) {
|
||||
this.store.delete(key);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ObservableMap;
|
||||
Reference in New Issue
Block a user