pdbe-molstar 3.3.0 → 3.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StateGalleryManager = void 0;
3
+ exports.StateGalleryManager = exports.ImageCategory = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const camera_1 = require("molstar/lib/mol-canvas3d/camera");
6
6
  const linear_algebra_1 = require("molstar/lib/mol-math/linear-algebra");
@@ -11,13 +11,24 @@ const rxjs_1 = require("rxjs");
11
11
  const helpers_1 = require("../../helpers");
12
12
  const behavior_1 = require("./behavior");
13
13
  const config_1 = require("./config");
14
- const ImageCategory = ['Entry', 'Assemblies', 'Entities', 'Ligands', 'Modified residues', 'Domains', 'Miscellaneous'];
14
+ const titles_1 = require("./titles");
15
+ /** Categories of images/states */
16
+ exports.ImageCategory = ['Entry', 'Assemblies', 'Entities', 'Ligands', 'Modified residues', 'Domains', 'Miscellaneous'];
17
+ /** Provides functionality to get list of images (3D states) for an entry, load individual images, keeps track of the currently loaded image.
18
+ * Use async `StateGalleryManager.create()` to create an instance. */
15
19
  class StateGalleryManager {
16
- constructor(plugin, entryId, data, options) {
20
+ constructor(plugin,
21
+ /** Entry identifier, i.e. '1cbs' */
22
+ entryId,
23
+ /** Data retrieved from API */
24
+ data,
25
+ /** Config values */
26
+ options) {
17
27
  this.plugin = plugin;
18
28
  this.entryId = entryId;
19
29
  this.data = data;
20
30
  this.options = options;
31
+ /** BehaviorSubjects for current state of the manager */
21
32
  this.events = {
22
33
  /** Image that has been requested to load most recently. */
23
34
  requestedImage: new rxjs_1.BehaviorSubject(undefined),
@@ -29,6 +40,7 @@ class StateGalleryManager {
29
40
  /** True if at least one image has been loaded (this is to skip animation on the first load) */
30
41
  this.firstLoaded = false;
31
42
  this.loader = new helpers_1.PreemptiveQueue((filename) => this._load(filename));
43
+ /** Cache for MOLJ states from API */
32
44
  this.cache = {};
33
45
  const allImages = listImages(data, true);
34
46
  this.images = removeWithSuffixes(allImages, ['_side', '_top']); // removing images in different orientation than 'front'
@@ -41,13 +53,15 @@ class StateGalleryManager {
41
53
  (_c = customState.manager) === null || _c === void 0 ? void 0 : _c.next(this);
42
54
  });
43
55
  this.events.requestedImage.subscribe(img => {
44
- var _a, _b, _c, _d;
56
+ var _a, _b, _c;
45
57
  const customState = (0, behavior_1.StateGalleryCustomState)(this.plugin);
46
- (_a = customState.title) === null || _a === void 0 ? void 0 : _a.next((_b = img === null || img === void 0 ? void 0 : img.simple_title) !== null && _b !== void 0 ? _b : img === null || img === void 0 ? void 0 : img.filename);
47
- if (((_c = customState.manager) === null || _c === void 0 ? void 0 : _c.value) !== this)
48
- (_d = customState.manager) === null || _d === void 0 ? void 0 : _d.next(this);
58
+ (_a = customState.requestedImage) === null || _a === void 0 ? void 0 : _a.next(img);
59
+ if (((_b = customState.manager) === null || _b === void 0 ? void 0 : _b.value) !== this)
60
+ (_c = customState.manager) === null || _c === void 0 ? void 0 : _c.next(this);
49
61
  });
50
62
  }
63
+ /** Create an instance of `StateGalleryManager` and retrieve list of images from API.
64
+ * Options that are not provided will use values from plugin config. */
51
65
  static create(plugin, entryId, options) {
52
66
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
53
67
  const fullOptions = Object.assign(Object.assign({}, (0, config_1.getStateGalleryConfig)(plugin)), options);
@@ -58,6 +72,7 @@ class StateGalleryManager {
58
72
  return new this(plugin, entryId, data, fullOptions);
59
73
  });
60
74
  }
75
+ /** Load an image (3D state). Do not call directly; use `load` instead, which handles concurrent requests. */
61
76
  _load(filename) {
62
77
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
63
78
  if (!this.plugin.canvas3d)
@@ -82,6 +97,7 @@ class StateGalleryManager {
82
97
  this.firstLoaded = true;
83
98
  });
84
99
  }
100
+ /** Request to load an image (3D state). When there are multiple concurrent requests, some requests may be skipped (will resolve to `{ status: 'cancelled' }` or `{ status: 'skipped' }`) as only the last request is really important. */
85
101
  load(img) {
86
102
  var _a;
87
103
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
@@ -108,6 +124,7 @@ class StateGalleryManager {
108
124
  }
109
125
  });
110
126
  }
127
+ /** Move to next/previous image in the list. */
111
128
  shift(shift) {
112
129
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
113
130
  const current = this.events.requestedImage.value;
@@ -117,16 +134,19 @@ class StateGalleryManager {
117
134
  return yield this.load(this.images[iNew]);
118
135
  });
