mirror of
https://github.com/molstar/molstar.git
synced 2026-06-07 15:14:22 +08:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c0e7e84da | ||
|
|
0d1e105343 | ||
|
|
f040c89ab3 | ||
|
|
5e9d8298ef | ||
|
|
7766ca2793 | ||
|
|
fb2f22f120 | ||
|
|
146fed3504 | ||
|
|
0b7a6e3375 | ||
|
|
f1fbdeaca0 | ||
|
|
ee7e37f6bc | ||
|
|
861f665ab3 | ||
|
|
456de23ad4 | ||
|
|
6d3578c17e | ||
|
|
57da7267e2 | ||
|
|
578b764406 | ||
|
|
f65a38a085 | ||
|
|
b87beb4a6e | ||
|
|
62a58facb2 | ||
|
|
5fa8178df7 |
@@ -6,6 +6,14 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v3.6.2] - 2022-04-05
|
||||
|
||||
- ModelServer ligand queries: fixes for alternate locations, additional atoms & UNL ligand
|
||||
- React 18 friendly ``useBehavior`` hook.
|
||||
|
||||
## [v3.6.1] - 2022-04-03
|
||||
|
||||
- Fix React18 related UI regressions.
|
||||
|
||||
## [v3.6.0] - 2022-04-03
|
||||
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.6.0",
|
||||
"version": "3.6.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "molstar",
|
||||
"version": "3.6.0",
|
||||
"version": "3.6.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/argparse": "^2.0.10",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "molstar",
|
||||
"version": "3.6.0",
|
||||
"version": "3.6.2",
|
||||
"description": "A comprehensive macromolecular library.",
|
||||
"homepage": "https://github.com/molstar/molstar#readme",
|
||||
"repository": {
|
||||
|
||||
@@ -80,20 +80,24 @@ export class AlphaOrbitalsExample {
|
||||
|
||||
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
|
||||
|
||||
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Error',
|
||||
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
|
||||
this.plugin.behaviors.canvas3d.initialized.subscribe(init => {
|
||||
if (!init) return;
|
||||
|
||||
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
|
||||
PluginCommands.Toast.Show(this.plugin, {
|
||||
title: 'Error',
|
||||
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.load({
|
||||
moleculeSdf: DemoMoleculeSDF,
|
||||
...DemoOrbitals
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.load({
|
||||
moleculeSdf: DemoMoleculeSDF,
|
||||
...DemoOrbitals
|
||||
mountControls(this, document.getElementById('controls')!);
|
||||
});
|
||||
|
||||
mountControls(this, document.getElementById('controls')!);
|
||||
}
|
||||
|
||||
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
|
||||
|
||||
@@ -9,12 +9,14 @@ import { Writer } from './writer';
|
||||
import { Encoder, Category, Field } from './cif/encoder';
|
||||
import { ComponentAtom } from '../../mol-model-formats/structure/property/atoms/chem_comp';
|
||||
import { ComponentBond } from '../../mol-model-formats/structure/property/bonds/chem_comp';
|
||||
import { getElementIdx, isHydrogen } from '../../mol-model/structure/structure/unit/bonds/common';
|
||||
import { ElementSymbol } from '../../mol-model/structure/model/types';
|
||||
|
||||
interface Atom {
|
||||
Cartn_x: number,
|
||||
Cartn_y: number,
|
||||
Cartn_z: number,
|
||||
type_symbol: string,
|
||||
type_symbol: ElementSymbol,
|
||||
index: number
|
||||
}
|
||||
|
||||
@@ -109,11 +111,12 @@ export abstract class LigandEncoder implements Encoder<string> {
|
||||
const key = it.move();
|
||||
|
||||
const lai = label_atom_id.value(key, data, index) as string;
|
||||
const ts = type_symbol.value(key, data, index) as string;
|
||||
if (this.skipHydrogen(ts)) {
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
// ignore all alternate locations after the first
|
||||
if (atoms.has(lai)) continue;
|
||||
|
||||
const ts = type_symbol.value(key, data, index) as ElementSymbol;
|
||||
if (this.skipHydrogen(ts)) continue;
|
||||
|
||||
const a: { [k: string]: (string | number) } = {};
|
||||
|
||||
for (let _f = 0, _fl = fields.length; _f < _fl; _f++) {
|
||||
@@ -131,11 +134,15 @@ export abstract class LigandEncoder implements Encoder<string> {
|
||||
return atoms;
|
||||
}
|
||||
|
||||
protected skipHydrogen(type_symbol: string) {
|
||||
protected skipHydrogen(type_symbol: ElementSymbol) {
|
||||
if (this.hydrogens) {
|
||||
return false;
|
||||
}
|
||||
return type_symbol === 'H';
|
||||
return this.isHydrogen(type_symbol);
|
||||
}
|
||||
|
||||
protected isHydrogen(type_symbol: ElementSymbol) {
|
||||
return isHydrogen(getElementIdx(type_symbol));
|
||||
}
|
||||
|
||||
private getSortedFields<Ctx>(instance: Category.Instance<Ctx>, names: string[]) {
|
||||
|
||||
@@ -26,14 +26,27 @@ export class MolEncoder extends LigandEncoder {
|
||||
|
||||
const atomMap = this.componentAtomData.entries.get(name)!;
|
||||
const bondMap = this.componentBondData.entries.get(name)!;
|
||||
// happens for the unknown ligands (UNL)
|
||||
if (!atomMap) throw Error(`The Chemical Component Dictionary doesn't hold any atom data for ${name}`);
|
||||
|
||||
let bondCount = 0;
|
||||
let chiral = false;
|
||||
|
||||
// traverse once to determine all actually present atoms
|
||||
const atoms = this.getAtoms(instance, source);
|
||||
atoms.forEach((atom1, label_atom_id1) => {
|
||||
const { index: i1 } = atom1;
|
||||
const { charge, stereo_config } = atomMap.map.get(label_atom_id1)!;
|
||||
const { index: i1, type_symbol: type_symbol1 } = atom1;
|
||||
const atomMapData1 = atomMap.map.get(label_atom_id1);
|
||||
|
||||
if (!atomMapData1) {
|
||||
if (this.isHydrogen(type_symbol1)) {
|
||||
return;
|
||||
} else {
|
||||
throw Error(`Unknown atom ${label_atom_id1} for component ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
const { charge, stereo_config } = atomMapData1;
|
||||
StringBuilder.writePadLeft(ctab, atom1.Cartn_x.toFixed(4), 10);
|
||||
StringBuilder.writePadLeft(ctab, atom1.Cartn_y.toFixed(4), 10);
|
||||
StringBuilder.writePadLeft(ctab, atom1.Cartn_z.toFixed(4), 10);
|
||||
@@ -50,8 +63,8 @@ export class MolEncoder extends LigandEncoder {
|
||||
const atom2 = atoms.get(label_atom_id2);
|
||||
if (!atom2) return;
|
||||
|
||||
const { index: i2, type_symbol: type_symbol2 } = atom2;
|
||||
if (i1 < i2 && !this.skipHydrogen(type_symbol2)) {
|
||||
const { index: i2 } = atom2;
|
||||
if (i1 < i2) {
|
||||
const { order } = bond;
|
||||
StringBuilder.writeIntegerPadLeft(bonds, i1 + 1, 3);
|
||||
StringBuilder.writeIntegerPadLeft(bonds, i2 + 1, 3);
|
||||
|
||||
@@ -29,21 +29,34 @@ export class Mol2Encoder extends LigandEncoder {
|
||||
const name = this.getName(instance, source);
|
||||
StringBuilder.writeSafe(this.builder, `# Name: ${name}\n# Created by ${this.encoder}\n\n`);
|
||||
|
||||
const atomMap = this.componentAtomData.entries.get(name)!;
|
||||
const bondMap = this.componentBondData.entries.get(name)!;
|
||||
// happens for the unknown ligands (UNL)
|
||||
if (!atomMap) throw Error(`The Chemical Component Dictionary doesn't hold any atom data for ${name}`);
|
||||
let bondCount = 0;
|
||||
|
||||
const atoms = this.getAtoms(instance, source);
|
||||
StringBuilder.writeSafe(a, '@<TRIPOS>ATOM\n');
|
||||
StringBuilder.writeSafe(b, '@<TRIPOS>BOND\n');
|
||||
atoms.forEach((atom1, label_atom_id1) => {
|
||||
const { index: i1 } = atom1;
|
||||
const { index: i1, type_symbol: type_symbol1 } = atom1;
|
||||
const atomMapData1 = atomMap.map.get(label_atom_id1);
|
||||
|
||||
if (!atomMapData1) {
|
||||
if (this.isHydrogen(type_symbol1)) {
|
||||
return;
|
||||
} else {
|
||||
throw Error(`Unknown atom ${label_atom_id1} for component ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (bondMap?.map) {
|
||||
bondMap.map.get(label_atom_id1)!.forEach((bond, label_atom_id2) => {
|
||||
const atom2 = atoms.get(label_atom_id2);
|
||||
if (!atom2) return;
|
||||
|
||||
const { index: i2, type_symbol: type_symbol2 } = atom2;
|
||||
if (i1 < i2 && !this.skipHydrogen(type_symbol2)) {
|
||||
const { index: i2 } = atom2;
|
||||
if (i1 < i2) {
|
||||
const { order, flags } = bond;
|
||||
const ar = BondType.is(BondType.Flag.Aromatic, flags);
|
||||
StringBuilder.writeSafe(b, `${++bondCount} ${i1 + 1} ${i2 + 1} ${ar ? 'ar' : order}`);
|
||||
@@ -52,7 +65,7 @@ export class Mol2Encoder extends LigandEncoder {
|
||||
});
|
||||
}
|
||||
|
||||
const sybyl = bondMap?.map ? this.mapToSybyl(label_atom_id1, atom1.type_symbol, bondMap) : atom1.type_symbol;
|
||||
const sybyl = bondMap?.map ? this.mapToSybyl(label_atom_id1, type_symbol1, bondMap) : type_symbol1;
|
||||
StringBuilder.writeSafe(a, `${i1 + 1} ${label_atom_id1} ${atom1.Cartn_x.toFixed(3)} ${atom1.Cartn_y.toFixed(3)} ${atom1.Cartn_z.toFixed(3)} ${sybyl} 1 ${name} 0.000\n`);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2020-21 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-22 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import React from 'react';
|
||||
import { skip } from 'rxjs';
|
||||
|
||||
interface Behavior<T> {
|
||||
value: T;
|
||||
subscribe(f: (v: T) => void): { unsubscribe(): void };
|
||||
}
|
||||
|
||||
export function useBehavior<T>(s: Behavior<T>): T;
|
||||
// eslint-disable-next-line
|
||||
export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined;
|
||||
// eslint-disable-next-line
|
||||
export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined {
|
||||
const [, next] = useState({});
|
||||
const current = useRef<T>();
|
||||
function useBehaviorLegacy<T>(s: Behavior<T> | undefined): T | undefined {
|
||||
const [, next] = React.useState({});
|
||||
const current = React.useRef<T>();
|
||||
current.current = s?.value;
|
||||
|
||||
useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
@@ -32,4 +29,29 @@ export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined {
|
||||
}, [s]);
|
||||
|
||||
return s?.value;
|
||||
}
|
||||
|
||||
function useBehaviorReact18<T>(s: Behavior<T> | undefined) {
|
||||
return (React as any).useSyncExternalStore(
|
||||
React.useCallback(
|
||||
(callback: () => void) => {
|
||||
const sub = (s as any)?.pipe!(skip(1)).subscribe(callback)!;
|
||||
return () => sub?.unsubscribe();
|
||||
},
|
||||
[s]
|
||||
),
|
||||
React.useCallback(() => s?.value, [s])
|
||||
);
|
||||
}
|
||||
|
||||
const _useBehavior = !!(React as any).useSyncExternalStore
|
||||
? useBehaviorReact18
|
||||
: useBehaviorLegacy;
|
||||
|
||||
export function useBehavior<T>(s: Behavior<T>): T;
|
||||
// eslint-disable-next-line
|
||||
export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined;
|
||||
// eslint-disable-next-line
|
||||
export function useBehavior<T>(s: Behavior<T> | undefined): T | undefined {
|
||||
return _useBehavior(s);
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import { PurePluginUIComponent } from '../base';
|
||||
import { ParameterControls, ParamOnChange } from '../controls/parameters';
|
||||
import { PluginContext } from '../../mol-plugin/context';
|
||||
import { ParamDefinition as PD } from '../../mol-util/param-definition';
|
||||
import { Subject } from 'rxjs';
|
||||
import { BehaviorSubject, skip } from 'rxjs';
|
||||
import { Icon, RefreshSvg, CheckSvg, ArrowRightSvg, ArrowDropDownSvg, TuneSvg } from '../controls/icons';
|
||||
import { ExpandGroup, ToggleButton, Button, IconButton } from '../controls/common';
|
||||
|
||||
@@ -124,7 +124,7 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
|
||||
abstract getSourceAndTarget(): { a?: StateObject, b?: StateObject, bCell?: StateObjectCell };
|
||||
abstract state: S;
|
||||
|
||||
private busy: Subject<boolean> = new Subject();
|
||||
private busy = new BehaviorSubject(false);
|
||||
|
||||
private onEnter = () => {
|
||||
if (this.state.error) return;
|
||||
@@ -167,11 +167,11 @@ abstract class TransformControlBase<P, S extends TransformControlBase.ComponentS
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, b => {
|
||||
if (this.state.busy !== b) this.busy.next(b);
|
||||
this.subscribe(this.plugin.behaviors.state.isBusy, busy => {
|
||||
if (this.busy.value !== busy) this.busy.next(busy);
|
||||
});
|
||||
this.subscribe(this.busy, busy => {
|
||||
if (this.state.busy !== busy) this.setState({ busy });
|
||||
this.subscribe(this.busy.pipe(skip(1)), busy => {
|
||||
this.setState({ busy });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,32 +4,39 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
import { PluginUIComponent } from './base';
|
||||
import { PluginReactContext, PluginUIComponent } from './base';
|
||||
import { OrderedMap } from 'immutable';
|
||||
import { TaskManager } from '../mol-plugin/util/task-manager';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { Progress } from '../mol-task';
|
||||
import { IconButton } from './controls/common';
|
||||
import { CancelSvg } from './controls/icons';
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
|
||||
export class BackgroundTaskProgress extends PluginUIComponent<{ }, { tracked: OrderedMap<number, TaskManager.ProgressEvent> }> {
|
||||
componentDidMount() {
|
||||
const hideOverlay = !!this.plugin.spec.components?.hideTaskOverlay;
|
||||
this.subscribe(this.plugin.events.task.progress.pipe(filter(e => e.level === 'background' && (hideOverlay || !e.useOverlay))), e => {
|
||||
this.setState({ tracked: this.state.tracked.set(e.id, e) });
|
||||
export function BackgroundTaskProgress() {
|
||||
const plugin = useContext(PluginReactContext);
|
||||
const [tracked, setTracked] = useState<OrderedMap<number, TaskManager.ProgressEvent>>(OrderedMap());
|
||||
|
||||
useEffect(() => {
|
||||
const started = plugin.events.task.progress.subscribe(e => {
|
||||
const hideOverlay = !!plugin.spec.components?.hideTaskOverlay;
|
||||
if (e.level === 'background' && (hideOverlay || !e.useOverlay)) {
|
||||
setTracked(tracked => tracked.set(e.id, e));
|
||||
}
|
||||
});
|
||||
this.subscribe(this.plugin.events.task.finished, ({ id }) => {
|
||||
this.setState({ tracked: this.state.tracked.delete(id) });
|
||||
|
||||
const finished = plugin.events.task.finished.subscribe(({ id }) => {
|
||||
setTracked(tracked => tracked.delete(id));
|
||||
});
|
||||
}
|
||||
|
||||
state = { tracked: OrderedMap<number, TaskManager.ProgressEvent>() };
|
||||
return () => {
|
||||
started.unsubscribe();
|
||||
finished.unsubscribe();
|
||||
};
|
||||
}, [plugin]);
|
||||
|
||||
render() {
|
||||
return <div className='msp-background-tasks'>
|
||||
{this.state.tracked.valueSeq().map(e => <ProgressEntry key={e!.id} event={e!} />)}
|
||||
</div>;
|
||||
}
|
||||
return <div className='msp-background-tasks'>
|
||||
{tracked.valueSeq().map(e => <ProgressEntry key={e!.id} event={e!} />)}
|
||||
</div>;
|
||||
}
|
||||
|
||||
class ProgressEntry extends PluginUIComponent<{ event: TaskManager.ProgressEvent }> {
|
||||
@@ -65,23 +72,30 @@ function countSubtasks(progress: Progress.Node) {
|
||||
return sum;
|
||||
}
|
||||
|
||||
export class OverlayTaskProgress extends PluginUIComponent<{ }, { tracked: OrderedMap<number, TaskManager.ProgressEvent> }> {
|
||||
componentDidMount() {
|
||||
this.subscribe(this.plugin.events.task.progress.pipe(filter(e => !!e.useOverlay)), e => {
|
||||
this.setState({ tracked: this.state.tracked.set(e.id, e) });
|
||||
export function OverlayTaskProgress() {
|
||||
const plugin = useContext(PluginReactContext);
|
||||
const [tracked, setTracked] = useState<OrderedMap<number, TaskManager.ProgressEvent>>(OrderedMap());
|
||||
|
||||
useEffect(() => {
|
||||
const started = plugin.events.task.progress.subscribe(e => {
|
||||
if (!!e.useOverlay) {
|
||||
setTracked(tracked => tracked.set(e.id, e));
|
||||
}
|
||||
});
|
||||
this.subscribe(this.plugin.events.task.finished, ({ id }) => {
|
||||
this.setState({ tracked: this.state.tracked.delete(id) });
|
||||
|
||||
const finished = plugin.events.task.finished.subscribe(({ id }) => {
|
||||
setTracked(tracked => tracked.delete(id));
|
||||
});
|
||||
}
|
||||
|
||||
state = { tracked: OrderedMap<number, TaskManager.ProgressEvent>() };
|
||||
return () => {
|
||||
started.unsubscribe();
|
||||
finished.unsubscribe();
|
||||
};
|
||||
}, [plugin]);
|
||||
|
||||
render() {
|
||||
if (this.state.tracked.size === 0) return null;
|
||||
if (tracked.size === 0) return null;
|
||||
|
||||
return <div className='msp-overlay-tasks'>
|
||||
{this.state.tracked.valueSeq().map(e => <ProgressEntry key={e!.id} event={e!} />)}
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
return <div className='msp-overlay-tasks'>
|
||||
{tracked.valueSeq().map(e => <ProgressEntry key={e!.id} event={e!} />)}
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,6 @@ export async function getContourLevelEmdb(plugin: PluginContext, taskCtx: Runtim
|
||||
}
|
||||
}
|
||||
const contourLevel = parseFloat(primaryContour.getElementsByTagName('level')[0].textContent!);
|
||||
|
||||
return contourLevel;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
# 0.9.9
|
||||
* /ligand queries: fix behavior for alternate locations
|
||||
* /ligand queries: handle additional atoms more gracefully
|
||||
* /ligand queries: better error message for UNL
|
||||
* /ligand queries: treat deuterium/tritium as hydrogen
|
||||
|
||||
# 0.9.8
|
||||
* fix support for chem_comp_bond and struct_conn categories
|
||||
|
||||
|
||||
@@ -237,10 +237,8 @@ async function resolveJobEntry(entry: JobEntry, structure: StructureWrapper, enc
|
||||
encoder.writeCategory(_model_server_params, entry);
|
||||
|
||||
if (entry.queryDefinition.niceName === 'Ligand') {
|
||||
if (encoder instanceof MolEncoder) {
|
||||
encoder.setComponentAtomData(ComponentAtom.Provider.get(structure.models[0])!);
|
||||
}
|
||||
if (encoder instanceof MolEncoder || encoder instanceof Mol2Encoder) {
|
||||
encoder.setComponentAtomData(ComponentAtom.Provider.get(structure.models[0])!);
|
||||
encoder.setComponentBondData(ComponentBond.Provider.get(structure.models[0])!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
*/
|
||||
|
||||
export const VERSION = '0.9.8';
|
||||
export const VERSION = '0.9.9';
|
||||
Reference in New Issue
Block a user