@adpt/core
Version:
AdaptJS core library
223 lines • 8.67 kB
JavaScript
;
/*
* Copyright 2018-2019 Unbounded Systems, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const json_stable_stringify_1 = tslib_1.__importDefault(require("json-stable-stringify"));
const error_1 = require("../error");
const jsx_1 = require("../jsx");
const deploy_types_1 = require("./deploy_types");
class WidgetPlugin {
constructor() {
this.queryDomains = new Map();
this.log = (arg, ...args) => {
if (this.log_)
this.log_(arg, ...args);
};
}
/*
* Methods that implement the Plugin interface
*/
async start(options) {
this.deployID_ = options.deployID;
this.log_ = options.log;
this.dataDir_ = options.dataDir;
}
createExpected(oldDom, newDom, createQueryDomains = false) {
const ret = {};
const changes = new Map();
const addElems = (dom, type) => {
this.findElems(dom).forEach((el) => {
const id = this.getWidgetIdFromElem(el);
let change = changes.get(id);
if (change) {
change[type] = el;
return;
}
change = { id, [type]: el };
changes.set(id, change);
const domain = this.getElemQueryDomain(el);
const qdKey = makeQueryDomainKey(domain);
const qdChangeList = ret[qdKey] || [];
ret[qdKey] = qdChangeList;
qdChangeList.push(change);
if (createQueryDomains && this.queryDomains.get(qdKey) == null) {
this.queryDomains.set(qdKey, domain);
}
});
};
addElems(oldDom, "from");
addElems(newDom, "to");
return ret;
}
async observe(oldDom, dom) {
const elemsInQDomain = this.createExpected(oldDom, dom, true);
const obs = {};
for (const [key, domain] of this.queryDomains.entries()) {
obs[key] = await this.getObservations(domain, this.deployID, elemsInQDomain[key]);
}
return obs;
}
analyze(oldDom, dom, obs) {
const deployID = this.deployID;
const expected = this.createExpected(oldDom, dom);
const actions = diffObservations(expected, obs, (o) => this.getWidgetIdFromObs(o), (el, o) => this.computeChanges(el, o));
const ret = [];
this.translatePairs(ret, actions.create, "Creating", (d, p) => this.createWidget(d, deployID, p));
this.translatePairs(ret, actions.modify, "Modifying", (d, p) => this.modifyWidget(d, deployID, p));
this.translatePairs(ret, actions.replace, "Replacing", async (d, p) => {
await this.destroyWidget(d, deployID, p);
await this.createWidget(d, deployID, p);
});
this.translatePairs(ret, actions.delete, "Destroying", (d, p) => this.destroyWidget(d, deployID, p));
this.translatePairs(ret, actions.none, "Not modifying", () => { });
return ret;
}
async finish() {
this.log_ = undefined;
}
/*
* Additional class methods
*/
get deployID() {
if (this.deployID_ == null) {
throw new error_1.InternalError(`deployID not initialized yet`);
}
return this.deployID_;
}
get dataDir() {
if (this.dataDir_ == null) {
throw new Error(`Internal error: dataDir not initialized yet`);
}
return this.dataDir_;
}
queryDomain(key) {
return this.queryDomains.get(key);
}
widgetInfo(pair) {
let type;
let id;
let key;
const el = pair.element;
if (el != null) {
if (!jsx_1.isMountedElement(el))
throw new error_1.InternalError(`element not mounted`);
type = this.getWidgetTypeFromElem(el);
id = this.getWidgetIdFromElem(el);
key = el.props.key;
}
else if (pair.observed !== undefined) {
type = this.getWidgetTypeFromObs(pair.observed);
id = this.getWidgetIdFromObs(pair.observed);
}
else {
throw new error_1.InternalError(`WidgetPair with no content`);
}
return { type, id, key };
}
/**
* Translate WidgetPairs into plugin Actions
*/
translatePairs(actions, pairs, actionType, action) {
for (const p of pairs) {
const { type, id, key } = this.widgetInfo(p);
const k = key ? ` '${key}'` : "";
const description = `${actionType} ${type}${k} (id=${id})`;
const domain = this.queryDomain(p.queryDomainKey);
if (domain == null)
throw new error_1.InternalError(`domain null`);
actions.push(Object.assign({}, p.actionInfo, { act: async () => {
try {
await action(domain, p);
}
catch (err) {
const path = p.element ? getPath(p.element) : "";
throw new Error(`An error occurred while ${description}` +
`${path}: ${err.message || err}`);
}
} }));
}
}
}
exports.WidgetPlugin = WidgetPlugin;
function getPath(el) {
if (jsx_1.isMountedElement(el))
return ` [${el.path}]`;
return "";
}
function makeQueryDomainKey(queryDomain) {
return json_stable_stringify_1.default(queryDomain.id);
}
function diffArrays(queryDomainKey, expected, observed, observedId, computeChanges, actions) {
const obsMap = new Map(observed.map((o) => [observedId(o), o]));
let actionInfo;
for (const e of expected) {
const o = obsMap.get(e.id);
if (o !== undefined)
obsMap.delete(e.id);
actionInfo = computeChanges(e, o);
const pair = { queryDomainKey, actionInfo };
const actionList = actions[actionInfo.type];
switch (actionInfo.type) {
case deploy_types_1.ChangeType.create:
actionList.push(Object.assign({}, pair, { element: e.to }));
break;
case deploy_types_1.ChangeType.none:
const element = e.to || e.from;
if (element == null)
throw new error_1.InternalError(`WidgetChange with no 'from' or 'to' properties`);
actionList.push(Object.assign({}, pair, { element }));
break;
case deploy_types_1.ChangeType.delete:
if (o == null) {
throw new Error(`Error in deployment plugin. Plugin computeChanges ` +
`method returned ChangeType.delete for Element type ` +
`${e.from.componentName} but no corresponding ` +
`object was observed in the environment.`);
}
actionList.push(Object.assign({}, pair, { observed: o }));
break;
case deploy_types_1.ChangeType.modify:
case deploy_types_1.ChangeType.replace:
actionList.push(Object.assign({}, pair, { element: e.to, observed: o }));
break;
}
}
for (const [id, o] of obsMap) {
actionInfo = computeChanges({ id }, o);
actions.delete.push({ queryDomainKey, actionInfo, observed: o });
}
}
function diffObservations(expected, observed, observedId, computeChanges) {
const actions = {
create: [],
delete: [],
modify: [],
none: [],
replace: [],
};
// Clone so we can modify
observed = Object.assign({}, observed);
for (const key of Object.keys(expected)) {
diffArrays(key, expected[key], observed[key] || [], observedId, computeChanges, actions);
delete observed[key];
}
for (const key of Object.keys(observed)) {
diffArrays(key, [], observed[key], observedId, computeChanges, actions);
}
return actions;
}
//# sourceMappingURL=widget_plugin.js.map