119
136
  }
137
+ /** Request to load the previous image in the list */
120
138
  loadPrevious() {
121
139
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
122
140
  return yield this.shift(-1);
123
141
  });
124
142
  }
143
+ /** Request to load the next image in the list */
125
144
  loadNext() {
126
145
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
127
146
  return yield this.shift(1);
128
147
  });
129
148
  }
149
+ /** Fetch a MOLJ state from API */
130
150
  fetchSnapshot(filename) {
131
151
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
132
152
  const url = (0, helpers_1.combineUrl)(this.options.ServerUrl, `${filename}.molj`);
@@ -134,6 +154,7 @@ class StateGalleryManager {
134
154
  return data;
135
155
  });
136
156
  }
157
+ /** Get MOLJ state for the image (get from cache or fetch from API) */
137
158
  getSnapshot(filename) {
138
159
  var _a;
139
160
  var _b;
@@ -141,6 +162,7 @@ class StateGalleryManager {
141
162
  return (_a = (_b = this.cache)[filename]) !== null && _a !== void 0 ? _a : (_b[filename] = yield this.fetchSnapshot(filename));
142
163
  });
143
164
  }
165
+ /** Get full image information based on filename. Return `undefined` if image with given filename is not in the list. */
144
166
  getImageByFilename(filename) {
145
167
  const index = this.filenameIndex.get(filename);
146
168
  if (index === undefined)
@@ -149,6 +171,7 @@ class StateGalleryManager {
149
171
  }
150
172
  }
151
173
  exports.StateGalleryManager = StateGalleryManager;
