pdbe-molstar
Version:
Molstar implementation for PDBe
618 lines (617 loc) • 32.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PluginConfigUtils = exports.PreemptiveQueue = exports.StructureComponentTags = exports.Tags = exports.ModelInfo = exports.QueryHelper = exports.LigandView = exports.AlphafoldView = exports.PDBeVolumes = void 0;
exports.queryParamsToMvsComponentExpressions = queryParamsToMvsComponentExpressions;
exports.runWithProgressMessage = runWithProgressMessage;
exports.getStructureUrl = getStructureUrl;
exports.combineUrl = combineUrl;
exports.addDefaults = addDefaults;
exports.normalizeColor = normalizeColor;
exports.applyOverpaint = applyOverpaint;
exports.getComponentTypeFromTags = getComponentTypeFromTags;
exports.distinct = distinct;
exports.groupElements = groupElements;
exports.createIndex = createIndex;
exports.nonnegativeModulo = nonnegativeModulo;
exports.pluginLayoutStateFromInitParams = pluginLayoutStateFromInitParams;
exports.getRotationMat4 = getRotationMat4;
const tslib_1 = require("tslib");
const prop_1 = require("molstar/lib/extensions/model-archive/quality-assessment/prop");
const linear_algebra_1 = require("molstar/lib/mol-math/linear-algebra");
const structure_1 = require("molstar/lib/mol-model/structure");
const query_1 = require("molstar/lib/mol-model/structure/query/query");
const transforms_1 = require("molstar/lib/mol-plugin-state/transforms");
const transformers_1 = require("molstar/lib/mol-plugin/behavior/dynamic/volume-streaming/transformers");
const commands_1 = require("molstar/lib/mol-plugin/commands");
const layout_1 = require("molstar/lib/mol-plugin/layout");
const builder_1 = require("molstar/lib/mol-script/language/builder");
const compiler_1 = require("molstar/lib/mol-script/runtime/query/compiler");
const mol_state_1 = require("molstar/lib/mol-state");
const mol_task_1 = require("molstar/lib/mol-task");
const color_1 = require("molstar/lib/mol-util/color");
const names_1 = require("molstar/lib/mol-util/color/names");
const sleep_1 = require("molstar/lib/mol-util/sleep");
const sifts_mapping_1 = require("./sifts-mapping");
var PDBeVolumes;
(function (PDBeVolumes) {
function mapParams(defaultParams, mapParams, ref) {
const pdbeParams = Object.assign({}, defaultParams);
pdbeParams.options.behaviorRef = 'volume-streaming' + '' + Math.floor(Math.random() * Math.floor(100));
pdbeParams.options.emContourProvider = 'pdbe';
pdbeParams.options.serverUrl = 'https://www.ebi.ac.uk/pdbe/volume-server';
const MAIN_MAP_DEFAULTS = { opacity: 0.49, wireframe: false };
const DIFF_MAP_DEFAULTS = { opacity: 0.3, wireframe: true };
pdbeParams.options.channelParams['em'] = addDefaults(mapParams === null || mapParams === void 0 ? void 0 : mapParams['em'], MAIN_MAP_DEFAULTS);
pdbeParams.options.channelParams['2fo-fc'] = addDefaults(mapParams === null || mapParams === void 0 ? void 0 : mapParams['2fo-fc'], MAIN_MAP_DEFAULTS);
pdbeParams.options.channelParams['fo-fc(+ve)'] = addDefaults(mapParams === null || mapParams === void 0 ? void 0 : mapParams['fo-fc(+ve)'], DIFF_MAP_DEFAULTS);
pdbeParams.options.channelParams['fo-fc(-ve)'] = addDefaults(mapParams === null || mapParams === void 0 ? void 0 : mapParams['fo-fc(-ve)'], DIFF_MAP_DEFAULTS);
return pdbeParams;
}
PDBeVolumes.mapParams = mapParams;
function displayUsibilityMessage(plugin) {
commands_1.PluginCommands.Toast.Show(plugin, {
title: 'Volume',
message: 'Streaming enabled, click on a residue or an atom to view the data.',
key: 'toast-1',
timeoutMs: 7000,
});
}
PDBeVolumes.displayUsibilityMessage = displayUsibilityMessage;
function toggle(plugin) {
const state = plugin.state.data;
const streamingState = state.select(mol_state_1.StateSelection.Generators.ofTransformer(transformers_1.CreateVolumeStreamingInfo))[0];
if (streamingState) {
commands_1.PluginCommands.State.ToggleVisibility(plugin, { state: state, ref: streamingState.transform.ref });
return;
}
}
PDBeVolumes.toggle = toggle;
})(PDBeVolumes || (exports.PDBeVolumes = PDBeVolumes = {}));
var AlphafoldView;
(function (AlphafoldView) {
function getLociByPLDDT(score, contextData) {
const queryExp = builder_1.MolScriptBuilder.struct.modifier.union([
builder_1.MolScriptBuilder.struct.modifier.wholeResidues([
builder_1.MolScriptBuilder.struct.modifier.union([
builder_1.MolScriptBuilder.struct.generator.atomGroups({
'chain-test': builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.ammp('objectPrimitive'), 'atomistic']),
'residue-test': builder_1.MolScriptBuilder.core.rel.gr([prop_1.QualityAssessment.symbols.pLDDT.symbol(), score]),
}),
]),
]),
]);
const query = (0, compiler_1.compile)(queryExp);
const sel = query(new structure_1.QueryContext(contextData));
return structure_1.StructureSelection.toLociWithSourceUnits(sel);
}
AlphafoldView.getLociByPLDDT = getLociByPLDDT;
})(AlphafoldView || (exports.AlphafoldView = AlphafoldView = {}));
var LigandView;
(function (LigandView) {
function query(ligandViewParams) {
const atomGroupsParams = {
'group-by': builder_1.MolScriptBuilder.core.str.concat([builder_1.MolScriptBuilder.struct.atomProperty.core.operatorName(), builder_1.MolScriptBuilder.struct.atomProperty.macromolecular.residueKey()]),
};
// Residue Param
let residueParam;
if (ligandViewParams.auth_seq_id !== undefined) {
residueParam = builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.struct.atomProperty.macromolecular.auth_seq_id(), ligandViewParams.auth_seq_id]);
}
else if (ligandViewParams.label_comp_id) {
residueParam = builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.struct.atomProperty.macromolecular.label_comp_id(), ligandViewParams.label_comp_id]);
}
if (residueParam)
atomGroupsParams['residue-test'] = residueParam;
// Chain Param
if (ligandViewParams.auth_asym_id) {
atomGroupsParams['chain-test'] = builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.struct.atomProperty.macromolecular.auth_asym_id(), ligandViewParams.auth_asym_id]);
}
else if (ligandViewParams.struct_asym_id) {
atomGroupsParams['chain-test'] = builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.struct.atomProperty.macromolecular.label_asym_id(), ligandViewParams.struct_asym_id]);
}
// Construct core query
const core = ligandViewParams.show_all ?
builder_1.MolScriptBuilder.struct.generator.atomGroups(atomGroupsParams) :
builder_1.MolScriptBuilder.struct.filter.first([
builder_1.MolScriptBuilder.struct.generator.atomGroups(atomGroupsParams),
]);
// Construct surroundings query
const surroundings = builder_1.MolScriptBuilder.struct.modifier.includeSurroundings({ 0: core, radius: 5, 'as-whole-residues': true });
return {
core,
surroundings,
};
}
LigandView.query = query;
function branchedQuery(params) {
const entityObjArray = [];
params.atom_site.forEach((param) => {
const qEntities = {
'group-by': builder_1.MolScriptBuilder.core.str.concat([builder_1.MolScriptBuilder.struct.atomProperty.core.operatorName(), builder_1.MolScriptBuilder.struct.atomProperty.macromolecular.residueKey()]),
'residue-test': builder_1.MolScriptBuilder.core.rel.eq([builder_1.MolScriptBuilder.struct.atomProperty.macromolecular.auth_seq_id(), param.auth_seq_id]),
};
entityObjArray.push(qEntities);
});
const atmGroupsQueries = [];
entityObjArray.forEach((entityObj) => {
atmGroupsQueries.push(builder_1.MolScriptBuilder.struct.generator.atomGroups(entityObj));
});
const core = builder_1.MolScriptBuilder.struct.modifier.union([
atmGroupsQueries.length === 1
? atmGroupsQueries[0]
// Need to union before merge for fast performance
: builder_1.MolScriptBuilder.struct.combinator.merge(atmGroupsQueries.map(q => builder_1.MolScriptBuilder.struct.modifier.union([q]))),
]);
// Construct surroundings query
const surroundings = builder_1.MolScriptBuilder.struct.modifier.includeSurroundings({ 0: core, radius: 5, 'as-whole-residues': true });
return {
core,
surroundings,
};
}
LigandView.branchedQuery = branchedQuery;
})(LigandView || (exports.LigandView = LigandView = {}));
function queryParamsToMvsComponentExpressions(params) {
const broadcasted = broadcast(params, ['atoms', 'atom_id']);
return broadcasted.map(item => {
var _a;
return ({
label_entity_id: item.entity_id,
label_asym_id: item.struct_asym_id,
auth_asym_id: item.auth_asym_id,
label_seq_id: item.residue_number,
auth_seq_id: (_a = item.auth_seq_id) !== null && _a !== void 0 ? _a : item.auth_residue_number,
pdbx_PDB_ins_code: item.auth_ins_code_id,
beg_label_seq_id: item.start_residue_number,
end_label_seq_id: item.end_residue_number,
beg_auth_seq_id: item.start_auth_residue_number,
end_auth_seq_id: item.end_auth_residue_number,
label_atom_id: item.atoms,
auth_atom_id: undefined,
type_symbol: undefined,
atom_id: item.atom_id,
atom_index: undefined,
label_comp_id: item.label_comp_id,
auth_comp_id: undefined,
});
});
}
/** `broadcastSingleKey([{chain: 'A', residue: 5, atom: ['C', 'O']}], 'atom')`
* -> `[{chain: 'A', residue: 5, atom: 'C'}], {chain: 'A', residue: 5, atom: 'O'}` */
function broadcastSingleKey(objs, key) {
const out = [];
for (const obj of objs) {
if (obj[key] === undefined) {
out.push(obj);
}
else {
for (const value of obj[key]) {
out.push(Object.assign(Object.assign({}, obj), { [key]: value }));
}
}
}
return out;
}
/** `broadcast([{chain: 'A', residue: [5, 6], atom: ['C', 'O']}], ['residue', 'atom'])`
* -> `[{chain: 'A', residue: 5, atom: 'C'}], {chain: 'A', residue: 5, atom: 'O', {chain: 'A', residue: 6, atom: 'C'}], {chain: 'A', residue: 6, atom: 'O'}` */
function broadcast(objs, keys) {
return keys.reduce((list, key) => broadcastSingleKey(list, key), objs);
}
var QueryHelper;
(function (QueryHelper) {
function getQueryObject(params, contextData) {
const selections = [];
/** `undefined` means SIFTSMappingMapping has not been retrieved yet. `null` means SIFTSMappingMapping has been retrieved unsuccessfully. */
let _siftMappings;
function getSiftsMappings() {
var _a;
if (_siftMappings === undefined)
_siftMappings = (_a = sifts_mapping_1.SIFTSMapping.Provider.get(contextData.models[0]).value) !== null && _a !== void 0 ? _a : null;
return _siftMappings;
}
for (const param of params) {
const predicates = {
entity: [],
chain: [],
residue: [],
atom: [],
};
// entity
if (param.entity_id !== undefined) {
predicates.entity.push(l => structure_1.StructureProperties.entity.id(l.element) === param.entity_id);
}
// chain
if (param.struct_asym_id !== undefined) {
predicates.chain.push(l => structure_1.StructureProperties.chain.label_asym_id(l.element) === param.struct_asym_id);
}
if (param.auth_asym_id !== undefined) {
predicates.chain.push(l => structure_1.StructureProperties.chain.auth_asym_id(l.element) === param.auth_asym_id);
}
// residue
if (param.label_comp_id !== undefined) {
predicates.residue.push(l => structure_1.StructureProperties.atom.label_comp_id(l.element) === param.label_comp_id);
}
if (param.residue_number !== undefined) {
predicates.residue.push(l => structure_1.StructureProperties.residue.label_seq_id(l.element) === param.residue_number);
}
if (param.start_residue_number !== undefined) {
predicates.residue.push(l => structure_1.StructureProperties.residue.label_seq_id(l.element) >= param.start_residue_number);
}
if (param.end_residue_number !== undefined) {
predicates.residue.push(l => structure_1.StructureProperties.residue.label_seq_id(l.element) <= param.end_residue_number);
}
if (param.auth_seq_id !== undefined) {
predicates.residue.push(l => structure_1.StructureProperties.residue.auth_seq_id(l.element) === param.auth_seq_id);
}
if (param.auth_residue_number !== undefined) {
predicates.residue.push(l => structure_1.StructureProperties.residue.auth_seq_id(l.element) === param.auth_residue_number);
}
if (param.auth_ins_code_id !== undefined) {
predicates.residue.push(l => structure_1.StructureProperties.residue.pdbx_PDB_ins_code(l.element) === param.auth_ins_code_id);
}
if (param.start_auth_residue_number !== undefined) {
predicates.residue.push(l => structure_1.StructureProperties.residue.auth_seq_id(l.element) >= param.start_auth_residue_number);
if (param.start_auth_ins_code_id !== undefined) {
// This assumes insertion code come in alphabetical order, which is not always true (e.g. 1ucy chain A [auth L]). However, authors of such monstrosities do not deserve their structures to be displayed correctly.
predicates.residue.push(l => structure_1.StructureProperties.residue.auth_seq_id(l.element) > param.start_auth_residue_number || structure_1.StructureProperties.residue.pdbx_PDB_ins_code(l.element) >= param.start_auth_ins_code_id);
}
}
if (param.end_auth_residue_number !== undefined) {
predicates.residue.push(l => structure_1.StructureProperties.residue.auth_seq_id(l.element) <= param.end_auth_residue_number);
if (param.end_auth_ins_code_id !== undefined) {
// This assumes insertion code come in alphabetical order, which is not always true (e.g. 1ucy chain A [auth L]). However, authors of such monstrosities do not deserve their structures to be displayed correctly.
predicates.residue.push(l => structure_1.StructureProperties.residue.auth_seq_id(l.element) < param.end_auth_residue_number || structure_1.StructureProperties.residue.pdbx_PDB_ins_code(l.element) <= param.end_auth_ins_code_id);
}
}
if (param.uniprot_accession !== undefined) {
predicates.residue.push(l => { var _a; return ((_a = getSiftsMappings()) === null || _a === void 0 ? void 0 : _a.accession[structure_1.StructureProperties.residue.key(l.element)]) === param.uniprot_accession; });
}
if (param.uniprot_residue_number !== undefined) {
predicates.residue.push(l => { var _a; return Number((_a = getSiftsMappings()) === null || _a === void 0 ? void 0 : _a.num[structure_1.StructureProperties.residue.key(l.element)]) === param.uniprot_residue_number; });
}
if (param.start_uniprot_residue_number !== undefined) {
predicates.residue.push(l => { var _a; return Number((_a = getSiftsMappings()) === null || _a === void 0 ? void 0 : _a.num[structure_1.StructureProperties.residue.key(l.element)]) >= param.start_uniprot_residue_number; });
}
if (param.end_uniprot_residue_number !== undefined) {
predicates.residue.push(l => { var _a; return Number((_a = getSiftsMappings()) === null || _a === void 0 ? void 0 : _a.num[structure_1.StructureProperties.residue.key(l.element)]) <= param.end_uniprot_residue_number; });
}
// atom
if (param.atoms) {
predicates.atom.push(l => param.atoms.includes(structure_1.StructureProperties.atom.label_atom_id(l.element)));
}
if (param.atom_id) {
predicates.atom.push(l => param.atom_id.includes(structure_1.StructureProperties.atom.id(l.element)));
}
selections.push({
entityTest: predicateConjunction(predicates.entity),
chainTest: predicateConjunction(predicates.chain),
residueTest: predicateConjunction(predicates.residue),
atomTest: predicateConjunction(predicates.atom),
});
}
const atmGroupsQueries = selections.map(selection => structure_1.Queries.generators.atoms(selection));
return structure_1.Queries.combinators.merge(atmGroupsQueries);
}
QueryHelper.getQueryObject = getQueryObject;
function predicateConjunction(predicates) {
if (predicates.length === 0)
return undefined;
if (predicates.length === 1)
return predicates[0];
return (x => predicates.every(pred => pred(x)));
}
function getInteractivityLoci(params, contextData) {
const sel = query_1.StructureQuery.run(QueryHelper.getQueryObject(params, contextData), contextData);
return structure_1.StructureSelection.toLociWithSourceUnits(sel);
}
QueryHelper.getInteractivityLoci = getInteractivityLoci;
function getHetLoci(queryExp, contextData) {
const query = (0, compiler_1.compile)(queryExp);
const sel = query(new structure_1.QueryContext(contextData));
return structure_1.StructureSelection.toLociWithSourceUnits(sel);
}
QueryHelper.getHetLoci = getHetLoci;
})(QueryHelper || (exports.QueryHelper = QueryHelper = {}));
var ModelInfo;
(function (ModelInfo) {
function get(model, structures) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const { _rowCount: residueCount } = model.atomicHierarchy.residues;
const { offsets: residueOffsets } = model.atomicHierarchy.residueAtomSegments;
const chainIndex = model.atomicHierarchy.chainAtomSegments.index;
const hetNames = [];
let carbEntityCount = 0;
for (let rI = 0; rI < residueCount; rI++) {
const cI = chainIndex[residueOffsets[rI]];
const eI = model.atomicHierarchy.index.getEntityFromChain(cI);
const entityType = model.entities.data.type.value(eI);
if (entityType !== 'non-polymer' && entityType !== 'branched')
continue;
// const comp_id = model.atomicHierarchy.atoms.label_comp_id.value(rI);
const comp_id = model.atomicHierarchy.atoms.label_comp_id.value(residueOffsets[rI]);
if (entityType === 'branched') {
carbEntityCount++;
}
else {
if (hetNames.indexOf(comp_id) === -1)
hetNames.push(comp_id);
}
}
return {
hetNames,
carbEntityCount,
};
});
}
ModelInfo.get = get;
})(ModelInfo || (exports.ModelInfo = ModelInfo = {}));
/** Run `action` with showing a message in the bottom-left corner of the plugin UI */
function runWithProgressMessage(plugin, progressMessage, action) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const task = mol_task_1.Task.create(progressMessage !== null && progressMessage !== void 0 ? progressMessage : 'Task', (ctx) => tslib_1.__awaiter(this, void 0, void 0, function* () {
let done = false;
try {
if (progressMessage) {
setTimeout(() => { if (!done)
ctx.update(progressMessage); }, 1000); // Delay the first update to force showing message in UI
}
yield action();
}
finally {
done = true;
}
}));
yield plugin.runTask(task);
});
}
/** Return URL for a ModelServer request.
* If `queryType` is 'full' and `lowPrecisionCoords` is false, return URL of the static file instead (updated mmCIF or bCIF). */
function getStructureUrl(initParams, request) {
const pdbeUrl = initParams.pdbeUrl.replace(/\/$/, ''); // without trailing slash
const useStaticFile = request.queryType === 'full' && !initParams.lowPrecisionCoords;
if (useStaticFile) {
const suffix = initParams.encoding === 'bcif' ? '.bcif' : '_updated.cif';
return `${pdbeUrl}/entry-files/download/${request.pdbId}${suffix}`;
}
else {
const queryParams = Object.assign(Object.assign({}, request.queryParams), { encoding: initParams.encoding, lowPrecisionCoords: initParams.lowPrecisionCoords ? 1 : undefined });
const queryString = Object.entries(queryParams).filter(([key, value]) => value !== undefined).map(([key, value]) => `${key}=${value}`).join('&');
const url = `${pdbeUrl}/model-server/v1/${request.pdbId}/${request.queryType}`;
return (queryString !== '') ? `${url}?${queryString}` : url;
}
}
/** Combine URL parts into one URL while avoiding double slashes. Examples:
* combineUrl('https://example.org', '1tqn') -> 'https://example.org/1tqn';
* combineUrl('https://example.org/', '1tqn') -> 'https://example.org/1tqn'; */
function combineUrl(firstPart, ...moreParts) {
let result = firstPart;
for (const part of moreParts) {
result = result.replace(/\/$/, '') + '/' + part; // removing extra trailing slash
}
return result;
}
/** Create a copy of object `object`, fill in missing/undefined keys using `defaults`.
* This is similar to {...defaults,...object} but `undefined` in `object` will not override a value from `defaults`. */
function addDefaults(object, defaults) {
var _a;
const result = Object.assign({}, object);
for (const key in defaults) {
(_a = result[key]) !== null && _a !== void 0 ? _a : (result[key] = defaults[key]);
}
return result;
}
/** Convert `colorVal` from any of supported color formats (e.g. 'yellow', '#ffff00', {r:255,g:255,b:0}) to `Color`.
* Return default color (gray) if `colorVal` is undefined or null.
*/
function normalizeColor(colorVal, defaultColor = color_1.Color.fromRgb(170, 170, 170)) {
var _a, _b, _c;
try {
if (colorVal === undefined || colorVal === null)
return defaultColor;
if (typeof colorVal === 'number')
return (0, color_1.Color)(colorVal);
if (typeof colorVal === 'string' && colorVal[0] === '#')
return (0, color_1.Color)(Number(`0x${colorVal.substring(1)}`));
if (typeof colorVal === 'string' && colorVal in names_1.ColorNames)
return names_1.ColorNames[colorVal];
if (typeof colorVal === 'object')
return color_1.Color.fromRgb((_a = colorVal.r) !== null && _a !== void 0 ? _a : 0, (_b = colorVal.g) !== null && _b !== void 0 ? _b : 0, (_c = colorVal.b) !== null && _c !== void 0 ? _c : 0);
}
catch (_d) {
// do nothing
}
return defaultColor;
}
/** Apply overpaint to every representation of every component in a structure.
* Excludes representations created as "added representations" by `PDBeMolstarPlugin.visual.select`. */
function applyOverpaint(plugin, structRef, overpaintLayers) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a;
if (overpaintLayers.length === 0)
return;
const update = plugin.build();
for (const component of structRef.components) {
if ((_a = component.cell.transform.tags) === null || _a === void 0 ? void 0 : _a.includes(exports.Tags.AddedComponent))
continue;
for (const repr of component.representations) {
const currentOverpaint = plugin.state.data.select(mol_state_1.StateSelection.Generators
.ofTransformer(transforms_1.StateTransforms.Representation.OverpaintStructureRepresentation3DFromBundle, repr.cell.transform.ref)
.withTag(exports.Tags.Overpaint));
if (currentOverpaint.length === 0) {
// Create a new overpaint
update.to(repr.cell.transform.ref).apply(transforms_1.StateTransforms.Representation.OverpaintStructureRepresentation3DFromBundle, { layers: overpaintLayers }, { tags: exports.Tags.Overpaint });
}
else {
// Add layers to existing overpaint
update.to(currentOverpaint[0]).update(old => ({ layers: old.layers.concat(overpaintLayers) }));
}
}
}
yield update.commit();
});
}
exports.Tags = {
/** Tag needed for `clearStructureOverpaint`; defined in src/mol-plugin-state/helpers/structure-overpaint.ts but private */
Overpaint: 'overpaint-controls',
/** Marks structure components added by `select` */
AddedComponent: 'pdbe-molstar.added-component',
};
exports.StructureComponentTags = {
polymer: ['structure-component-static-polymer'],
het: ['structure-component-static-ligand', 'structure-component-static-ion'],
water: ['structure-component-static-water'],
carbs: ['structure-component-static-branched'],
nonStandard: ['structure-component-static-non-standard'],
coarse: ['structure-component-static-coarse'],
maps: ['volume-streaming-info'],
};
/** Return component type based on the component's PluginStateObject tags */
function getComponentTypeFromTags(tags) {
let type;
for (type in exports.StructureComponentTags) {
const typeTags = exports.StructureComponentTags[type];
if (typeTags.some(tag => tags === null || tags === void 0 ? void 0 : tags.includes(tag))) {
return type;
}
}
return undefined;
}
/** Return a new array containing `values` without duplicates (only first occurrence will be kept).
* Values v1, v2 are considered duplicates when `key(v1)===key(v2)`. */
function distinct(values, key = (value => value)) {
const out = [];
const seenKeys = new Set();
for (const value of values) {
const theKey = key(value);
if (!seenKeys.has(theKey)) {
out.push(value);
seenKeys.add(theKey);
}
}
return out;
}
/** Group elements by result of `groupFunction` applied to them */
function groupElements(elements, groupFunction) {
const groups = [];
const members = new Map();
for (const elem of elements) {
const g = groupFunction(elem);
if (members.has(g)) {
members.get(g).push(elem);
}
else {
groups.push(g);
members.set(g, [elem]);
}
}
return {
/** Groups (results of `groupFunction`) in order as they first appeared in `elements` */
groups,
/** Mapping of groups to lists of they members */
members,
};
}
/** Return a mapping of elements to their index in the `elements` array */
function createIndex(elements) {
const index = new Map();
elements.forEach((elem, i) => index.set(elem, i));
return index;
}
/** Return modulo of two numbers (a % b) within range [0, b) */
function nonnegativeModulo(a, b) {
const modulo = a % b;
return (modulo < 0) ? modulo + b : modulo;
}
/** Queue for running jobs where enqueued jobs get discarded when a new job is enqueued.
* (Discarded jobs may or may not actually be executed, but their result is not accessible anyway.) */
class PreemptiveQueue {
constructor(run) {
this.run = run;
}
/** Enqueue a job which will execute `run(args)`.
* Return a promise that either resolves to `{ status: 'completed', result }` where `result` is `await run(args)`,
* or resolves to `{ status: 'cancelled' }` if the job starts being executed but another jobs gets enqueued before its completion,
* or resolves to `{ status: 'skipped' }` if the job does not even start before another jobs gets enqueued,
* or rejects if the job is completed but throws an error. */
requestRun(args) {
if (this.running) {
this.running.cancelled = true;
this.running.callbacks.resolve({ status: 'cancelled' });
}
if (this.queuing) {
this.queuing.callbacks.resolve({ status: 'skipped' });
}
const promise = new Promise((resolve, reject) => {
this.queuing = { args, callbacks: { resolve, reject } };
});
if (!this.running) {
this.handleRequests(); // do not await
}
return promise;
}
/** Request handling loop. Resolves when there are no more requests. Not to be awaited, should run in the background. */
handleRequests() {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
while (this.queuing) {
this.running = this.queuing;
this.queuing = undefined;
yield (0, sleep_1.sleep)(0); // let other things happen (this pushes the rest of the function to the end of the queue)
try {
const result = yield this.run(this.running.args);
if (!this.running.cancelled) {
this.running.callbacks.resolve({ status: 'completed', result });
}
}
catch (error) {
if (!this.running.cancelled) {
this.running.callbacks.reject(error);
}
}
this.running = undefined;
}
});
}
}
exports.PreemptiveQueue = PreemptiveQueue;
/** Functions for working with plugin config items */
var PluginConfigUtils;
(function (PluginConfigUtils) {
/** Retrieve config values for items in `configItems` from the current plugin config */
function getConfigValues(plugin, configItems, defaults) {
var _a;
const values = {};
for (const name in configItems) {
values[name] = (_a = plugin === null || plugin === void 0 ? void 0 : plugin.config.get(configItems[name])) !== null && _a !== void 0 ? _a : defaults[name];
}
return values;
}
PluginConfigUtils.getConfigValues = getConfigValues;
})(PluginConfigUtils || (exports.PluginConfigUtils = PluginConfigUtils = {}));
function pluginLayoutStateFromInitParams(initParams) {
return {
isExpanded: initParams.expanded,
showControls: !initParams.hideControls,
regionState: {
left: initParams.leftPanel ? 'full' : 'hidden',
right: initParams.rightPanel ? 'full' : 'hidden',
top: initParams.sequencePanel ? 'full' : 'hidden',
bottom: initParams.logPanel ? 'full' : 'hidden',
},
controlsDisplay: initParams.reactive ? 'reactive' : initParams.landscape ? 'landscape' : layout_1.PluginLayoutStateParams.controlsDisplay.defaultValue,
};
}
function getRotationMat4(view) {
switch (view) {
case 'front': return linear_algebra_1.Mat4.identity();
case 'back': return [-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1];
case 'right': return [0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1];
case 'left': return [0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1];
case 'top': return [1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1];
case 'bottom': return [1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1];
default: {
const out = linear_algebra_1.Mat4.fromMat3((0, linear_algebra_1.Mat4)(), view);
out[15] = 1;
return out;
}
}
}