174
+ /** Get the list of images, captions etc. for an entry from API */
152
175
  function getData(plugin, serverUrl, entryId) {
153
176
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
154
177
  const url = (0, helpers_1.combineUrl)(serverUrl, entryId + '.json');
@@ -168,62 +191,52 @@ function listImages(data, byCategory = false) {
168
191
  const out = [];
169
192
  // Entry
170
193
  for (const img of (_c = (_b = (_a = data === null || data === void 0 ? void 0 : data.entry) === null || _a === void 0 ? void 0 : _a.all) === null || _b === void 0 ? void 0 : _b.image) !== null && _c !== void 0 ? _c : []) {
171
- const title = img.filename.includes('_chemically_distinct_molecules')
172
- ? 'Deposited model (color by entity)'
173
- : img.filename.includes('_chain')
174
- ? 'Deposited model (color by chain)'
175
- : undefined;
176
- out.push(Object.assign(Object.assign({}, img), { category: 'Entry', simple_title: title }));
194
+ out.push(Object.assign(Object.assign(Object.assign({}, img), { category: 'Entry' }), titles_1.ImageTitles.entry(img)));
177
195
  }
178
196
  // Validation
179
197
  for (const img of (_g = (_f = (_e = (_d = data === null || data === void 0 ? void 0 : data.validation) === null || _d === void 0 ? void 0 : _d.geometry) === null || _e === void 0 ? void 0 : _e.deposited) === null || _f === void 0 ? void 0 : _f.image) !== null && _g !== void 0 ? _g : []) {
180
- out.push(Object.assign(Object.assign({}, img), { category: 'Entry', simple_title: 'Geometry validation' }));
198
+ out.push(Object.assign(Object.assign(Object.assign({}, img), { category: 'Entry' }), titles_1.ImageTitles.validation(img)));
181
199
  }
182
200
  // Bfactor
183
201
  for (const img of (_k = (_j = (_h = data === null || data === void 0 ? void 0 : data.entry) === null || _h === void 0 ? void 0 : _h.bfactor) === null || _j === void 0 ? void 0 : _j.image) !== null && _k !== void 0 ? _k : []) {
184
- out.push(Object.assign(Object.assign({}, img), { category: 'Entry', simple_title: 'B-factor' }));
202
+ out.push(Object.assign(Object.assign(Object.assign({}, img), { category: 'Entry' }), titles_1.ImageTitles.bfactor(img)));
185
203
  }
186
204
  // Assembly
187
205
  const assemblies = data === null || data === void 0 ? void 0 : data.assembly;
188
- for (const ass in assemblies) {
189
- for (const img of (_l = assemblies[ass].image) !== null && _l !== void 0 ? _l : []) {
190
- const title = img.filename.includes('_chemically_distinct_molecules')
191
- ? `Assembly ${ass} (color by entity)`
192
- : img.filename.includes('_chain')
193
- ? `Assembly ${ass} (color by chain)`
194
- : undefined;
195
- out.push(Object.assign(Object.assign({}, img), { category: 'Assemblies', simple_title: title }));
206
+ for (const assemblyId in assemblies) {
207
+ for (const img of (_l = assemblies[assemblyId].image) !== null && _l !== void 0 ? _l : []) {
208
+ out.push(Object.assign(Object.assign(Object.assign({}, img), { category: 'Assemblies' }), titles_1.ImageTitles.assembly(img, { assemblyId })));
196
209
  }
197
210
  }
198
211
  // Entity
199
212
  const entities = data === null || data === void 0 ? void 0 : data.entity;
200
- for (const entity in entities) {
201
- for (const img of (_m = entities[entity].image) !== null && _m !== void 0 ? _m : []) {
202
- out.push(Object.assign(Object.assign({}, img), { category: 'Entities', simple_title: `Entity ${entity}` }));
213
+ for (const entityId in entities) {
214
+ for (const img of (_m = entities[entityId].image) !== null && _m !== void 0 ? _m : []) {
215
+ out.push(Object.assign(Object.assign(Object.assign({}, img), { category: 'Entities' }), titles_1.ImageTitles.entity(img, { entityId })));
203
216
  }
204
217
  }
205
218
  // Ligand
206
219
  const ligands = (_o = data === null || data === void 0 ? void 0 : data.entry) === null || _o === void 0 ? void 0 : _o.ligands;
207
- for (const ligand in ligands) {
208
- for (const img of (_p = ligands[ligand].image) !== null && _p !== void 0 ? _p : []) {
209
- out.push(Object.assign(Object.assign({}, img), { category: 'Ligands', simple_title: `Ligand environment for ${ligand}` }));
220
+ for (const compId in ligands) {
221
+ for (const img of (_p = ligands[compId].image) !== null && _p !== void 0 ? _p : []) {
222
+ out.push(Object.assign(Object.assign(Object.assign({}, img), { category: 'Ligands' }), titles_1.ImageTitles.ligand(img, { compId })));
210
223
  }
211
224
  }
212
225
  // Modres
213
226
  const modres = (_q = data === null || data === void 0 ? void 0 : data.entry) === null || _q === void 0 ? void 0 : _q.mod_res;
214
- for (const res in modres) {
215
- for (const img of (_r = modres[res].image) !== null && _r !== void 0 ? _r : []) {
216
- out.push(Object.assign(Object.assign({}, img), { category: 'Modified residues', simple_title: `Modified residue ${res}` }));
227
+ for (const compId in modres) {
228
+ for (const img of (_r = modres[compId].image) !== null && _r !== void 0 ? _r : []) {
229
+ out.push(Object.assign(Object.assign(Object.assign({}, img), { category: 'Modified residues' }), titles_1.ImageTitles.modres(img, { compId })));
217
230
  }
218
231
  }
219
232
  // Domain
220
- for (const entity in entities) {
221
- const dbs = entities[entity].database;
233
+ for (const entityId in entities) {
234
+ const dbs = entities[entityId].database;
222
235
  for (const db in dbs) {
223
236
  const domains = dbs[db];
224
- for (const domain in domains) {
225
- for (const img of (_s = domains[domain].image) !== null && _s !== void 0 ? _s : []) {
226
- out.push(Object.assign(Object.assign({}, img), { category: 'Domains', simple_title: `${db} ${domain} (entity ${entity})` }));
237
+ for (const familyId in domains) {
238
+ for (const img of (_s = domains[familyId].image) !== null && _s !== void 0 ? _s : []) {
239
+ out.push(Object.assign(Object.assign(Object.assign({}, img), { category: 'Domains' }), titles_1.ImageTitles.domain(img, { db, familyId, entityId })));
227
240
  }
228
241
  }
229
242
  }
@@ -250,6 +263,7 @@ function pushImages(out, data) {
250
263
  }
251
264
  return out;
252
265
  }
266
+ /** Return a filtered list of images, removing all images with filename ending in one of `suffixes` */
253
267
  function removeWithSuffixes(images, suffixes) {
254
268
  return images.filter(img => !suffixes.some(suffix => img.filename.endsWith(suffix)));
255
269
  }
@@ -278,12 +292,15 @@ function getCameraFromSnapshot(snapshot) {
278
292
  const json = JSON.parse(snapshot);
279
293
  return (_d = (_c = (_b = (_a = json === null || json === void 0 ? void 0 : json.entries) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.snapshot) === null || _c === void 0 ? void 0 : _c.camera) === null || _d === void 0 ? void 0 : _d.current;
280
294
  }
295
+ /** Recalculate camera distance from target in `snapshot` based on `snapshot.radius`,
296
+ * keeping target, direction, and up from snapshot but using camera mode and FOV from `camera`. */
281
297
  function refocusCameraSnapshot(camera, snapshot) {
282
298
  if (snapshot === undefined)
283
299
  return undefined;
284
300
  const dir = linear_algebra_1.Vec3.sub((0, linear_algebra_1.Vec3)(), snapshot.target, snapshot.position);
285
301
  return camera.getInvariantFocus(snapshot.target, snapshot.radius, snapshot.up, dir);
286
302
  }
303
+ /** Get current camera positioning */
287
304
  function getCurrentCamera(plugin) {
288
305
  if (!plugin.canvas3d)
289
306
  return camera_1.Camera.createDefaultSnapshot();
@@ -0,0 +1,26 @@
1
+ import { Image } from './manager';
2
+ type Titles = Pick<Image, 'title' | 'subtitle'>;
3
+ /** Functions for creating informative image (3D state) titles for display in UI */
4
+ export declare const ImageTitles: {
5
+ entry(img: Image): Titles;
6
+ validation(img: Image): Titles;
7
+ bfactor(img: Image): Titles;
8
+ assembly(img: Image, info: {
9
+ assemblyId: string;
10
+ }): Titles;
11
+ entity(img: Image, info: {
12
+ entityId: string;
13
+ }): Titles;
14
+ ligand(img: Image, info: {
15
+ compId: string;
16
+ }): Titles;
17
+ modres(img: Image, info: {
18
+ compId: string;
19
+ }): Titles;
20
+ domain(img: Image, info: {
21
+ db: string;
22
+ familyId: string;
23
+ entityId: string;
24
+ }): Titles;
25
+ };
26
+ export {};
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ImageTitles = void 0;
4
+ /** Functions for creating informative image (3D state) titles for display in UI */
5
+ exports.ImageTitles = {
6
+ entry(img) {
7
+ if (img.filename.includes('_chemically_distinct_molecules')) {
8
+ return { title: 'Deposited model (color by entity)' };
9
+ }
10
+ if (img.filename.includes('_chain')) {
11
+ return { title: 'Deposited model (color by chain)' };
12
+ }
13
+ return {};
14
+ },
15
+ validation(img) {
16
+ return { title: 'Geometry validation' };
17
+ },
18
+ bfactor(img) {
19
+ return { title: 'B-factor' };
20
+ },
21
+ assembly(img, info) {
22
+ if (img.filename.includes('_chemically_distinct_molecules')) {
23
+ return { title: `Assembly ${info.assemblyId} (color by entity)` };
24
+ }
25
+ if (img.filename.includes('_chain')) {
26
+ return { title: `Assembly ${info.assemblyId} (color by chain)` };
27
+ }
28
+ return {};
29
+ },
30
+ entity(img, info) {
31
+ const entityName = getSpans(img.description)[1];
32
+ return {
33
+ title: `Entity ${info.entityId}`,
34
+ subtitle: entityName,
35
+ };
36
+ },
37
+ ligand(img, info) {
38
+ const ligandName = getParenthesis(getSpans(img.description)[0]);
39
+ return {
40
+ title: `Ligand environment for ${info.compId}`,
41
+ subtitle: ligandName,
42
+ };
43
+ },
44
+ modres(img, info) {
45
+ const modresName = getParenthesis(getSpans(img.description)[1]);
46
+ return {
47
+ title: `Modified residue ${info.compId}`,
48
+ subtitle: modresName,
49
+ };
50
+ },
51
+ domain(img, info) {
52
+ const familyName = getParenthesis(getSpans(img.description)[1]);
53
+ return {
54
+ title: `${info.db} ${info.familyId} (entity ${info.entityId})`,
55
+ subtitle: familyName,
56
+ };
57
+ },
58
+ };
59
+ /** Get contents of `<span ...>...</span>` tags from an HTML string */
60
+ function getSpans(text) {
61
+ const matches = (text !== null && text !== void 0 ? text : '').matchAll(/<span [^>]*>([^<]*)<\/span>/g);
62
+ return Array.from(matches).map(match => match[1]);
63
+ }
64
+ /** Get content of parenthesis (`(...)`) from a string */
65
+ function getParenthesis(text) {
66
+ var _a;
67
+ return (_a = text === null || text === void 0 ? void 0 : text.match(/\((.*)\)/)) === null || _a === void 0 ? void 0 : _a[1];
68
+ }
@@ -1,10 +1,14 @@
1
1
  import { CollapsableControls, CollapsableState } from 'molstar/lib/mol-plugin-ui/base';
2
2
  import React from 'react';
3
3
  import { StateGalleryManager } from './manager';
4
+ /** React state for `StateGalleryControls` */
4
5
  interface StateGalleryControlsState {
6
+ /** Content of "Entry ID" text field */
5
7
  entryId: string;
6
8
  manager: StateGalleryManager | undefined;
9
+ /** `true` when initializing manager (fetching list of images) */
7
10
  isLoading: boolean;
11
+ /** Mirrors `this.plugin.behaviors.state.isBusy` (`true` when loading a specific image) */
8
12
  isBusy: boolean;
9
13
  }
10
14
  /** "3D State Gallery" section in Structure Tools (right panel) */
@@ -12,12 +16,16 @@ export declare class StateGalleryControls extends CollapsableControls<{}, StateG
12
16
  protected defaultState(): StateGalleryControlsState & CollapsableState;
13
17
  componentDidMount(): void;
14
18
  private get values();
15
- private setEntryId;
16
- private load;
17
19
  private onChangeValues;
20
+ /** Load entry given by `this.state.entryId` */
21
+ private load;
18
22
  private loadDisabled;
19
23
  protected renderControls(): React.JSX.Element | null;
20
24
  }
21
- /** Box in viewport with state title and arrows to move between states */
25
+ /** Part of "3D State Gallery" section related to a specific entry */
26
+ export declare function StateGalleryManagerControls(props: {
27
+ manager: StateGalleryManager;
28
+ }): import("react/jsx-runtime").JSX.Element;
29
+ /** Box in viewport with image title and arrows to move between images (3D states) */
22
30
  export declare function StateGalleryTitleBox(): import("react/jsx-runtime").JSX.Element | null;
23
31
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.StateGalleryTitleBox = exports.StateGalleryControls = void 0;
3
+ exports.StateGalleryTitleBox = exports.StateGalleryManagerControls = exports.StateGalleryControls = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const jsx_runtime_1 = require("react/jsx-runtime");
6
6
  const base_1 = require("molstar/lib/mol-plugin-ui/base");
@@ -13,18 +13,18 @@ const helpers_1 = require("../../helpers");
13
13
  const icons_2 = require("../../ui/icons");
14
14
  const behavior_1 = require("./behavior");
15
15
  const manager_1 = require("./manager");
16
+ /** Parameter definition for ParameterControls part of "3D State Gallery" section */
16
17
  const Params = {
17
- entryId: param_definition_1.ParamDefinition.Text(),
18
+ entryId: param_definition_1.ParamDefinition.Text(undefined, { label: 'Entry ID' }),
18
19
  };
19
20
  /** "3D State Gallery" section in Structure Tools (right panel) */
20
21
  class StateGalleryControls extends base_1.CollapsableControls {
21
22
  constructor() {
22
23
  super(...arguments);
23
- this.setEntryId = (entryId) => {
24
- this.setState(old => ({
25
- entryId,
26
- }));
24
+ this.onChangeValues = (values) => {
25
+ this.setState({ entryId: values.entryId });
27
26
  };
27
+ /** Load entry given by `this.state.entryId` */
28
28
  this.load = () => tslib_1.__awaiter(this, void 0, void 0, function* () {
29
29
  if (this.loadDisabled())
30
30
  return;
@@ -32,9 +32,6 @@ class StateGalleryControls extends base_1.CollapsableControls {
32
32
  const manager = yield manager_1.StateGalleryManager.create(this.plugin, this.state.entryId);
33
33
  this.setState({ manager, isLoading: false, description: this.state.entryId.toUpperCase() });
34
34
  });
35
- this.onChangeValues = (values) => {
36
- this.setEntryId(values.entryId);
37
- };
38
35
  this.loadDisabled = () => { var _a; return !this.state.entryId || this.state.entryId === ((_a = this.state.manager) === null || _a === void 0 ? void 0 : _a.entryId) || this.state.isBusy || this.state.isLoading; };
39
36
  }
40
37
  defaultState() {
@@ -59,7 +56,7 @@ class StateGalleryControls extends base_1.CollapsableControls {
59
56
  if (this.state.entryId === '' && sel.structures.length > 0) {
60
57
  const id = (_a = sel.structures[0].cell.obj) === null || _a === void 0 ? void 0 : _a.data.model.entryId;
61
58
  if (id) {
62
- this.setEntryId(id.toLowerCase());
59
+ this.setState({ entryId: id.toLowerCase() });
63
60
  }
64
61
  }
65
62
  });
@@ -73,11 +70,12 @@ class StateGalleryControls extends base_1.CollapsableControls {
73
70
  renderControls() {
74
71
  return (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(parameters_1.ParameterControls, { params: Params, values: this.values, onChangeValues: this.onChangeValues, onEnter: this.load }), (!this.state.manager || this.state.manager.entryId !== this.state.entryId) &&
75
72
  (0, jsx_runtime_1.jsx)(common_1.Button, Object.assign({ icon: this.state.isLoading ? undefined : icons_1.CheckSvg, title: 'Load', disabled: this.loadDisabled(), onClick: this.load, className: 'msp-btn-block' }, { children: this.state.isLoading ? 'Loading...' : 'Load' })), this.state.manager &&
76
- (0, jsx_runtime_1.jsx)(ManagerControls, { manager: this.state.manager })] });
73
+ (0, jsx_runtime_1.jsx)(StateGalleryManagerControls, { manager: this.state.manager })] });
77
74
  }
78
75
  }
79
76
  exports.StateGalleryControls = StateGalleryControls;
80
- function ManagerControls(props) {
77
+ /** Part of "3D State Gallery" section related to a specific entry */
78
+ function StateGalleryManagerControls(props) {
81
79
  var _a;
82
80
  const images = props.manager.images;
83
81
  const nImages = images.length;
@@ -108,21 +106,23 @@ function ManagerControls(props) {
108
106
  }
109
107
  return (0, jsx_runtime_1.jsxs)("div", Object.assign({ className: 'pdbemolstar-state-gallery-controls', onKeyDown: handleKeyDown, tabIndex: -1, ref: keyDownTargetRef }, { children: [(0, jsx_runtime_1.jsx)(common_1.ExpandGroup, Object.assign({ header: 'States', initiallyExpanded: true }, { children: (0, jsx_runtime_1.jsx)("div", Object.assign({ style: { marginBottom: 8 } }, { children: categories.groups.map(cat => {
110
108
  var _a;
111
- return (0, jsx_runtime_1.jsx)(common_1.ExpandGroup, Object.assign({ header: cat, initiallyExpanded: true }, { children: (_a = categories.members.get(cat)) === null || _a === void 0 ? void 0 : _a.map(img => (0, jsx_runtime_1.jsx)(StateButton, { img: img, isSelected: img === selected, status: status, onClick: () => props.manager.load(img) }, img.filename)) }), cat);
109
+ return (0, jsx_runtime_1.jsx)(common_1.ExpandGroup, Object.assign({ header: cat, initiallyExpanded: true }, { children: (_a = categories.members.get(cat)) === null || _a === void 0 ? void 0 : _a.map(img => (0, jsx_runtime_1.jsx)(ImageButton, { img: img, isSelected: img === selected, status: status, onClick: () => props.manager.load(img) }, img.filename)) }), cat);
112
110
  }) })) }), 'states'), (0, jsx_runtime_1.jsx)(common_1.ExpandGroup, Object.assign({ header: 'Description', initiallyExpanded: true }, { children: (0, jsx_runtime_1.jsx)("div", Object.assign({ className: 'pdbemolstar-state-gallery-legend' }, { children: (0, jsx_runtime_1.jsx)("div", { dangerouslySetInnerHTML: { __html: (_a = selected === null || selected === void 0 ? void 0 : selected.description) !== null && _a !== void 0 ? _a : '' } }) })) }), 'description')] }));
113
111
  }
114
- function StateButton(props) {
112
+ exports.StateGalleryManagerControls = StateGalleryManagerControls;
113
+ /** Button with image title */
114
+ function ImageButton(props) {
115
115
  var _a;
116
116
  const { img, isSelected, status, onClick } = props;
117
117
  const icon = !isSelected ? icons_2.EmptyIconSvg : (status === 'loading') ? icons_2.HourglassBottomSvg : (status === 'error') ? icons_1.ErrorSvg : icons_1.CheckSvg;
118
- const title = (_a = img.simple_title) !== null && _a !== void 0 ? _a : img.filename;
119
- const errorMsg = (isSelected && status === 'error') ? '[Failed to load] ' : '';
120
- return (0, jsx_runtime_1.jsx)(common_1.Button, Object.assign({ className: 'msp-action-menu-button pdbemolstar-state-gallery-state-button', icon: icon, onClick: onClick, title: `${errorMsg}${title}`, style: { fontWeight: isSelected ? 'bold' : undefined } }, { children: title }));
118
+ const tooltip = imageTooltip(img, isSelected ? status : 'ready');
119
+ return (0, jsx_runtime_1.jsxs)(common_1.Button, Object.assign({ className: 'msp-action-menu-button pdbemolstar-state-gallery-state-button', icon: icon, onClick: onClick, title: tooltip, style: { fontWeight: isSelected ? 'bold' : undefined } }, { children: [(_a = img.title) !== null && _a !== void 0 ? _a : img.filename, img.subtitle && (0, jsx_runtime_1.jsxs)("small", { children: ["\u2002 ", img.subtitle] })] }));
121
120
  }
122
- /** Box in viewport with state title and arrows to move between states */
121
+ /** Box in viewport with image title and arrows to move between images (3D states) */
123
122
  function StateGalleryTitleBox() {
123
+ var _a;
124
124
  const plugin = react_1.default.useContext(base_1.PluginReactContext);
125
- const [title, setTitle] = react_1.default.useState(undefined);
125
+ const [image, setImage] = react_1.default.useState(undefined);
126
126
  const [manager, setManager] = react_1.default.useState(undefined);
127
127
  const [status, setStatus] = react_1.default.useState('ready');
128
128
  const loadingCounter = (0, react_1.useRef)(0);
@@ -130,8 +130,8 @@ function StateGalleryTitleBox() {
130
130
  var _a, _b, _c;
131
131
  const customState = (0, behavior_1.StateGalleryCustomState)(plugin);
132
132
  const subs = [
133
- (_a = customState.title) === null || _a === void 0 ? void 0 : _a.subscribe(x => setTitle(x)),
134
- (_b = customState.manager) === null || _b === void 0 ? void 0 : _b.subscribe(x => setManager(x)),
133
+ (_a = customState.requestedImage) === null || _a === void 0 ? void 0 : _a.subscribe(img => setImage(img)),
134
+ (_b = customState.manager) === null || _b === void 0 ? void 0 : _b.subscribe(mgr => setManager(mgr)),
135
135
  (_c = customState.status) === null || _c === void 0 ? void 0 : _c.subscribe(status => {
136
136
  const counter = ++loadingCounter.current;
137
137
  if (status === 'loading') {
@@ -145,10 +145,18 @@ function StateGalleryTitleBox() {
145
145
  ];
146
146
  return () => subs.forEach(sub => sub === null || sub === void 0 ? void 0 : sub.unsubscribe());
147
147
  }, [plugin]);
148
- if (title === undefined)
148
+ if (image === undefined)
149
149
  return null;
150
150
  return (0, jsx_runtime_1.jsxs)("div", Object.assign({ className: 'pdbemolstar-state-gallery-title-box' }, { children: [manager &&
151
- (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(common_1.Button, { className: 'msp-btn-icon', title: 'Previous state', icon: icons_2.ChevronLeftSvg, onClick: () => manager.loadPrevious() }) }), (0, jsx_runtime_1.jsxs)("div", Object.assign({ className: 'pdbemolstar-state-gallery-title', title: status === 'error' ? `${title} (failed to load)` : status === 'loading' ? `${title} (loading)` : title }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: 'pdbemolstar-state-gallery-title-icon' }, { children: (0, jsx_runtime_1.jsx)(icons_1.Icon, { svg: status === 'error' ? icons_1.ErrorSvg : status === 'loading' ? icons_2.HourglassBottomSvg : icons_2.EmptyIconSvg }) })), (0, jsx_runtime_1.jsx)("div", Object.assign({ className: 'pdbemolstar-state-gallery-title-text' }, { children: title }))] })), manager &&
151
+ (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(common_1.Button, { className: 'msp-btn-icon', title: 'Previous state', icon: icons_2.ChevronLeftSvg, onClick: () => manager.loadPrevious() }) }), (0, jsx_runtime_1.jsxs)("div", Object.assign({ className: 'pdbemolstar-state-gallery-title', title: imageTooltip(image, status) }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: 'pdbemolstar-state-gallery-title-icon' }, { children: (0, jsx_runtime_1.jsx)(icons_1.Icon, { svg: status === 'error' ? icons_1.ErrorSvg : status === 'loading' ? icons_2.HourglassBottomSvg : icons_2.EmptyIconSvg }) })), (0, jsx_runtime_1.jsxs)("div", Object.assign({ className: 'pdbemolstar-state-gallery-title-text' }, { children: [(_a = image.title) !== null && _a !== void 0 ? _a : image.filename, (0, jsx_runtime_1.jsx)("br", {}), (0, jsx_runtime_1.jsx)("small", { children: image.subtitle })] }))] })), manager &&
152
152
  (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)(common_1.Button, { className: 'msp-btn-icon', title: 'Next state', icon: icons_2.ChevronRightSvg, onClick: () => manager === null || manager === void 0 ? void 0 : manager.loadNext() }) })] }));
153
153
  }
154
154
  exports.StateGalleryTitleBox = StateGalleryTitleBox;
155
+ /** Return tooltip text for an image */
156
+ function imageTooltip(img, status) {
157
+ var _a;
158
+ const tooltip = (status === 'error' ? '[Failed to load] \n' : status === 'loading' ? '[Loading] \n' : '')
159
+ + ((_a = img.title) !== null && _a !== void 0 ? _a : img.filename)
160
+ + (img.subtitle ? `: ${img.subtitle}` : '');
161
+ return tooltip;
162
+ }
package/lib/helpers.d.ts CHANGED
@@ -179,12 +179,13 @@ export declare class PreemptiveQueue<X, Y> {
179
179
  /** Request handling loop. Resolves when there are no more requests. Not to be awaited, should run in the background. */
180
180
  private handleRequests;
181
181
  }
182
+ /** Functions for working with plugin config items */
182
183
  export declare namespace PluginConfigUtils {
183
- type ConfigFor<T> = {
184
+ /** Type of config definition for given type of config values T */
185
+ type ConfigFor<T extends object> = {
184
186
  [key in keyof T]: PluginConfigItem<T[key]>;
185
187
  };
186
- function getConfigValues<T>(plugin: PluginContext | undefined, configItems: {
187
- [name in keyof T]: PluginConfigItem<T[name]>;
188
- }, defaults: T): T;
188
+ /** Retrieve config values for items in `configItems` from the current plugin config */
189
+ function getConfigValues<T extends object>(plugin: PluginContext | undefined, configItems: ConfigFor<T>, defaults: T): T;
189
190
  }
190
191
  export {};
package/lib/helpers.js CHANGED
@@ -507,8 +507,10 @@ class PreemptiveQueue {
507
507
  }
508
508
  }
509
509
  exports.PreemptiveQueue = PreemptiveQueue;
510
+ /** Functions for working with plugin config items */
510
511
  var PluginConfigUtils;
511
512
  (function (PluginConfigUtils) {
513
+ /** Retrieve config values for items in `configItems` from the current plugin config */
512
514
  function getConfigValues(plugin, configItems, defaults) {
513
515
  var _a;
514
516
  const values = {};
@@ -59,9 +59,11 @@ export interface PluginCustomState {
59
59
  };
60
60
  };
61
61
  superpositionError?: string;
62
+ /** Space for extensions to save their plugin-bound custom state. Only access via `ExtensionCustomState`! */
62
63
  extensions?: {
63
64
  [extensionId: string]: {} | undefined;
64
65
  };
66
+ /** Registry for custom UI components. Only access via `PluginCustomControls`! */
65
67
  customControls?: {
66
68
  [region in PluginCustomControlRegion]?: PluginCustomControlRegistry;
67
69
  };
@@ -83,9 +85,15 @@ export interface Segment {
83
85
  /** Access `plugin.customState` only through this function to get proper typing.
84
86
  * Supports getting and setting properties. */
85
87
  export declare function PluginCustomState(plugin: PluginContext): PluginCustomState;
86
- export declare function getExtensionCustomState<T extends {}>(plugin: PluginContext, extensionId: string): Partial<T>;
87
- export declare function clearExtensionCustomState(plugin: PluginContext, extensionId: string): void;
88
- export declare function extensionCustomStateGetter<StateType extends {}>(extensionId: string): (plugin: PluginContext) => Partial<StateType>;
88
+ /** Functions for accessing plugin-bound custom state for extensions. */
89
+ export declare const ExtensionCustomState: {
90
+ /** Get plugin-bound custom state for a specific extension. If not present, initialize with empty object. */
91
+ get<T extends {}>(plugin: PluginContext, extensionId: string): Partial<T>;
92
+ /** Remove plugin-bound custom state for a specific extension (if present). */
93
+ clear(plugin: PluginContext, extensionId: string): void;
94
+ /** Return function which gets plugin-bound custom state for a specific extension. */
95
+ getter<StateType extends {}>(extensionId: string): (plugin: PluginContext) => Partial<StateType>;
96
+ };
89
97
  /** UI region where custom controls can be registered */
90
98
  export type PluginCustomControlRegion = 'structure-tools' | 'viewport-top-center' | 'viewport-top-left';
91
99
  /** Collection of registered custom controls in a UI region */
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PluginCustomControls = exports.extensionCustomStateGetter = exports.clearExtensionCustomState = exports.getExtensionCustomState = exports.PluginCustomState = void 0;
3
+ exports.PluginCustomControls = exports.ExtensionCustomState = exports.PluginCustomState = void 0;
4
4
  ;
5
5
  ;
6
6
  /** Access `plugin.customState` only through this function to get proper typing.
@@ -10,25 +10,28 @@ function PluginCustomState(plugin) {
10
10
  return (_a = plugin.customState) !== null && _a !== void 0 ? _a : (plugin.customState = {});
11
11
  }
12
12
  exports.PluginCustomState = PluginCustomState;
13
- function getExtensionCustomState(plugin, extensionId) {
14
- var _a, _b;
15
- var _c;
16
- const extensionStates = (_a = (_c = PluginCustomState(plugin)).extensions) !== null && _a !== void 0 ? _a : (_c.extensions = {});
17
- const extensionState = (_b = extensionStates[extensionId]) !== null && _b !== void 0 ? _b : (extensionStates[extensionId] = {});
18
- return extensionState;
19
- }
20
- exports.getExtensionCustomState = getExtensionCustomState;
21
- function clearExtensionCustomState(plugin, extensionId) {
22
- var _a;
23
- var _b;
24
- const extensionStates = (_a = (_b = PluginCustomState(plugin)).extensions) !== null && _a !== void 0 ? _a : (_b.extensions = {});
25
- delete extensionStates[extensionId];
26
- }
27
- exports.clearExtensionCustomState = clearExtensionCustomState;
28
- function extensionCustomStateGetter(extensionId) {
29
- return (plugin) => getExtensionCustomState(plugin, extensionId);
30
- }
31
- exports.extensionCustomStateGetter = extensionCustomStateGetter;
13
+ /** Functions for accessing plugin-bound custom state for extensions. */
14
+ exports.ExtensionCustomState = {
15
+ /** Get plugin-bound custom state for a specific extension. If not present, initialize with empty object. */
16
+ get(plugin, extensionId) {
17
+ var _a, _b;
18
+ var _c;
19
+ const extensionStates = (_a = (_c = PluginCustomState(plugin)).extensions) !== null && _a !== void 0 ? _a : (_c.extensions = {});
20
+ const extensionState = (_b = extensionStates[extensionId]) !== null && _b !== void 0 ? _b : (extensionStates[extensionId] = {});
21
+ return extensionState;
22
+ },
23
+ /** Remove plugin-bound custom state for a specific extension (if present). */
24
+ clear(plugin, extensionId) {
25
+ var _a;
26
+ var _b;
27
+ const extensionStates = (_a = (_b = PluginCustomState(plugin)).extensions) !== null && _a !== void 0 ? _a : (_b.extensions = {});
28
+ delete extensionStates[extensionId];
29
+ },
30
+ /** Return function which gets plugin-bound custom state for a specific extension. */
31
+ getter(extensionId) {
32
+ return (plugin) => this.get(plugin, extensionId);
33
+ },
34
+ };
32
35
  /** Functions for registering/unregistering custom UI controls */
33
36
  exports.PluginCustomControls = {
34
37
  /** Get custom controls in the specified UI `region`. */
package/lib/spec.d.ts CHANGED
@@ -77,7 +77,9 @@ export interface InitParams {
77
77
  };
78
78
  /** Display 3D State Gallery */
79
79
  galleryView: boolean;
80
- /** Leave `undefined` to keep both cartoon and ball-and-sticks based on component type */
80
+ /** Set default visual style.
81
+ * Leave undefined to use default visual styles for each component type (polymer, ligand etc.).
82
+ * Use a `VisualStylesSpec` object to define more detailed visual styles for individual component types, e.g. `{polymer: {type: 'putty', size: 'uniform'}, ligand: 'ball-and-stick'}` */
81
83
  visualStyle?: VisualStylesSpec;
82
84
  /** Molstar renders multiple visuals (polymer, ligand, water...) visuals by default. This option is to exclude any of these default visuals */
83
85
  hideStructure: ('polymer' | 'het' | 'water' | 'carbs' | 'nonStandard' | 'coarse')[];